A social knowledge tool for researchers built on ATProto
at development 3.5 kB view raw
1import { UseCase } from 'src/shared/core/UseCase'; 2import { Result, err, ok } from 'src/shared/core/Result'; 3import { AppError } from 'src/shared/core/AppError'; 4import { IOAuthProcessor } from '../services/IOAuthProcessor'; 5import { ITokenService } from '../services/ITokenService'; 6import { IUserRepository } from '../../domain/repositories/IUserRepository'; 7import { OAuthCallbackDTO } from '../dtos/OAuthCallbackDTO'; 8import { TokenPair } from '../dtos/TokenDTO'; 9import { DID } from '../../domain/value-objects/DID'; 10import { Handle } from '../../domain/value-objects/Handle'; 11import { CompleteOAuthSignInErrors } from './errors/CompleteOAuthSignInErrors'; 12import { IUserAuthenticationService } from '../../domain/services/IUserAuthenticationService'; 13 14export type CompleteOAuthSignInResponse = Result< 15 TokenPair, 16 | CompleteOAuthSignInErrors.InvalidCallbackParamsError 17 | CompleteOAuthSignInErrors.AuthenticationFailedError 18 | CompleteOAuthSignInErrors.TokenGenerationError 19 | AppError.UnexpectedError 20>; 21 22export class CompleteOAuthSignInUseCase 23 implements UseCase<OAuthCallbackDTO, Promise<CompleteOAuthSignInResponse>> 24{ 25 constructor( 26 private oauthProcessor: IOAuthProcessor, 27 private tokenService: ITokenService, 28 private userRepository: IUserRepository, 29 private userAuthService: IUserAuthenticationService, 30 ) {} 31 32 async execute( 33 request: OAuthCallbackDTO, 34 ): Promise<CompleteOAuthSignInResponse> { 35 try { 36 // Validate callback parameters 37 if (!request.code || !request.state || !request.iss) { 38 return err(new CompleteOAuthSignInErrors.InvalidCallbackParamsError()); 39 } 40 41 // Process OAuth callback 42 const authResult = await this.oauthProcessor.processCallback(request); 43 44 if (authResult.isErr()) { 45 return err( 46 new CompleteOAuthSignInErrors.AuthenticationFailedError( 47 authResult.error.message, 48 ), 49 ); 50 } 51 52 // Create DID value object 53 const didOrError = DID.create(authResult.value.did); 54 if (didOrError.isErr()) { 55 return err( 56 new CompleteOAuthSignInErrors.AuthenticationFailedError( 57 didOrError.error.message, 58 ), 59 ); 60 } 61 const did = didOrError.value; 62 63 // Create Handle value object if available 64 let handle: Handle | undefined; 65 if (authResult.value.handle) { 66 const handleOrError = Handle.create(authResult.value.handle); 67 if (handleOrError.isOk()) { 68 handle = handleOrError.value; 69 } 70 } 71 72 // Validate user credentials through domain service 73 const authenticationResult = 74 await this.userAuthService.validateUserCredentials(did, handle); 75 76 if (authenticationResult.isErr()) { 77 return err( 78 new CompleteOAuthSignInErrors.AuthenticationFailedError( 79 authenticationResult.error.message, 80 ), 81 ); 82 } 83 84 const user = authenticationResult.value.user; 85 86 // Record login 87 user.recordLogin(); 88 89 // Save updated user 90 await this.userRepository.save(user); 91 92 // Generate tokens 93 const tokenResult = await this.tokenService.generateToken(did.value); 94 95 if (tokenResult.isErr()) { 96 return err( 97 new CompleteOAuthSignInErrors.TokenGenerationError( 98 tokenResult.error.message, 99 ), 100 ); 101 } 102 103 return ok(tokenResult.value); 104 } catch (error: any) { 105 return err(new AppError.UnexpectedError(error)); 106 } 107 } 108}