A social knowledge tool for researchers built on ATProto
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}