Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature 2fa #213

Merged
merged 15 commits into from
Feb 23, 2024
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 }
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this prop already be in UserEntity?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This field doesn't exist in the entity because it's not being stored in the db

): 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
Loading