Skip to content

Commit

Permalink
refactor auth
Browse files Browse the repository at this point in the history
  • Loading branch information
wermarter committed Dec 27, 2023
1 parent 0d12502 commit a7c18a5
Show file tree
Hide file tree
Showing 21 changed files with 157 additions and 82 deletions.
2 changes: 1 addition & 1 deletion apps/levansy-access-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"license": "MIT",
"scripts": {
"build": "rimraf dist && nest build",
"dev": "nest start --watch --preserveWatchOutput",
"dev": "rimraf dist && nest start --watch --preserveWatchOutput",
"lint": "tsc && rimraf dist"
},
"dependencies": {
Expand Down
4 changes: 2 additions & 2 deletions apps/levansy-access-service/src/domain/use-case/auth/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ export class AuthLoginUseCase {
const authPayload: AuthPayload = {
userId: user._id,
}
const jwtAccessToken = await this.jwtService.signAsync(authPayload)
const accessToken = await this.jwtService.signAsync(authPayload)

return { user, jwtAccessToken }
return { user, accessToken }
}
}
4 changes: 1 addition & 3 deletions apps/levansy-access-service/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
CorsBootstrap,
HttpAppFactory,
HttpListenBootstrap,
InterceptorBootstrap,
LifecycleBootstrap,
LogBootstrap,
PipeBootstrap,
Expand All @@ -21,13 +20,12 @@ bootstrapApp(
HttpAppFactory,
AppModule,
{
serviceName: 'process.env.SERVICE_NAME',
serviceName: process.env.SERVICE_NAME,
nodeEnv: process.env.NODE_ENV,
},
[
LogBootstrap,
CorsBootstrap,
InterceptorBootstrap,
LifecycleBootstrap,
PipeBootstrap,
PrefixBootstrap,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { AuthPayload, AuthContextToken, IAuthContext } from 'src/domain'
import { HTTP_PUBLIC_ROUTE } from './common'

@Injectable({ scope: Scope.REQUEST })
export class AuthContextGuard implements CanActivate {
export class HttpAuthContextGuard implements CanActivate {
constructor(
@Inject(AuthContextToken)
private authContext: IAuthContext,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { NodeEnv } from '@diut/common'
import { Inject, Injectable } from '@nestjs/common'
import { CookieOptions, Response, Request } from 'express'

import {
AppConfig,
AuthConfig,
loadAppConfig,
loadAuthConfig,
} from 'src/config'

export type AuthCookiePayload = {
accessToken: string
}

@Injectable()
export class AuthCookieService {
private readonly accessTokenCookieName: string

constructor(
@Inject(loadAuthConfig.KEY) private authConfig: AuthConfig,
@Inject(loadAppConfig.KEY) private appConfig: AppConfig,
) {
this.accessTokenCookieName = `${appConfig.SERVICE_NAME}-access_token`
}

private makeCookieOptions(): CookieOptions {
const isDevelopment = this.appConfig.NODE_ENV === NodeEnv.Development

return {
signed: true,
httpOnly: true,
secure: !isDevelopment,
sameSite: 'lax',
expires: new Date(
Date.now() + parseInt(this.authConfig.AUTH_JWT_EXPIRE_SECONDS) * 1000,
),
}
}

setAuthCookie(res: Response, cookiePayload: AuthCookiePayload) {
this.clearAuthCookie(res)

const cookieOptions = this.makeCookieOptions()
res.cookie(
this.accessTokenCookieName,
cookiePayload.accessToken,
cookieOptions,
)
}

getAuthCookie(req: Request): Partial<AuthCookiePayload> {
const accessToken = req.signedCookies[this.accessTokenCookieName]

return { accessToken }
}

clearAuthCookie(res: Response) {
res.clearCookie(this.accessTokenCookieName)
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AuthContextGuard } from './context.guard'
import { AuthJwtGuard } from './jwt'
import { HttpAuthContextGuard } from './context.guard'
import { HttpJwtGuard } from './jwt'

export const authGuards = [AuthJwtGuard, AuthContextGuard]
export const authGuards = [HttpJwtGuard, HttpAuthContextGuard]
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './module'
export * from './jwt'
export * from './guards'
export * from './common'
export * from './cookie.service'
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
export const AUTH_JWT_STRATEGY_KEY = 'auth.jwt'

export const AUTH_JWT_ACCESS_TOKEN_COOKIE = 'diut_levansy_access_token'
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { EAuthnInvalidJwtToken, EUnknown, EDomain } from 'src/domain'
import { HTTP_PUBLIC_ROUTE } from '../common'

@Injectable()
export class AuthJwtGuard
export class HttpJwtGuard
extends AuthGuard(AUTH_JWT_STRATEGY_KEY)
implements CanActivate
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,40 +1,35 @@
import { Inject, Injectable } from '@nestjs/common'
import { PassportStrategy } from '@nestjs/passport'
import { Strategy, VerifiedCallback, StrategyOptions } from 'passport-jwt'
import { NodeEnv } from '@diut/common'
import { Request } from 'express'

import { AUTH_JWT_ACCESS_TOKEN_COOKIE, AUTH_JWT_STRATEGY_KEY } from './common'
import {
AppConfig,
AuthConfig,
loadAppConfig,
loadAuthConfig,
} from 'src/config'
import { AUTH_JWT_STRATEGY_KEY } from './common'
import { AuthConfig, loadAuthConfig } from 'src/config'
import {
AuthPayload,
EAuthnAccessTokenCookieExpired,
EAuthzPayloadNotFound,
} from 'src/domain'
import { AuthCookieService } from '../cookie.service'

@Injectable()
export class JwtStrategy extends PassportStrategy(
export class HttpJwtStrategy extends PassportStrategy(
Strategy,
AUTH_JWT_STRATEGY_KEY,
) {
constructor(
@Inject(loadAppConfig.KEY) appConfig: AppConfig,
@Inject(loadAuthConfig.KEY) authConfig: AuthConfig,
authCookieService: AuthCookieService,
) {
super({
// ignoreExpiration: appConfig.NODE_ENV === NodeEnv.Development,
jwtFromRequest: (req: Request) => {
const jwtAccessToken = req.signedCookies[AUTH_JWT_ACCESS_TOKEN_COOKIE]
if (!jwtAccessToken) {
const { accessToken } = authCookieService.getAuthCookie(req)

if (!accessToken) {
throw new EAuthnAccessTokenCookieExpired()
}

return jwtAccessToken
return accessToken
},
secretOrKey: authConfig.AUTH_JWT_SECRET,
} satisfies StrategyOptions)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { ModuleMetadata } from '@nestjs/common'

import { JwtStrategy } from './jwt'
import { HttpJwtStrategy } from './jwt'
import { AuthCookieService } from './cookie.service'

export const authMetadata: ModuleMetadata = {
providers: [JwtStrategy],
providers: [HttpJwtStrategy, AuthCookieService],
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { UseFilters, UseGuards } from '@nestjs/common'
import {
CustomHttpController,
CustomHttpControllerOptions,
} from '@diut/nest-core'

import { authGuards } from '../auth'
import { exceptionFilters } from '../exception'

export const HttpController = (options: CustomHttpControllerOptions) => {
const customOptions: CustomHttpControllerOptions = {
...options,
controllerDecorators: [
...(options?.controllerDecorators ?? []),
UseGuards(...authGuards),
UseFilters(...exceptionFilters),
],
}

return CustomHttpController(customOptions)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { CustomHttpRoute, CustomHttpRouteOptions } from '@diut/nest-core'

import { HttpErrorResponse } from '../dto'

export const HttpRoute = (options: CustomHttpRouteOptions) => {
const customOptions: CustomHttpRouteOptions = {
...options,
openApi: {
...options?.openApi,
responses: [
...(options?.openApi?.responses ?? []),
{
status: '4XX',
description: 'Custom error',
type: HttpErrorResponse,
},
],
},
}

return CustomHttpRoute(customOptions)
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './controller-decorators'
export * from './route-decorators'
export * from './http-controller.decorator'
export * from './http-route.decorator'

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,60 +1,39 @@
import { CustomHttpController, CustomHttpRoute } from '@diut/nest-core'
import { Body, Inject, Res } from '@nestjs/common'
import { omit } from 'lodash'
import { CookieOptions, Response } from 'express'
import { Body, Res } from '@nestjs/common'
import { Response } from 'express'

import { AuthMeUseCase, AuthLoginUseCase, EAuthzUserNotFound } from 'src/domain'
import { AuthLoginRequestDto } from './dto/login.request'
import { authRoutes } from './routes'
import { LoginResponseDto } from './dto/login.response'
import { AuthMeResponseDto } from './dto/me.response'
import { AUTH_JWT_ACCESS_TOKEN_COOKIE, HttpPublicRoute } from '../../common'
import {
AppConfig,
AuthConfig,
loadAppConfig,
loadAuthConfig,
} from 'src/config'
import { NodeEnv } from '@diut/common'
AuthCookieService,
HttpController,
HttpPublicRoute,
HttpRoute,
} from '../../common'

@CustomHttpController(authRoutes.controller)
@HttpController(authRoutes.controller)
export class AuthController {
constructor(
private authMeUseCase: AuthMeUseCase,
private authLoginUseCase: AuthLoginUseCase,
@Inject(loadAuthConfig.KEY) private authConfig: AuthConfig,
@Inject(loadAppConfig.KEY) private appConfig: AppConfig,
private authCookieService: AuthCookieService,
) {}

private makeCookieOptions(): CookieOptions {
const isDevelopment = this.appConfig.NODE_ENV === NodeEnv.Development

return {
signed: true,
httpOnly: true,
secure: !isDevelopment,
sameSite: 'lax',
expires: new Date(
Date.now() + parseInt(this.authConfig.AUTH_JWT_EXPIRE_SECONDS) * 1000,
),
}
}

@CustomHttpRoute(authRoutes.login)
@HttpRoute(authRoutes.login)
@HttpPublicRoute
async login(
@Body() body: AuthLoginRequestDto,
@Res({ passthrough: true }) res: Response,
): Promise<LoginResponseDto> {
const { user, jwtAccessToken } = await this.authLoginUseCase.execute(body)
const cookieOptions = this.makeCookieOptions()
const { user, accessToken } = await this.authLoginUseCase.execute(body)
this.authCookieService.setAuthCookie(res, { accessToken })

res.cookie(AUTH_JWT_ACCESS_TOKEN_COOKIE, jwtAccessToken, cookieOptions)

return omit(user, 'password')
return user
}

@CustomHttpRoute(authRoutes.me)
@HttpRoute(authRoutes.me)
async me(): Promise<AuthMeResponseDto> {
const info = await this.authMeUseCase.execute()

Expand All @@ -64,4 +43,9 @@ export class AuthController {

return info
}

@HttpRoute(authRoutes.logout)
logout(@Res({ passthrough: true }) res: Response): void {
this.authCookieService.clearAuthCookie(res)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,18 @@ import {
import { HttpStatus, RequestMethod } from '@nestjs/common'

import { LoginResponseDto } from './dto/login.response'
import { controllerDecorators } from '../../common'

export const authRoutes = {
controller: <CustomHttpControllerOptions>{
basePath: 'v1/auth',
controllerDecorators,
},

login: <CustomHttpRouteOptions>{
isPublic: true,
path: 'login',
method: RequestMethod.POST,
code: HttpStatus.OK,
// serialize: LoginResponseDto,
serialize: LoginResponseDto,
openApi: {
responses: [
{
Expand All @@ -38,4 +36,10 @@ export const authRoutes = {
],
},
},

logout: <CustomHttpRouteOptions>{
path: 'logout',
method: RequestMethod.POST,
code: HttpStatus.OK,
},
}
2 changes: 1 addition & 1 deletion apps/nest-template/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"license": "MIT",
"scripts": {
"build": "rimraf dist && nest build",
"dev": "nest start --watch --preserveWatchOutput",
"dev": "rimraf dist && nest start --watch --preserveWatchOutput",
"lint": "tsc && rimraf dist"
},
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion apps/puppeteer-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"license": "MIT",
"scripts": {
"build": "rimraf dist && nest build",
"dev": "nest start --watch --preserveWatchOutput",
"dev": "rimraf dist && nest start --watch --preserveWatchOutput",
"lint": "tsc && rimraf dist"
},
"dependencies": {
Expand Down
Loading

0 comments on commit a7c18a5

Please sign in to comment.