({
ctx,
input,
}: InferProcedureOpts<typeof baseProcedure>)
| 60 | export const loginProcedure = once(() => baseProcedure.mutation(login)); |
| 61 | |
| 62 | export async function login({ |
| 63 | ctx, |
| 64 | input, |
| 65 | }: InferProcedureOpts<typeof baseProcedure>) { |
| 66 | return await ctx.dataAbstraction.transaction(async (dtrx) => { |
| 67 | // Check for excessive failed login attempts |
| 68 | |
| 69 | const failedLoginAttempts = await _checkFailedLoginAttempts({ |
| 70 | redis: ctx.redis, |
| 71 | |
| 72 | ip: ctx.req.ip, |
| 73 | email: input.email, |
| 74 | }); |
| 75 | |
| 76 | if (failedLoginAttempts.excessive) { |
| 77 | throw new TRPCError({ |
| 78 | message: `Too many failed login attempts. Try again in ${failedLoginAttempts.loginBlockTTL} minutes.`, |
| 79 | code: 'TOO_MANY_REQUESTS', |
| 80 | }); |
| 81 | } |
| 82 | |
| 83 | // Get user data |
| 84 | |
| 85 | const user = await UserModel.query() |
| 86 | .where('email_hash', Buffer.from(hashUserEmail(input.email))) |
| 87 | .where((builder) => |
| 88 | builder |
| 89 | .where('email_verified', true) |
| 90 | .orWhere('email_verification_expiration_date', '>', new Date()), |
| 91 | ) |
| 92 | .select( |
| 93 | 'users.id', |
| 94 | |
| 95 | 'users.email_verified', |
| 96 | 'users.email_verification_code', |
| 97 | |
| 98 | 'users.encrypted_rehashed_login_hash', |
| 99 | |
| 100 | 'users.public_keyring', |
| 101 | 'users.encrypted_private_keyring', |
| 102 | 'users.encrypted_symmetric_keyring', |
| 103 | |
| 104 | 'users.two_factor_auth_enabled', |
| 105 | 'users.encrypted_authenticator_secret', |
| 106 | 'users.encrypted_recovery_codes', |
| 107 | |
| 108 | 'users.personal_group_id', |
| 109 | ) |
| 110 | .first(); |
| 111 | |
| 112 | if (user == null) { |
| 113 | await _incrementFailedLoginAttempts({ |
| 114 | redis: ctx.redis, |
| 115 | |
| 116 | ip: ctx.req.ip, |
| 117 | email: input.email, |
| 118 | }); |
| 119 |
nothing calls this directly
no test coverage detected