Skip to content

Commit

Permalink
fix(env.ts): accessing env variable via 'process.env' and implementin…
Browse files Browse the repository at this point in the history
…g Google Oauth

Previously, encountered an error when accessing env variables from `envs` outside `env.ts` in the
envalid library. As a temporary  solution, switched to using `process.env`.
Additionally, started
implementing the basics of Google OAuth but haven't verified it yet.

BREAKING CHANGE: Changing to using process.env instead of envs (envalid). Need to fix later.
  • Loading branch information
quannhg committed Nov 14, 2023
1 parent 0a03664 commit 1983de2
Show file tree
Hide file tree
Showing 17 changed files with 401 additions and 67 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/dev-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ jobs:
docker compose down --volumes --remove-orphans
docker compose rm -f ${{vars.DOCKER_COMPOSE_DEPLOY_SERVICE_NAME}}
docker compose up -d ${{vars.DOCKER_COMPOSE_DEPLOY_SERVICE_NAME}}
docker exec -it $(docker ps --filter "ancestor=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" -q) /bin/sh -c "npx prisma seed"
#TODO: npx prisma DB seed
docker exec -it $(docker ps --filter "ancestor=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" -q) /bin/sh -c "npx prisma db seed"
docker logout ${{ env.REGISTRY }}
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@
"axios": "^1.6.0",
"bcrypt": "^5.1.0",
"dotenv": "^16.3.1",
"envalid": "^7.3.1",
"envalid": "^8.0.0",
"fastify": "^4.21.0",
"fluent-json-schema": "^4.2.0-beta.0",
"googleapis": "^128.0.0",
"jsonwebtoken": "^9.0.1",
"minio": "^7.1.3",
"node-cache": "^5.1.2",
Expand All @@ -52,6 +53,7 @@
"@commitlint/cli": "^17.7.1",
"@commitlint/config-conventional": "^17.7.0",
"@types/bcrypt": "^5.0.0",
"@types/find-config": "^1.0.4",
"@types/jest": "^29.5.3",
"@types/jsonwebtoken": "^9.0.2",
"@types/node": "^20.4.8",
Expand Down
3 changes: 3 additions & 0 deletions prisma/migrations/20231111075656_/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "User" ALTER COLUMN "userName" DROP NOT NULL,
ALTER COLUMN "password" DROP NOT NULL;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
Warnings:
- A unique constraint covering the columns `[email]` on the table `User` will be added. If there are existing duplicate values, this will fail.
*/
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
6 changes: 3 additions & 3 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ datasource db {

model User {
id String @id @default(cuid())
userName String @unique @map("userName") @db.VarChar(50)
password String
userName String? @unique @map("userName") @db.VarChar(50)
password String?
role Int[]
name String
email String
email String @unique
Student Student[]
}

Expand Down
66 changes: 44 additions & 22 deletions src/configs/env.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,50 @@
import { logger } from '@utils';
import { config as configEnv } from 'dotenv';
import { str, num, cleanEnv } from 'envalid';
import { str, url, host, port, cleanEnv } from 'envalid';
import path from 'path';

configEnv();
const envFilePath = path.join(process.cwd(), '.env');
if (!envFilePath) {
logger.error('.env file not found.');
process.exit(1); // Exit the process with an error code
}
configEnv({ path: envFilePath });

export const envs = cleanEnv(process.env, {
NODE_ENV: str<NodeEnv>({
devDefault: 'development',
choices: ['development', 'test', 'production']
}),
JWT_SECRET: str(),
COOKIE_SECRET: str(),
CORS_WHITE_LIST: str(),
MINIO_URL: str(),
MINIO_SERVER_ENDPOINT: str(),
MINIO_PORT: num(),
MINIO_ACCESS_KEY: str(),
MINIO_SECRET_KEY: str(),
MINIO_BUCKET_NAME: str(),
CHECKOUT_ENVIRONMENT: str(),
PAYPAL_LIVE_ENDPOINT: str(),
PAYPAL_SANDBOX_ENDPOINT: str(),
PAYPAL_CLIENT_ID: str(),
PAYPAL_CLIENT_SECRET: str()
});
export const envs = cleanEnv(
process.env,
{
NODE_ENV: str<NodeEnv>({
devDefault: 'development',
choices: ['development', 'test', 'production']
}),
JWT_SECRET: str(),
COOKIE_SECRET: str(),
CORS_WHITE_LIST: str(),
MINIO_URL: url(),
MINIO_SERVER_ENDPOINT: host(),
MINIO_PORT: port(),
MINIO_ACCESS_KEY: str(),
MINIO_SECRET_KEY: str(),
MINIO_BUCKET_NAME: str(),
CHECKOUT_ENVIRONMENT: str(),
PAYPAL_LIVE_ENDPOINT: url(),
PAYPAL_SANDBOX_ENDPOINT: url(),
PAYPAL_CLIENT_ID: str(),
PAYPAL_CLIENT_SECRET: str(),
GOOGLE_CLIENT_ID: str({ default: 'anc' }),
GOOGLE_CLIENT_SECRET: str(),
GOOGLE_REDIRECT_URL: url()
},
{
// eslint-disable-next-line @typescript-eslint/no-unused-vars
reporter: ({ errors, env }) => {
logger.error(`error of envs: ${errors}`);
for (const [envVar, err] of Object.entries(errors)) {
logger.error(`Invalid env '${envVar}': ${Object.keys(err.message)}`);
}
}
}
);

export const CORS_WHITE_LIST = envs.CORS_WHITE_LIST.split(',');
export const PAYPAL_ENDPOINT = envs.CHECKOUT_ENVIRONMENT === 'live' ? envs.PAYPAL_LIVE_ENDPOINT : envs.PAYPAL_SANDBOX_ENDPOINT;
5 changes: 2 additions & 3 deletions src/constants/cookie.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { envs } from '@configs';

export const cookieOptions = {
signed: false,
secure: envs.isProduction,
//TODO: using envs
secure: process.env.isProduction,
path: '/',
httpOnly: true
};
9 changes: 9 additions & 0 deletions src/dtos/in/auth/googleOauth.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Static, Type } from '@sinclair/typebox';

// See https://github.com/sinclairzx81/typebox

export const GoogleOAuthParamsDto = Type.Object({
code: Type.String()
});

export type GoogleOAuthParamsDto = Static<typeof GoogleOAuthParamsDto>;
1 change: 1 addition & 0 deletions src/dtos/in/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export * from './checkout.dto';
export * from './printingRequest.dto';
export * from './uploadFile.dto ';
export * from './auth/auth.dto';
export * from './auth/googleOauth.dto';
export * from './auth/signUp.dto';
3 changes: 1 addition & 2 deletions src/dtos/out/auth.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { ObjectId } from '@dtos/common';
import { Static, Type } from '@sinclair/typebox';

export const AuthResultDto = Type.Object({
id: ObjectId,
userName: Type.String({ format: 'userName' })
id: ObjectId
});

export type AuthResultDto = Static<typeof AuthResultDto>;
59 changes: 51 additions & 8 deletions src/handlers/auth.handler.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { compare, hash } from 'bcrypt';
import { prisma } from '@repositories';
import { cookieOptions, DUPLICATED_userName, LOGIN_FAIL, SALT_ROUNDS, USER_NOT_FOUND } from '@constants';
import { getUserInfo, prisma } from '@repositories';
import { cookieOptions, DUPLICATED_userName, LOGIN_FAIL, SALT_ROUNDS, USER_NOT_FOUND, USER_ROLES } from '@constants';
import jwt from 'jsonwebtoken';
import { envs } from '@configs';
import { User } from '@prisma/client';
import { AuthInputDto, SignUpRequestDto } from '@dtos/in';
import { AuthInputDto, GoogleOAuthParamsDto, SignUpRequestDto } from '@dtos/in';
import { AuthResultDto } from '@dtos/out';
import { Handler } from '@interfaces';
import { logger } from '@utils';
import { UserRole } from 'src/types/auth';

const login: Handler<AuthResultDto, { Body: AuthInputDto }> = async (req, res) => {
const user = await prisma.user.findUnique({
Expand All @@ -21,15 +22,15 @@ const login: Handler<AuthResultDto, { Body: AuthInputDto }> = async (req, res) =
});
if (!user) return res.badRequest(USER_NOT_FOUND);

if (!user.password) return res.badRequest('User is not valid!');
const correctPassword = await compare(req.body.password, user.password);
if (!correctPassword) return res.badRequest(LOGIN_FAIL);

const userToken = jwt.sign({ userId: user.id, roles: user.role }, envs.JWT_SECRET);
res.setCookie('token', userToken, cookieOptions);

return {
id: user.id,
userName: user.userName
id: user.id
};
};

Expand All @@ -55,12 +56,54 @@ const signup: Handler<AuthResultDto, { Body: SignUpRequestDto }> = async (req, r
res.setCookie('token', userToken, cookieOptions);

return {
id: user.id,
userName: user.userName
id: user.id
};
};

const createUser = async (userData: { name: string; email: string; role: UserRole[] }) => {
return prisma.user.create({
data: userData,
select: { id: true }
});
};

const googleOAuth: Handler<AuthResultDto, { Params: GoogleOAuthParamsDto }> = async (req, res) => {
try {
const { userEmail, userName, isVerifiedEmail } = await getUserInfo(req.params.code);

if (!isVerifiedEmail) {
return res.status(406).send('Email needs to be verified for authentication.');
}

if (userEmail && userName) {
const user = await prisma.user.findUnique({
where: { email: userEmail },
select: { id: true }
});

const userData = {
name: userName,
email: userEmail,
role: [USER_ROLES.student]
};

const userId = user ? user.id : (await createUser(userData)).id;

const userToken = jwt.sign({ userId: userId }, envs.JWT_SECRET);
res.setCookie('token', userToken, cookieOptions);

return { id: userId };
} else {
res.status(400).send('User information not available.');
}
} catch (error) {
console.error('Error processing user information:', error);
res.status(500).send('Error processing user information');
}
};

export const authHandler = {
login,
signup
signup,
googleOAuth
};
29 changes: 29 additions & 0 deletions src/repositories/googleOAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// import { envs } from '@configs';
import { google } from 'googleapis';

export const googleOAuth2Client = new google.auth.OAuth2(
//TODO: using envs
process.env.GOOGLE_CLIENT_ID,
process.env.GOOGLE_CLIENT_SECRET,
process.env.GOOGLE_REDIRECT_URL
);

export const getUserInfo = async (authorizationCode: string) => {
const { tokens } = await googleOAuth2Client.getToken(authorizationCode);
googleOAuth2Client.setCredentials(tokens);

const peopleApi = google.people({ version: 'v1', auth: googleOAuth2Client });
const userInfo = await peopleApi.people.get({
resourceName: 'people/me',
personFields: 'emailAddresses,names'
});

const userEmail = userInfo.data.emailAddresses?.[0].value;
const isVerifiedEmail = userInfo.data.emailAddresses?.[0].metadata?.verified || false;
const userName = userInfo.data.names?.[0].displayName;
return {
userEmail,
userName,
isVerifiedEmail
};
};
1 change: 1 addition & 0 deletions src/repositories/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* @file Automatically generated by barrelsby.
*/

export * from './googleOAuth';
export * from './minio';
export * from './nodePrinter';
export * from './prisma';
10 changes: 5 additions & 5 deletions src/repositories/minio.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { envs } from '@configs';
import * as Minio from 'minio';

export const minioClient = new Minio.Client({
endPoint: envs.MINIO_SERVER_ENDPOINT,
port: envs.MINIO_PORT,
//TODO: using envs
endPoint: process.env.MINIO_SERVER_ENDPOINT || '14.225.192.183',
port: Number(process.env.MINIO_PORT) || 9000,
useSSL: false,
accessKey: envs.MINIO_ACCESS_KEY,
secretKey: envs.MINIO_SECRET_KEY
accessKey: process.env.MINIO_ACCESS_KEY || '',
secretKey: process.env.MINIO_SECRET_KEY || ''
});
9 changes: 9 additions & 0 deletions src/routes/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,14 @@ export const authPlugin = createRoutes('Auth', [
}
},
handler: authHandler.signup
},
{
method: 'POST',
url: '/google',
roles: ['*'],
schema: {
summary: 'Redirect URL of google auth'
},
handler: authHandler.googleOAuth
}
]);
5 changes: 3 additions & 2 deletions src/utils/logger.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { envs, loggerConfig } from '@configs';
import { loggerConfig } from '@configs';
import pino from 'pino';

export const logger = pino(loggerConfig[envs.NODE_ENV]);
//TODO: using envs
export const logger = pino(loggerConfig['development']);
Loading

0 comments on commit 1983de2

Please sign in to comment.