Skip to content

Commit

Permalink
refactor(auth): adjust api api/v1/auth/login/credential to work wit…
Browse files Browse the repository at this point in the history
…h two factor
  • Loading branch information
LivioAlvarenga committed Sep 10, 2024
1 parent 3e65f48 commit 9040e16
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 47 deletions.
18 changes: 17 additions & 1 deletion src/components/LoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export function LoginForm({

const responseBody = await response.json()

if (response.ok) {
if (response.status === 201) {
showToast({
message: 'Usuário logado com sucesso!',
duration: 3000,
Expand All @@ -123,6 +123,22 @@ export function LoginForm({
return
}

if (response.status === 200) {
showToast({
message: 'Autenticação de dois fatores necessária.',
duration: 3000,
variant: 'info',
closeButton: false,
redirect: {
path: `${webserver.host}/verify-two-factor-opt?token=${responseBody.userId}`,
countdownSeconds: 2,
},
})

form.reset()
return
}

if (response.status === 404 || response.status === 401) {
showToast({
message: 'Usuário ou Senha incorretos.',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe('POST /api/v1/public/auth/login/credential', () => {

test('should return 403 if email not verified', async () => {
const user = await utilsTest.createDefaultUser() // this user is not email verified
const password = 'Password23@#!'
const password = 'Password123$%$'
const device = 'device-id'

const response = await fetch(
Expand Down Expand Up @@ -228,6 +228,35 @@ describe('POST /api/v1/public/auth/login/credential', () => {
expect(diffDays).toBe(30)
})

test('should return 200 and send two-factor token when user has two_factor_enabled true', async () => {
const user = await utilsTest.createDefaultUserTwoFactor()

const password = 'Password123$%$'
const device = 'device-id'

const response = await fetch(
`${webserver.host}/api/v1/public/auth/login/credential`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: user.email,
password,
device,
}),
},
)

const responseBody = await response.json()

expect(response.status).toBe(200)
expect(responseBody.message).toBe(
'Token de autenticação de dois fatores enviado com sucesso!',
)
})

test('should delete all expired sessions', async () => {
// Create 10 sessions with expired tokens
for (let i = 0; i < 10; i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ describe('GET /api/v1/public/auth/verify-email-opt', () => {

expect(user.emailVerified).toBeDefined()
expect(user.email_verified_provider).toBe('credential')
expect(user.profile_completion_score).toBe(4)
expect(user.profile_completion_score).toBe(3)

const tokenResult = await database.query({
text: `
Expand Down
108 changes: 64 additions & 44 deletions src/use-cases/auth/login/credential/login-credential.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { comparePassword } from '@/lib/bcrypt'
import { CookieRepository } from '@/repositories/cookie-repository'
import { SessionRepository } from '@/repositories/session-repository'
import { UserRepository } from '@/repositories/user-repository'
import { VerificationTokenRepository } from '@/repositories/verification-token-repository'
import { v4 } from 'uuid'
import { generateAndSendTwoFactorToken } from './two-factor/send-token/two-factor-send-token'

interface LoginCredentialUseCaseRequest {
email: string
Expand All @@ -25,6 +27,7 @@ export class LoginCredentialUseCase {
constructor(
private userRepository: UserRepository,
private sessionRepository: SessionRepository,
private verificationTokenRepository: VerificationTokenRepository,
private cookieRepository: CookieRepository,
) {}

Expand All @@ -46,16 +49,7 @@ export class LoginCredentialUseCase {
}
}

// 3. useCase - check if email was verified
if (!user.emailVerified) {
return {
status: 403,
message: 'Email não verificado.',
userId: user.id,
}
}

// 4. useCase - check if password is correct
// 3. useCase - check if password is correct
const isPasswordCorrect = await comparePassword(
password,
user.password_hash as string,
Expand All @@ -68,41 +62,67 @@ export class LoginCredentialUseCase {
}
}

// 5. useCase - create session token
const sessionToken = v4()
const sessionExpiry = new Date(Date.now() + DAYS_30_IN_MILLISECONDS)

// 6. useCase - check if exist a session with userId and device, if exist delete the session and create new other, if not create a new session
const sessionExists =
await this.sessionRepository.getSessionByUserIdAndDevice(
user.id,
device || '',
)

if (sessionExists) {
await this.sessionRepository.deleteSessionByToken(
sessionExists.sessionToken,
)
}
// 4. useCase - check if user is two factor enabled
if (user.two_factor_enabled) {
// 5. useCase - Generate and send two-factor token
await generateAndSendTwoFactorToken({
userId: user.id,
userEmail: user.email,
verificationTokenRepository: this.verificationTokenRepository,
tokenType: 'TWO_FACTOR_VERIFICATION',
})

return {
status: 200,
message: 'Token de autenticação de dois fatores enviado com sucesso!',
userId: user.id,
}
} else {
// 5. useCase - check if email was verified
if (!user.emailVerified) {
return {
status: 403,
message: 'Email não verificado.',
userId: user.id,
}
}

// 6. useCase - create session token
const sessionToken = v4()
const sessionExpiry = new Date(Date.now() + DAYS_30_IN_MILLISECONDS)

// 7. useCase - check if exist a session with userId and device, if exist delete the session and create new other, if not create a new session
const sessionExists =
await this.sessionRepository.getSessionByUserIdAndDevice(
user.id,
device || '',
)

if (sessionExists) {
await this.sessionRepository.deleteSessionByToken(
sessionExists.sessionToken,
)
}

await this.sessionRepository.createSession({
sessionToken,
userId: user.id,
expires: sessionExpiry,
device_identifier: device,
})

// 8. useCase - set session token cookie with the appropriate name and security settings
this.cookieRepository.setCookie({
name: 'authjs.session-token',
value: sessionToken,
expires: sessionExpiry,
})

await this.sessionRepository.createSession({
sessionToken,
userId: user.id,
expires: sessionExpiry,
device_identifier: device,
})

// 7. useCase - set session token cookie with the appropriate name and security settings
this.cookieRepository.setCookie({
name: 'authjs.session-token',
value: sessionToken,
expires: sessionExpiry,
})

return {
status: 201,
message: 'Usuário logado com sucesso!',
userId: user.id,
return {
status: 201,
message: 'Usuário logado com sucesso!',
userId: user.id,
}
}
}
}
3 changes: 3 additions & 0 deletions src/use-cases/auth/login/credential/make-login-credential.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { NextCookieRepository } from '@/repositories/nextjs/next-cookie-repository'
import { PgSessionRepository } from '@/repositories/pg/pg-session-repository'
import { PgUserRepository } from '@/repositories/pg/pg-user-repository'
import { PgVerificationTokenRepository } from '@/repositories/pg/pg-verification-token-repository'
import { LoginCredentialUseCase } from './login-credential'

export function makeLoginCredentialUseCase() {
const userRepository = new PgUserRepository()
const sessionRepository = new PgSessionRepository()
const verificationTokenRepository = new PgVerificationTokenRepository()
const cookieRepository = new NextCookieRepository()

const useCase = new LoginCredentialUseCase(
userRepository,
sessionRepository,
verificationTokenRepository,
cookieRepository,
)

Expand Down
2 changes: 2 additions & 0 deletions src/use-cases/auth/register/get-register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface GetRegisterUseCaseResponse {
email: string
name?: string
emailVerified?: string
two_factor_enabled?: boolean
}
}

Expand Down Expand Up @@ -39,6 +40,7 @@ export class GetRegisterUseCase {
email: userAlreadyExists.email,
name: userAlreadyExists.nick_name || userAlreadyExists.name,
emailVerified: userAlreadyExists.emailVerified,
two_factor_enabled: userAlreadyExists.two_factor_enabled,
},
}
}
Expand Down

0 comments on commit 9040e16

Please sign in to comment.