This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

feat: implement user subdomain application layer with OAuth and token management use cases

+364
+4
src/modules/user/application/dtos/OAuthCallbackDTO.ts
··· 1 + export interface OAuthCallbackDTO { 2 + code: string; 3 + state: string; 4 + }
+11
src/modules/user/application/dtos/TokenDTO.ts
··· 1 + export interface TokenPair { 2 + accessToken: string; 3 + refreshToken: string; 4 + expiresIn: number; 5 + } 6 + 7 + export interface TokenPayload { 8 + did: string; 9 + iat: number; 10 + exp: number; 11 + }
+6
src/modules/user/application/dtos/UserDTO.ts
··· 1 + export interface UserDTO { 2 + did: string; 3 + handle?: string; 4 + linkedAt: Date; 5 + lastLoginAt: Date; 6 + }
+45
src/modules/user/application/mappers/UserMap.ts
··· 1 + import { User } from "../../domain/User"; 2 + import { UserDTO } from "../dtos/UserDTO"; 3 + import { DID } from "../../domain/value-objects/DID"; 4 + import { Handle } from "../../domain/value-objects/Handle"; 5 + import { UniqueEntityID } from "src/shared/domain/UniqueEntityID"; 6 + 7 + export class UserMap { 8 + public static toDTO(user: User): UserDTO { 9 + return { 10 + did: user.did.value, 11 + handle: user.handle?.value, 12 + linkedAt: user.linkedAt, 13 + lastLoginAt: user.lastLoginAt 14 + }; 15 + } 16 + 17 + public static toDomain(dto: UserDTO): User { 18 + const didOrError = DID.create(dto.did); 19 + 20 + if (didOrError.isErr()) { 21 + throw new Error(`Could not create DID: ${didOrError.error.message}`); 22 + } 23 + 24 + let handle: Handle | undefined; 25 + if (dto.handle) { 26 + const handleOrError = Handle.create(dto.handle); 27 + if (handleOrError.isOk()) { 28 + handle = handleOrError.value; 29 + } 30 + } 31 + 32 + const userOrError = User.create({ 33 + did: didOrError.value, 34 + handle, 35 + linkedAt: dto.linkedAt, 36 + lastLoginAt: dto.lastLoginAt 37 + }, new UniqueEntityID(dto.did)); 38 + 39 + if (userOrError.isErr()) { 40 + throw new Error(`Could not create User: ${userOrError.error.message}`); 41 + } 42 + 43 + return userOrError.value; 44 + } 45 + }
+12
src/modules/user/application/services/IOAuthProcessor.ts
··· 1 + import { Result } from "src/shared/core/Result"; 2 + import { OAuthCallbackDTO } from "../dtos/OAuthCallbackDTO"; 3 + 4 + export interface AuthResult { 5 + did: string; 6 + handle?: string; 7 + } 8 + 9 + export interface IOAuthProcessor { 10 + generateAuthUrl(handle?: string): Promise<Result<string>>; 11 + processCallback(params: OAuthCallbackDTO): Promise<Result<AuthResult>>; 12 + }
+9
src/modules/user/application/services/ITokenService.ts
··· 1 + import { Result } from "src/shared/core/Result"; 2 + import { TokenPair } from "../dtos/TokenDTO"; 3 + 4 + export interface ITokenService { 5 + generateToken(did: string): Promise<Result<TokenPair>>; 6 + validateToken(token: string): Promise<Result<string | null>>; // Returns DID if valid 7 + refreshToken(refreshToken: string): Promise<Result<TokenPair | null>>; 8 + revokeToken(refreshToken: string): Promise<Result<void>>; 9 + }
+90
src/modules/user/application/use-cases/CompleteOAuthSignInUseCase.ts
··· 1 + import { UseCase } from "src/shared/core/UseCase"; 2 + import { Result, err, ok } from "src/shared/core/Result"; 3 + import { AppError } from "src/shared/core/AppError"; 4 + import { IOAuthProcessor } from "../services/IOAuthProcessor"; 5 + import { ITokenService } from "../services/ITokenService"; 6 + import { IUserRepository } from "../../domain/repositories/IUserRepository"; 7 + import { OAuthCallbackDTO } from "../dtos/OAuthCallbackDTO"; 8 + import { TokenPair } from "../dtos/TokenDTO"; 9 + import { DID } from "../../domain/value-objects/DID"; 10 + import { Handle } from "../../domain/value-objects/Handle"; 11 + import { User } from "../../domain/User"; 12 + import { CompleteOAuthSignInErrors } from "./errors/CompleteOAuthSignInErrors"; 13 + import { IUserAuthenticationService } from "../../domain/services/IUserAuthenticationService"; 14 + 15 + export type CompleteOAuthSignInResponse = Result< 16 + TokenPair, 17 + | CompleteOAuthSignInErrors.InvalidCallbackParamsError 18 + | CompleteOAuthSignInErrors.AuthenticationFailedError 19 + | CompleteOAuthSignInErrors.TokenGenerationError 20 + | AppError.UnexpectedError 21 + >; 22 + 23 + export class CompleteOAuthSignInUseCase 24 + implements UseCase<OAuthCallbackDTO, Promise<CompleteOAuthSignInResponse>> 25 + { 26 + constructor( 27 + private oauthProcessor: IOAuthProcessor, 28 + private tokenService: ITokenService, 29 + private userRepository: IUserRepository, 30 + private userAuthService: IUserAuthenticationService 31 + ) {} 32 + 33 + async execute(request: OAuthCallbackDTO): Promise<CompleteOAuthSignInResponse> { 34 + try { 35 + // Validate callback parameters 36 + if (!request.code || !request.state) { 37 + return err(new CompleteOAuthSignInErrors.InvalidCallbackParamsError()); 38 + } 39 + 40 + // Process OAuth callback 41 + const authResult = await this.oauthProcessor.processCallback(request); 42 + 43 + if (authResult.isErr()) { 44 + return err(new CompleteOAuthSignInErrors.AuthenticationFailedError(authResult.error.message)); 45 + } 46 + 47 + // Create DID value object 48 + const didOrError = DID.create(authResult.value.did); 49 + if (didOrError.isErr()) { 50 + return err(new CompleteOAuthSignInErrors.AuthenticationFailedError(didOrError.error.message)); 51 + } 52 + const did = didOrError.value; 53 + 54 + // Create Handle value object if available 55 + let handle: Handle | undefined; 56 + if (authResult.value.handle) { 57 + const handleOrError = Handle.create(authResult.value.handle); 58 + if (handleOrError.isOk()) { 59 + handle = handleOrError.value; 60 + } 61 + } 62 + 63 + // Validate user credentials through domain service 64 + const authenticationResult = await this.userAuthService.validateUserCredentials(did, handle); 65 + 66 + if (authenticationResult.isErr()) { 67 + return err(new CompleteOAuthSignInErrors.AuthenticationFailedError(authenticationResult.error.message)); 68 + } 69 + 70 + const user = authenticationResult.value.user; 71 + 72 + // Record login 73 + user.recordLogin(); 74 + 75 + // Save updated user 76 + await this.userRepository.save(user); 77 + 78 + // Generate tokens 79 + const tokenResult = await this.tokenService.generateToken(did.value); 80 + 81 + if (tokenResult.isErr()) { 82 + return err(new CompleteOAuthSignInErrors.TokenGenerationError(tokenResult.error.message)); 83 + } 84 + 85 + return ok(tokenResult.value); 86 + } catch (error: any) { 87 + return err(new AppError.UnexpectedError(error)); 88 + } 89 + } 90 + }
+52
src/modules/user/application/use-cases/GetCurrentUserUseCase.ts
··· 1 + import { UseCase } from "src/shared/core/UseCase"; 2 + import { Result, err, ok } from "src/shared/core/Result"; 3 + import { AppError } from "src/shared/core/AppError"; 4 + import { IUserRepository } from "../../domain/repositories/IUserRepository"; 5 + import { UserDTO } from "../dtos/UserDTO"; 6 + import { DID } from "../../domain/value-objects/DID"; 7 + import { GetCurrentUserErrors } from "./errors/GetCurrentUserErrors"; 8 + import { UserMap } from "../mappers/UserMap"; 9 + 10 + export interface GetCurrentUserDTO { 11 + did: string; 12 + } 13 + 14 + export type GetCurrentUserResponse = Result< 15 + UserDTO, 16 + GetCurrentUserErrors.UserNotFoundError | AppError.UnexpectedError 17 + >; 18 + 19 + export class GetCurrentUserUseCase 20 + implements UseCase<GetCurrentUserDTO, Promise<GetCurrentUserResponse>> 21 + { 22 + constructor(private userRepository: IUserRepository) {} 23 + 24 + async execute(request: GetCurrentUserDTO): Promise<GetCurrentUserResponse> { 25 + try { 26 + const didOrError = DID.create(request.did); 27 + 28 + if (didOrError.isErr()) { 29 + return err(new GetCurrentUserErrors.UserNotFoundError()); 30 + } 31 + 32 + const userResult = await this.userRepository.findByDID(didOrError.value); 33 + 34 + if (userResult.isErr()) { 35 + return err(new AppError.UnexpectedError(userResult.error)); 36 + } 37 + 38 + const user = userResult.value; 39 + 40 + if (!user) { 41 + return err(new GetCurrentUserErrors.UserNotFoundError()); 42 + } 43 + 44 + // Map domain entity to DTO 45 + const userDTO = UserMap.toDTO(user); 46 + 47 + return ok(userDTO); 48 + } catch (error: any) { 49 + return err(new AppError.UnexpectedError(error)); 50 + } 51 + } 52 + }
+44
src/modules/user/application/use-cases/InitiateOAuthSignInUseCase.ts
··· 1 + import { UseCase } from "src/shared/core/UseCase"; 2 + import { Result, err, ok } from "src/shared/core/Result"; 3 + import { AppError } from "src/shared/core/AppError"; 4 + import { IOAuthProcessor } from "../services/IOAuthProcessor"; 5 + import { Handle } from "../../domain/value-objects/Handle"; 6 + import { InitiateOAuthSignInErrors } from "./errors/InitiateOAuthSignInErrors"; 7 + 8 + export interface InitiateOAuthSignInDTO { 9 + handle?: string; 10 + } 11 + 12 + export type InitiateOAuthSignInResponse = Result< 13 + { authUrl: string }, 14 + InitiateOAuthSignInErrors.InvalidHandleError | AppError.UnexpectedError 15 + >; 16 + 17 + export class InitiateOAuthSignInUseCase 18 + implements UseCase<InitiateOAuthSignInDTO, Promise<InitiateOAuthSignInResponse>> 19 + { 20 + constructor(private oauthProcessor: IOAuthProcessor) {} 21 + 22 + async execute(request: InitiateOAuthSignInDTO): Promise<InitiateOAuthSignInResponse> { 23 + try { 24 + // Validate handle if provided 25 + if (request.handle) { 26 + const handleOrError = Handle.create(request.handle); 27 + if (handleOrError.isErr()) { 28 + return err(new InitiateOAuthSignInErrors.InvalidHandleError(handleOrError.error.message)); 29 + } 30 + } 31 + 32 + // Generate auth URL 33 + const authUrlResult = await this.oauthProcessor.generateAuthUrl(request.handle); 34 + 35 + if (authUrlResult.isErr()) { 36 + return err(new AppError.UnexpectedError(authUrlResult.error)); 37 + } 38 + 39 + return ok({ authUrl: authUrlResult.value }); 40 + } catch (error: any) { 41 + return err(new AppError.UnexpectedError(error)); 42 + } 43 + } 44 + }
+39
src/modules/user/application/use-cases/RefreshAccessTokenUseCase.ts
··· 1 + import { UseCase } from "src/shared/core/UseCase"; 2 + import { Result, err, ok } from "src/shared/core/Result"; 3 + import { AppError } from "src/shared/core/AppError"; 4 + import { ITokenService } from "../services/ITokenService"; 5 + import { TokenPair } from "../dtos/TokenDTO"; 6 + import { RefreshAccessTokenErrors } from "./errors/RefreshAccessTokenErrors"; 7 + 8 + export interface RefreshAccessTokenDTO { 9 + refreshToken: string; 10 + } 11 + 12 + export type RefreshAccessTokenResponse = Result< 13 + TokenPair, 14 + RefreshAccessTokenErrors.InvalidRefreshTokenError | AppError.UnexpectedError 15 + >; 16 + 17 + export class RefreshAccessTokenUseCase 18 + implements UseCase<RefreshAccessTokenDTO, Promise<RefreshAccessTokenResponse>> 19 + { 20 + constructor(private tokenService: ITokenService) {} 21 + 22 + async execute(request: RefreshAccessTokenDTO): Promise<RefreshAccessTokenResponse> { 23 + try { 24 + const tokenResult = await this.tokenService.refreshToken(request.refreshToken); 25 + 26 + if (tokenResult.isErr()) { 27 + return err(new AppError.UnexpectedError(tokenResult.error)); 28 + } 29 + 30 + if (!tokenResult.value) { 31 + return err(new RefreshAccessTokenErrors.InvalidRefreshTokenError()); 32 + } 33 + 34 + return ok(tokenResult.value); 35 + } catch (error: any) { 36 + return err(new AppError.UnexpectedError(error)); 37 + } 38 + } 39 + }
+21
src/modules/user/application/use-cases/errors/CompleteOAuthSignInErrors.ts
··· 1 + import { UseCaseError } from "src/shared/core/UseCaseError"; 2 + 3 + export namespace CompleteOAuthSignInErrors { 4 + export class InvalidCallbackParamsError extends UseCaseError { 5 + constructor() { 6 + super("The OAuth callback parameters are invalid or missing"); 7 + } 8 + } 9 + 10 + export class AuthenticationFailedError extends UseCaseError { 11 + constructor(message: string = "Authentication failed") { 12 + super(message); 13 + } 14 + } 15 + 16 + export class TokenGenerationError extends UseCaseError { 17 + constructor(message: string = "Failed to generate authentication tokens") { 18 + super(message); 19 + } 20 + } 21 + }
+9
src/modules/user/application/use-cases/errors/GetCurrentUserErrors.ts
··· 1 + import { UseCaseError } from "src/shared/core/UseCaseError"; 2 + 3 + export namespace GetCurrentUserErrors { 4 + export class UserNotFoundError extends UseCaseError { 5 + constructor() { 6 + super("User not found"); 7 + } 8 + } 9 + }
+9
src/modules/user/application/use-cases/errors/InitiateOAuthSignInErrors.ts
··· 1 + import { UseCaseError } from "src/shared/core/UseCaseError"; 2 + 3 + export namespace InitiateOAuthSignInErrors { 4 + export class InvalidHandleError extends UseCaseError { 5 + constructor(message: string) { 6 + super(`Invalid handle format: ${message}`); 7 + } 8 + } 9 + }
+9
src/modules/user/application/use-cases/errors/RefreshAccessTokenErrors.ts
··· 1 + import { UseCaseError } from "src/shared/core/UseCaseError"; 2 + 3 + export namespace RefreshAccessTokenErrors { 4 + export class InvalidRefreshTokenError extends UseCaseError { 5 + constructor() { 6 + super("Invalid or expired refresh token"); 7 + } 8 + } 9 + }
+4
src/modules/user/application/use-cases/index.ts
··· 1 + export * from './InitiateOAuthSignInUseCase'; 2 + export * from './CompleteOAuthSignInUseCase'; 3 + export * from './GetCurrentUserUseCase'; 4 + export * from './RefreshAccessTokenUseCase';