auth.ts (5069B)
1 import { AuthRepository } from "#core/application/authn/auth_repository.ts"; 2 import { 3 EntityLocked, 4 EntityNotFound, 5 } from "#core/application/repository_error.ts"; 6 import { Auth } from "#core/domain/auth.ts"; 7 import { Email } from "#core/domain/email.ts"; 8 import { Token } from "#core/domain/token.ts"; 9 import { UUID } from "#core/domain/uuid.ts"; 10 import { 11 AuthDto, 12 mapFromAuth, 13 mapToAuth, 14 } from "#infrastructure/memory/mapper/auth.ts"; 15 import { mapError } from "#infrastructure/postgres/error.ts"; 16 import { Pool } from "$postgres"; 17 18 export class PostgresAuthRepositoryAdapter implements AuthRepository { 19 constructor(readonly pool: Pool) { 20 } 21 22 async find(uuid: UUID): Promise<Auth> { 23 try { 24 const connection = await this.pool.connect(); 25 try { 26 const result = await connection.queryObject< 27 AuthDto 28 >`select * from "auth" where "uuid" = ${uuid.toString()} limit 1;`; 29 if (result.rowCount !== 1) { 30 throw new EntityNotFound(uuid.toString()); 31 } 32 return mapToAuth(result.rows[0]); 33 } finally { 34 connection.release(); 35 } 36 } catch (error) { 37 throw mapError(error); 38 } 39 } 40 41 async findByEmail(email: Email): Promise<Auth> { 42 try { 43 const connection = await this.pool.connect(); 44 try { 45 const result = await connection.queryObject< 46 AuthDto 47 >`select * from "auth" where "email" = ${email.toString()} limit 1;`; 48 if (result.rowCount !== 1) { 49 throw new EntityNotFound(email.toString()); 50 } 51 return mapToAuth(result.rows[0]); 52 } finally { 53 connection.release(); 54 } 55 } catch (error) { 56 throw mapError(error); 57 } 58 } 59 60 async findBySessionToken(sessionToken: Token): Promise<Auth> { 61 try { 62 const connection = await this.pool.connect(); 63 try { 64 const result = await connection.queryObject< 65 AuthDto 66 >`select * from "auth" where "sessionToken" = ${sessionToken.toString()} and "sessionExpire" > current_timestamp limit 1;`; 67 if (result.rowCount !== 1) { 68 throw new EntityNotFound(sessionToken.toString()); 69 } 70 return mapToAuth(result.rows[0]); 71 } finally { 72 connection.release(); 73 } 74 } catch (error) { 75 throw mapError(error); 76 } 77 } 78 79 async store(auth: Auth): Promise<void> { 80 try { 81 const dto = mapFromAuth(auth); 82 const connection = await this.pool.connect(); 83 const transaction = connection.createTransaction( 84 `txn_${dto.uuid}_${dto.version}`, 85 ); 86 try { 87 await transaction.begin(); 88 dto.version++; 89 if (dto.version === 1) { 90 await transaction.queryObject<void>` 91 insert into "auth" ( 92 "uuid", "email", "emailVerified", "emailCode", "emailCodeExpire", 93 "emailChallengeRequest", "emailChallengeRequestExpire", "emailChallengeAttempt", "emailChallengeAttemptExpire", 94 "passwordHash", "passwordAttempt", "passwordAttemptExpire", 95 "sessionToken", "sessionExpire", "version" 96 ) values ( 97 ${dto.uuid}, ${dto.email}, ${dto.emailVerified}, ${dto.emailCode}, ${dto.emailCodeExpire}, 98 ${dto.emailChallengeRequest}, ${dto.emailChallengeRequestExpire}, ${dto.emailChallengeAttempt}, ${dto.emailChallengeAttemptExpire}, 99 ${dto.passwordHash}, ${dto.passwordAttempt}, ${dto.passwordAttemptExpire}, 100 ${dto.sessionToken}, ${dto.sessionExpire}, ${dto.version} 101 ); 102 `; 103 await transaction.commit(); 104 auth.version = dto.version; 105 return; 106 } 107 const result = await transaction.queryObject<{ version: number }>` 108 update "auth" set 109 "email" = ${dto.email}, 110 "emailVerified" = ${dto.emailVerified}, 111 "emailCode" = ${dto.emailCode}, 112 "emailCodeExpire" = ${dto.emailCodeExpire}, 113 "emailChallengeRequest" = ${dto.emailChallengeRequest}, 114 "emailChallengeRequestExpire" = ${dto.emailChallengeRequestExpire}, 115 "emailChallengeAttempt" = ${dto.emailChallengeAttempt}, 116 "emailChallengeAttemptExpire" = ${dto.emailChallengeAttemptExpire}, 117 "passwordAttempt" = ${dto.passwordAttempt}, 118 "passwordAttemptExpire" = ${dto.passwordAttemptExpire}, 119 "sessionToken" = ${dto.sessionToken}, 120 "sessionExpire" = ${dto.sessionExpire}, 121 "version" = "version" + 1 122 where uuid = ${dto.uuid} 123 returning version; 124 `; 125 if (result.rowCount !== 1) { 126 transaction.rollback(); 127 throw new EntityNotFound(dto.uuid); 128 } 129 if (result.rows[0].version === dto.version) { 130 await transaction.commit(); 131 auth.version = result.rows[0].version; 132 return; 133 } 134 transaction.rollback(); 135 throw new EntityLocked(); 136 } finally { 137 connection.release(); 138 } 139 } catch (error) { 140 throw mapError(error); 141 } 142 } 143 }