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 { IAppPasswordProcessor } from 'src/modules/atproto/application/IAppPasswordProcessor';
5import { ITokenService } from '../services/ITokenService';
6import { IUserRepository } from '../../domain/repositories/IUserRepository';
7import { LoginWithAppPasswordDTO } from '../dtos/LoginWithAppPasswordDTO';
8import { TokenPair } from '../dtos/TokenDTO';
9import { DID } from '../../domain/value-objects/DID';
10import { Handle } from '../../domain/value-objects/Handle';
11import { LoginWithAppPasswordErrors } from './errors/LoginWithAppPasswordErrors';
12import { IUserAuthenticationService } from '../../domain/services/IUserAuthenticationService';
13
14export type LoginWithAppPasswordResponse = Result<
15 TokenPair,
16 | LoginWithAppPasswordErrors.InvalidCredentialsError
17 | LoginWithAppPasswordErrors.AuthenticationFailedError
18 | LoginWithAppPasswordErrors.TokenGenerationError
19 | AppError.UnexpectedError
20>;
21
22export class LoginWithAppPasswordUseCase
23 implements
24 UseCase<LoginWithAppPasswordDTO, Promise<LoginWithAppPasswordResponse>>
25{
26 constructor(
27 private appPasswordProcessor: IAppPasswordProcessor,
28 private tokenService: ITokenService,
29 private userRepository: IUserRepository,
30 private userAuthService: IUserAuthenticationService,
31 ) {}
32
33 async execute(
34 request: LoginWithAppPasswordDTO,
35 ): Promise<LoginWithAppPasswordResponse> {
36 try {
37 // Validate input parameters
38 if (!request.identifier || !request.appPassword) {
39 return err(new LoginWithAppPasswordErrors.InvalidCredentialsError());
40 }
41
42 // Process app password authentication
43 const authResult = await this.appPasswordProcessor.processAppPassword(
44 request.identifier,
45 request.appPassword,
46 );
47
48 if (authResult.isErr()) {
49 return err(
50 new LoginWithAppPasswordErrors.AuthenticationFailedError(
51 authResult.error.message,
52 ),
53 );
54 }
55
56 // Create DID value object
57 const didOrError = DID.create(authResult.value.did);
58 if (didOrError.isErr()) {
59 return err(
60 new LoginWithAppPasswordErrors.AuthenticationFailedError(
61 didOrError.error.message,
62 ),
63 );
64 }
65 const did = didOrError.value;
66
67 // Create Handle value object if available
68 let handle: Handle | undefined;
69 if (authResult.value.handle) {
70 const handleOrError = Handle.create(authResult.value.handle);
71 if (handleOrError.isOk()) {
72 handle = handleOrError.value;
73 }
74 }
75
76 // Validate user credentials through domain service
77 const authenticationResult =
78 await this.userAuthService.validateUserCredentials(did, handle);
79
80 if (authenticationResult.isErr()) {
81 return err(
82 new LoginWithAppPasswordErrors.AuthenticationFailedError(
83 authenticationResult.error.message,
84 ),
85 );
86 }
87
88 const user = authenticationResult.value.user;
89
90 // Record login
91 user.recordLogin();
92
93 // Save updated user
94 await this.userRepository.save(user);
95
96 // Generate tokens
97 const tokenResult = await this.tokenService.generateToken(did.value);
98
99 if (tokenResult.isErr()) {
100 return err(
101 new LoginWithAppPasswordErrors.TokenGenerationError(
102 tokenResult.error.message,
103 ),
104 );
105 }
106
107 return ok(tokenResult.value);
108 } catch (error: any) {
109 return err(new AppError.UnexpectedError(error));
110 }
111 }
112}