Skip to content

Commit

Permalink
Merge pull request #5 from ribeirogab/feature/magic-link-login
Browse files Browse the repository at this point in the history
Feature/magic link login
  • Loading branch information
ribeirogab authored Sep 21, 2024
2 parents 12f8716 + 045d5dc commit 8b76bd6
Show file tree
Hide file tree
Showing 42 changed files with 334 additions and 493 deletions.
8 changes: 6 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# Application
# Environment
NODE_ENV=development
STAGE=dev
JWT_SECRET=secret

# Application
JWT_SECRET=auth-jwt-secret
JWT_SECRET_VERIFICATION_TOKEN=verification-token-jwt-secret

# AWS
AWS_REGION=us-east-1
AWS_DYNAMO_TABLE_NAME=dev-authentication

35 changes: 0 additions & 35 deletions src/adapters/hash/crypto.provider.ts

This file was deleted.

38 changes: 0 additions & 38 deletions src/adapters/hash/index.ts

This file was deleted.

1 change: 0 additions & 1 deletion src/adapters/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * from './unique-id';
export * from './logger';
export * from './email';
export * from './hash';
5 changes: 5 additions & 0 deletions src/configs/env.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ export class EnvConfig {
public readonly PORT = get('PORT').default(8080).asPortNumber();
public readonly CORS_ORIGIN = get('CORS_ORIGIN').default('*').asString();
public readonly JWT_SECRET = get('JWT_SECRET').required().asString();
public readonly JWT_SECRET_VERIFICATION_TOKEN = get(
'JWT_SECRET_VERIFICATION_TOKEN',
)
.required()
.asString();

public readonly EMAIL_PROVIDER = get('EMAIL_PROVIDER')
.default(EmailProviderEnum.Resend)
Expand Down
30 changes: 25 additions & 5 deletions src/configs/jwt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { sign, verify } from 'jsonwebtoken';
import { inject, injectable } from 'tsyringe';

import type { EnvConfig } from './env.config';
import type { LoggerAdapter } from '@/interfaces';

type SignInDto = {
/**
Expand All @@ -10,20 +11,39 @@ type SignInDto = {
*/
expiresIn?: string | number;
subject?: string;
secret?: string;
};

@injectable()
export class JwtConfig {
constructor(@inject('EnvConfig') private readonly envConfig: EnvConfig) {}
constructor(
@inject('LoggerAdapter') private readonly logger: LoggerAdapter,

public sign({ expiresIn, subject }: SignInDto) {
return sign({}, this.envConfig.JWT_SECRET, {
@inject('EnvConfig') private readonly envConfig: EnvConfig,
) {
this.logger = this.logger.setPrefix(this.logger, JwtConfig.name);
}

public sign({ expiresIn, subject, secret }: SignInDto) {
return sign({}, secret || this.envConfig.JWT_SECRET, {
expiresIn,
subject,
});
}

public verify<T>(token: string) {
return verify(token, this.envConfig.JWT_SECRET) as T;
public verify<T>({
token,
secret,
}: {
token: string;
secret?: string;
}): T | null {
try {
return verify(token, secret || this.envConfig.JWT_SECRET) as T;
} catch (error) {
this.logger.error('Error while verifying token', error);

return null;
}
}
}
5 changes: 4 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ export enum HttpMethodEnum {
}

export enum AppErrorCodeEnum {
VerificationCodeNotFound = 'verification_code_not_found',
VerificationCodeInvalidOrExpired = 'verification_code_invalid_or_expired',
RegisterTokenNotFound = 'register_token_not_found',
EmailAlreadyInUse = 'email_already_in_use',
ValidationError = 'validation_error',
InvalidLogin = 'invalid_login',
Unknown = 'unknown',
}

export enum NodeEnvEnum {
Expand Down
15 changes: 5 additions & 10 deletions src/container.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { container } from 'tsyringe';

import { EmailAdapter, HashAdapter, LoggerAdapter, UniqueIdAdapter } from './adapters';
import { EmailAdapter, LoggerAdapter, UniqueIdAdapter } from './adapters';
import { DynamoConfig, EnvConfig, JwtConfig, RateLimit } from './configs';
import { AuthController, PasswordController, RegistrationController } from './controllers';
import { AuthController, RegistrationController } from './controllers';
import { AuthHelper } from './helpers';
import { EnsureAuthenticatedMiddleware, ErrorHandlingMiddleware, RequestAuditMiddleware } from './middlewares';
import { EmailTemplateRepository, SessionRepository, UserRepository, VerificationCodeRepository } from './repositories';
import { AppRouter, AuthRouter, PasswordRouter, RegistrationRouter } from './routers';
import { AppRouter, AuthRouter, RegistrationRouter } from './routers';
import {
LoginConfirmService,
LoginService,
LogoutService,
RecoveryPasswordService,
RecoveryPasswordVerifyService,
RefreshLoginService,
RegistrationConfirmService,
RegistrationService,
Expand All @@ -22,7 +21,6 @@ import {
container.registerSingleton<UniqueIdAdapter>('UniqueIdAdapter', UniqueIdAdapter);
container.registerSingleton<LoggerAdapter>('LoggerAdapter', LoggerAdapter);
container.registerSingleton<EmailAdapter>('EmailAdapter', EmailAdapter);
container.registerSingleton<HashAdapter>('HashAdapter', HashAdapter);

// Configs
container.registerSingleton<DynamoConfig>('DynamoConfig', DynamoConfig);
Expand All @@ -45,22 +43,19 @@ container.registerSingleton<SessionRepository>('SessionRepository', SessionRepos
container.registerSingleton<UserRepository>('UserRepository', UserRepository);

// Services
container.registerSingleton<RecoveryPasswordVerifyService>('RecoveryPasswordVerifyService', RecoveryPasswordVerifyService);
container.registerSingleton<RegistrationConfirmService>('RegistrationConfirmService', RegistrationConfirmService);
container.registerSingleton<RecoveryPasswordService>('RecoveryPasswordService', RecoveryPasswordService);
container.registerSingleton<ResetPasswordService>('ResetPasswordService', ResetPasswordService);
container.registerSingleton<RegistrationService>('RegistrationService', RegistrationService);
container.registerSingleton<RefreshLoginService>('RefreshLoginService', RefreshLoginService);
container.registerSingleton<LoginConfirmService>('LoginConfirmService', LoginConfirmService);
container.registerSingleton<LogoutService>('LogoutService', LogoutService);
container.registerSingleton<LoginService>('LoginService', LoginService);

// Controllers
container.registerSingleton<RegistrationController>('RegistrationController', RegistrationController);
container.registerSingleton<PasswordController>('PasswordController', PasswordController);
container.registerSingleton<AuthController>('AuthController', AuthController);

// Routers
container.registerSingleton<RegistrationRouter>('RegistrationRouter', RegistrationRouter);
container.registerSingleton<PasswordRouter>('PasswordRouter', PasswordRouter);
container.registerSingleton<AuthRouter>('AuthRouter', AuthRouter);
container.registerSingleton<AppRouter>('AppRouter', AppRouter);
25 changes: 19 additions & 6 deletions src/controllers/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { inject, injectable } from 'tsyringe';

import { HttpStatusCodesEnum } from '@/constants';
import type {
LoginConfirmService,
LoginConfirmServiceDto,
LoginService,
LoginServiceDto,
LogoutService,
Expand All @@ -12,20 +14,31 @@ import type {
@injectable()
export class AuthController {
constructor(
@inject('LoginService')
private readonly loginService: LoginService,
@inject('RefreshLoginService')
private readonly refreshLoginService: RefreshLoginService,

@inject('LoginConfirmService')
private readonly loginConfirmService: LoginConfirmService,

@inject('LogoutService')
private readonly logoutService: LogoutService,

@inject('RefreshLoginService')
private readonly refreshLoginService: RefreshLoginService,
@inject('LoginService')
private readonly loginService: LoginService,
) {}

public async login(request: FastifyRequest, reply: FastifyReply) {
const { email, password } = request.body as LoginServiceDto;
const { email } = request.body as LoginServiceDto;

const response = await this.loginService.execute({ email });

return reply.code(HttpStatusCodesEnum.OK).send(response);
}

public async loginConfirm(request: FastifyRequest, reply: FastifyReply) {
const { code, token } = request.body as LoginConfirmServiceDto;

const response = await this.loginService.execute({ email, password });
const response = await this.loginConfirmService.execute({ code, token });

return reply.code(HttpStatusCodesEnum.OK).send(response);
}
Expand Down
1 change: 0 additions & 1 deletion src/controllers/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export * from './registration.controller';
export * from './password.controller';
export * from './auth.controller';
48 changes: 0 additions & 48 deletions src/controllers/password.controller.ts

This file was deleted.

13 changes: 5 additions & 8 deletions src/controllers/registration.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,20 @@ export class RegistrationController {
) {}

public async registration(request: FastifyRequest, reply: FastifyReply) {
const { email, name, password } = request.body as Omit<
User,
'id' | 'password_salt'
>;
const { email, name } = request.body as Omit<User, 'id'>;

await this.registrationService.execute({ email, name, password });
const response = await this.registrationService.execute({ email, name });

return reply.code(HttpStatusCodesEnum.NO_CONTENT).send();
return reply.code(HttpStatusCodesEnum.OK).send(response);
}

public async registrationConfirm(
request: FastifyRequest,
reply: FastifyReply,
) {
const { code } = request.body as RegistrationConfirmServiceDto;
const { code, token } = request.body as RegistrationConfirmServiceDto;

await this.registrationConfirmService.execute({ code });
await this.registrationConfirmService.execute({ code, token });

return reply.code(HttpStatusCodesEnum.NO_CONTENT).send();
}
Expand Down
12 changes: 10 additions & 2 deletions src/errors/app.error.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
import type { HttpStatusCodesEnum } from '@/constants';
import { AppErrorCodeEnum, type HttpStatusCodesEnum } from '@/constants';

export type AppErrorConstructor = {
status_code: HttpStatusCodesEnum;
error_code?: AppErrorCodeEnum;
details?: unknown;
message: string;
};

export class AppError extends Error {
public readonly status_code: HttpStatusCodesEnum;
public readonly error_code?: AppErrorCodeEnum;
public readonly details?: unknown;

constructor({ message, status_code, details }: AppErrorConstructor) {
constructor({
error_code = AppErrorCodeEnum.Unknown,
status_code,
message,
details,
}: AppErrorConstructor) {
super(message);
this.status_code = status_code;
this.error_code = error_code;
this.details = details;
}
}
Loading

0 comments on commit 8b76bd6

Please sign in to comment.