1
1
import {
2
2
router ,
3
- accountProcedure ,
4
3
turnstileProcedure ,
5
4
publicProcedure
6
5
} from '~platform/trpc/trpc' ;
7
- import { setCookie , getCookie , deleteCookie } from '@u22n/hono/helpers ' ;
6
+ import { COOKIE_TWO_FACTOR_LOGIN_CHALLENGE } from '~platform/utils/cookieNames ' ;
8
7
import { createLuciaSessionCookie } from '~platform/utils/session' ;
9
8
import { nanoIdToken , zodSchemas } from '@u22n/utils/zodSchemas' ;
10
9
import { strongPasswordSchema } from '@u22n/utils/password' ;
11
10
import { ratelimiter } from '~platform/trpc/ratelimit' ;
12
11
import { typeIdGenerator } from '@u22n/utils/typeid' ;
13
12
import { validateUsername } from './signupRouter' ;
14
13
import { accounts } from '@u22n/database/schema' ;
15
- import { lucia } from '~platform/utils/auth ' ;
14
+ import { setCookie } from '@u22n/hono/helpers ' ;
16
15
import { storage } from '~platform/storage' ;
17
- import { decodeHex } from 'oslo/encoding' ;
18
- import { TOTPController } from 'oslo/otp' ;
16
+ import { datePlus } from '@u22n/utils/ms' ;
19
17
import { Argon2id } from 'oslo/password' ;
20
18
import { TRPCError } from '@trpc/server' ;
21
19
import { eq } from '@u22n/database/orm' ;
22
- import { ms } from '@u22n/utils/ms' ;
23
20
import { env } from '~platform/env' ;
24
21
import { z } from 'zod' ;
25
22
@@ -68,101 +65,7 @@ export const passwordRouter = router({
68
65
69
66
return { success : true } ;
70
67
} ) ,
71
- signUpWithPassword2FA : publicProcedure
72
- . unstable_concat ( turnstileProcedure )
73
- . use ( ratelimiter ( { limit : 10 , namespace : 'signUp.password' } ) )
74
- . input (
75
- z . object ( {
76
- username : zodSchemas . username ( ) ,
77
- password : strongPasswordSchema ,
78
- twoFactorCode : z . string ( ) . min ( 6 ) . max ( 6 )
79
- } )
80
- )
81
- . mutation ( async ( { ctx, input } ) => {
82
- const { username, password, twoFactorCode } = input ;
83
- const { db, event } = ctx ;
84
-
85
- const twoFaChallengeCookie = getCookie ( event , 'un-2fa-challenge' ) ;
86
- if ( ! twoFaChallengeCookie ) {
87
- return {
88
- success : false ,
89
- error : '2FA cookie not found or expired, Please try to setup new 2FA'
90
- } ;
91
- }
92
-
93
- const authStorage = storage . auth ;
94
- const twoFaChallenge = await authStorage . getItem (
95
- `un2faChallenge:${ username } -${ twoFaChallengeCookie } `
96
- ) ;
97
-
98
- if ( typeof twoFaChallenge !== 'string' ) {
99
- return {
100
- success : false ,
101
- error :
102
- '2FA challenge was invalid or expired, Please try to setup new 2FA'
103
- } ;
104
- }
105
-
106
- const secret = decodeHex ( twoFaChallenge ) ;
107
- const isValid = await new TOTPController ( ) . verify ( twoFactorCode , secret ) ;
108
-
109
- if ( ! isValid ) {
110
- return {
111
- success : false ,
112
- error : 'Invalid 2FA code'
113
- } ;
114
- }
115
-
116
- const { accountId, publicId, recoveryCode } = await db . transaction (
117
- async ( tx ) => {
118
- try {
119
- // making sure someone doesn't bypass the client side validation
120
- const { available, error } = await validateUsername ( tx , username ) ;
121
- if ( ! available ) {
122
- throw new TRPCError ( {
123
- code : 'FORBIDDEN' ,
124
- message : `Username Error : ${ error } `
125
- } ) ;
126
- }
127
-
128
- const passwordHash = await new Argon2id ( ) . hash ( password ) ;
129
- const publicId = typeIdGenerator ( 'account' ) ;
130
-
131
- const recoveryCode = nanoIdToken ( ) ;
132
- const hashedRecoveryCode = await new Argon2id ( ) . hash ( recoveryCode ) ;
133
-
134
- const newUser = await tx . insert ( accounts ) . values ( {
135
- username,
136
- publicId,
137
- passwordHash,
138
- twoFactorEnabled : true ,
139
- twoFactorSecret : twoFaChallenge ,
140
- recoveryCode : hashedRecoveryCode
141
- } ) ;
142
68
143
- return {
144
- accountId : Number ( newUser . insertId ) ,
145
- publicId,
146
- recoveryCode
147
- } ;
148
- } catch ( err ) {
149
- tx . rollback ( ) ;
150
- console . error ( err ) ;
151
- throw err ;
152
- }
153
- }
154
- ) ;
155
-
156
- await createLuciaSessionCookie ( event , {
157
- accountId,
158
- username,
159
- publicId
160
- } ) ;
161
-
162
- deleteCookie ( event , 'un-2fa-challenge' ) ;
163
-
164
- return { success : true , error : null , recoveryCode } ;
165
- } ) ,
166
69
signIn : publicProcedure
167
70
. unstable_concat ( turnstileProcedure )
168
71
. use ( ratelimiter ( { limit : 20 , namespace : 'signIn.password' } ) )
@@ -241,10 +144,10 @@ export const passwordRouter = router({
241
144
242
145
setCookie (
243
146
ctx . event ,
244
- 'two-factor-login-challenge' ,
147
+ COOKIE_TWO_FACTOR_LOGIN_CHALLENGE ,
245
148
twoFactorChallengeId ,
246
149
{
247
- maxAge : ms ( '5 minutes' ) ,
150
+ expires : datePlus ( '5 minutes' ) ,
248
151
domain : env . PRIMARY_DOMAIN ,
249
152
httpOnly : true
250
153
}
@@ -264,92 +167,5 @@ export const passwordRouter = router({
264
167
defaultOrgShortcode : userResponse . orgMemberships [ 0 ] ?. org . shortcode
265
168
} ;
266
169
}
267
- } ) ,
268
-
269
- updateUserPassword : accountProcedure
270
- . input (
271
- z . object ( {
272
- oldPassword : z . string ( ) . min ( 8 ) ,
273
- newPassword : strongPasswordSchema ,
274
- otp : zodSchemas . nanoIdToken ( ) ,
275
- invalidateAllSessions : z . boolean ( ) . default ( false )
276
- } )
277
- )
278
- . mutation ( async ( { ctx, input } ) => {
279
- const { db, account, event } = ctx ;
280
- const accountId = account . id ;
281
-
282
- const accountData = await db . query . accounts . findFirst ( {
283
- where : eq ( accounts . id , accountId ) ,
284
- columns : {
285
- publicId : true ,
286
- username : true ,
287
- passwordHash : true ,
288
- twoFactorSecret : true
289
- }
290
- } ) ;
291
-
292
- if ( ! accountData ) {
293
- throw new TRPCError ( {
294
- code : 'NOT_FOUND' ,
295
- message : 'User not found'
296
- } ) ;
297
- }
298
-
299
- if ( ! accountData . passwordHash ) {
300
- throw new TRPCError ( {
301
- code : 'METHOD_NOT_SUPPORTED' ,
302
- message : 'Password sign-in is not enabled'
303
- } ) ;
304
- }
305
-
306
- const oldPasswordValid = await new Argon2id ( ) . verify (
307
- accountData . passwordHash ,
308
- input . oldPassword
309
- ) ;
310
-
311
- if ( ! oldPasswordValid ) {
312
- throw new TRPCError ( {
313
- code : 'UNAUTHORIZED' ,
314
- message : 'Incorrect old password'
315
- } ) ;
316
- }
317
-
318
- if ( ! accountData . twoFactorSecret ) {
319
- throw new TRPCError ( {
320
- code : 'METHOD_NOT_SUPPORTED' ,
321
- message : '2FA is not enabled on this account, contact support'
322
- } ) ;
323
- }
324
- const secret = decodeHex ( accountData . twoFactorSecret ) ;
325
- const otpValid = await new TOTPController ( ) . verify ( input . otp , secret ) ;
326
- if ( ! otpValid ) {
327
- throw new TRPCError ( {
328
- code : 'UNAUTHORIZED' ,
329
- message : 'Invalid 2FA code'
330
- } ) ;
331
- }
332
-
333
- const passwordHash = await new Argon2id ( ) . hash ( input . newPassword ) ;
334
-
335
- await db
336
- . update ( accounts )
337
- . set ( {
338
- passwordHash
339
- } )
340
- . where ( eq ( accounts . id , accountId ) ) ;
341
-
342
- // Invalidate all sessions if requested
343
- if ( input . invalidateAllSessions ) {
344
- await lucia . invalidateUserSessions ( accountId ) ;
345
- }
346
-
347
- await createLuciaSessionCookie ( event , {
348
- accountId,
349
- username : accountData . username ,
350
- publicId : accountData . publicId
351
- } ) ;
352
-
353
- return { success : true } ;
354
170
} )
355
171
} ) ;
0 commit comments