Skip to content

Commit

Permalink
Feature 2fa (#213)
Browse files Browse the repository at this point in the history
* Create migration

* Backend api for 2FA feature

* Recovery 2fa feature

* Update yarn lock

* Fix email template

* Add 2fa to front end

* Implement recovery process, fix bugs

* Update disable 2fa email and flow

* added default value for jwt expirity

* Fix comment

* Fix test

* Fix recovery codes display error and double toast message

* Turn off 2fa

* Turn of 2fa platform

* Fix lint

---------

Co-authored-by: Vygandas Pliasas <[email protected]>
  • Loading branch information
PhamAnhHoang and vygandas authored Feb 23, 2024
1 parent 782f633 commit 2aa7119
Show file tree
Hide file tree
Showing 49 changed files with 1,319 additions and 82 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ JWT_ACCESS_TOKEN_EXPIRATION_TIME=60
JWT_REFRESH_TOKEN_EXPIRATION_TIME=28800

RESET_PASSWORD_PERIOD=15

JWT_REFRESH_TOKEN_EXPIRATION_TIME="360"
1 change: 1 addition & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
110 changes: 103 additions & 7 deletions apps/api/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ import { AuthService } from './auth.service'
import {
ConfirmationCodeDto,
ForgotPasswordResetRequestDto,
Recovery2FADto,
ResetPasswordRequestDto,
SignInWithEmailCredentialsDto,
SignUpWithEmailCredentialsDto
SignUpWithEmailCredentialsDto,
TurnOff2FADto,
TurnOn2FADto
} from '@isomera/dtos'
import { JWTAuthGuard } from './guards/jwt-auth.guard'
import { LocalAuthGuard } from './guards/local-auth.guard'
import { SessionAuthGuard } from './guards/session-auth.guard'
import { TokenInterceptor } from './interceptors/token.interceptor'
Expand All @@ -31,9 +33,12 @@ import {
PasswordResetRequestInterface,
Pure,
RefreshTokenResponseInterface,
StatusType
StatusType,
TurnOff2FAResponseInterface
} from '@isomera/interfaces'
import { JwtRefreshTokenGuard } from './guards/jwt-refresh-token'
import { Jwt2faAuthGuard } from './guards/jwt-2fa-auth.guard'
import { JWTAuthGuard } from './guards/jwt-auth.guard'

@Controller('auth')
export class AuthController {
Expand Down Expand Up @@ -75,7 +80,7 @@ export class AuthController {
}

@Get('/me')
@UseGuards(SessionAuthGuard, JWTAuthGuard)
@UseGuards(SessionAuthGuard, Jwt2faAuthGuard)
me(@AuthUser() user: Pure<SignInWithEmailCredentialsDto>): UserEntity {
return user as UserEntity
}
Expand All @@ -102,9 +107,15 @@ export class AuthController {
@Post('/refresh')
@HttpCode(HttpStatus.OK)
async refreshToken(
@AuthUser() user: Pure<UserEntity>
@AuthUser() user: Pure<UserEntity> & { isTwoFactorAuthenticated: boolean }
): Promise<RefreshTokenResponseInterface> {
const { refresh_token, access_token } = this.authService.signToken(user)
const payload = {
email: user.email,
isTwoFactorAuthenticationEnabled: !!user.isTwoFAEnabled,
isTwoFactorAuthenticated: !!user.isTwoFactorAuthenticated
}

const { refresh_token, access_token } = this.authService.signToken(payload)

await this.authService.storeRefreshToken(user, refresh_token)
return {
Expand All @@ -115,7 +126,7 @@ export class AuthController {
}

@Post('/logout')
@UseGuards(SessionAuthGuard, JWTAuthGuard)
@UseGuards(SessionAuthGuard, Jwt2faAuthGuard)
@HttpCode(HttpStatus.OK)
async logout(
@AuthUser() user: Pure<UserEntity>
Expand All @@ -125,4 +136,89 @@ export class AuthController {
status: StatusType.OK
}
}

@Post('2fa/generate')
@UseGuards(SessionAuthGuard, Jwt2faAuthGuard)
@HttpCode(HttpStatus.OK)
async register2FA(@AuthUser() user: Pure<UserEntity>) {
const { otpAuthUrl } =
await this.authService.generateTwoFactorAuthenticationSecret(user)

return {
status: StatusType.OK,
image: await this.authService.generateQrCodeDataURL(otpAuthUrl)
}
}

@Post('2fa/request-recovery')
@HttpCode(HttpStatus.OK)
async requestRecovery2FA(@Body() { code }: Pure<Recovery2FADto>) {
await this.authService.requestRecovery2FA(code)
return {
status: StatusType.OK
}
}

@Post('2fa/confirm-recovery')
@HttpCode(HttpStatus.OK)
async confirmRecovery2FACode(
@Body() { code, email }: Pure<ConfirmationCodeDto>
) {
await this.authService.confirmRecovery2FACode({ code, email })
return {
status: StatusType.OK
}
}

@Post('2fa/turn-on')
@UseGuards(SessionAuthGuard, Jwt2faAuthGuard)
@HttpCode(HttpStatus.OK)
async turnOnTwoFactorAuthentication(
@AuthUser() user: Pure<UserEntity>,
@Body() { code }: Pure<TurnOn2FADto>
) {
await this.authService.turnOn2FA(user, code)
const data = await this.authService.loginWith2fa(user, code)
delete data.password

const { access_token, refresh_token } = data

return {
status: StatusType.OK,
secret: user.twoFASecret,
access_token,
refresh_token
}
}

@Post('2fa/authenticate')
@HttpCode(200)
@UseGuards(SessionAuthGuard, JWTAuthGuard)
async authenticate(
@AuthUser() user: Pure<UserEntity>,
@Body() { code }: Pure<TurnOn2FADto>
) {
const data = await this.authService.loginWith2fa(user, code)
delete data.password

return data
}

@Post('2fa/turn-off')
@UseGuards(SessionAuthGuard, Jwt2faAuthGuard)
@HttpCode(HttpStatus.OK)
async turnOffTwoFactorAuthentication(
@AuthUser() user: Pure<UserEntity>,
@Body() { code }: Pure<TurnOff2FADto>
): Promise<TurnOff2FAResponseInterface> {
await this.authService.turnOff2FA(user, code)
const { access_token, refresh_token } =
await this.authService.generateTokenFromUser(user)

return {
status: StatusType.OK,
access_token,
refresh_token
}
}
}
4 changes: 3 additions & 1 deletion apps/api/src/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { MailerModule } from '../mailer/mailer.module'
import { ConfirmCodeModule } from '../user/confirm-code.module'
import { OrganizationModule } from '../organization/organization.module'
import { JwtRefreshTokenStrategy } from './strategies/jwt-refresh-token.strategy'
import { Jwt2faStrategy } from './strategies/jwt-2fa.strategy'

@Module({
imports: [
Expand All @@ -37,7 +38,8 @@ import { JwtRefreshTokenStrategy } from './strategies/jwt-refresh-token.strategy
LocalStrategy,
JwtStrategy,
SessionSerializer,
JwtRefreshTokenStrategy
JwtRefreshTokenStrategy,
Jwt2faStrategy
]
})
export class AuthModule {}
Loading

1 comment on commit 2aa7119

@vercel
Copy link

@vercel vercel bot commented on 2aa7119 Feb 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

isomera-platform – ./

isomera-platform-git-main-cortip.vercel.app
isomera-platform-cortip.vercel.app
isomera.vercel.app
app.isomera.org

Please sign in to comment.