@@ -14,7 +14,7 @@ import {
14
14
strongPasswordSchema
15
15
} from '@u22n/utils' ;
16
16
import { TRPCError } from '@trpc/server' ;
17
- import { createError , setCookie } from 'h3' ;
17
+ import { createError , deleteCookie , getCookie , setCookie } from 'h3' ;
18
18
import { lucia } from '../../../utils/auth' ;
19
19
import { validateUsername } from './signupRouter' ;
20
20
import { createLuciaSessionCookie } from '../../../utils/session' ;
@@ -23,6 +23,9 @@ import { TOTPController } from 'oslo/otp';
23
23
import { useStorage , useRuntimeConfig } from '#imports' ;
24
24
25
25
export const passwordRouter = router ( {
26
+ /**
27
+ * @deprecated remove with Nuxt Webapp
28
+ */
26
29
signUpWithPassword : publicRateLimitedProcedure . signUpWithPassword
27
30
. input (
28
31
z . object ( {
@@ -77,6 +80,106 @@ export const passwordRouter = router({
77
80
return { success : true } ;
78
81
} ) ,
79
82
83
+ signUpWithPassword2FA : publicRateLimitedProcedure . signUpWithPassword
84
+ . input (
85
+ z . object ( {
86
+ username : zodSchemas . username ( ) ,
87
+ password : strongPasswordSchema ,
88
+ twoFactorCode : z . string ( ) . min ( 6 ) . max ( 6 )
89
+ } )
90
+ )
91
+ . mutation ( async ( { ctx, input } ) => {
92
+ const { username, password, twoFactorCode } = input ;
93
+ const { db } = ctx ;
94
+
95
+ const twoFaChallengeCookie = getCookie ( ctx . event , 'un-2fa-challenge' ) ;
96
+ if ( ! twoFaChallengeCookie ) {
97
+ return {
98
+ success : false ,
99
+ error : '2FA cookie not found or expired, Please try to setup new 2FA'
100
+ } ;
101
+ }
102
+
103
+ const authStorage = useStorage ( 'auth' ) ;
104
+ const twoFaChallenge = await authStorage . getItem (
105
+ `un2faChallenge:${ username } -${ twoFaChallengeCookie } `
106
+ ) ;
107
+
108
+ if ( typeof twoFaChallenge !== 'string' ) {
109
+ return {
110
+ success : false ,
111
+ error :
112
+ '2FA challenge was invalid or expired, Please try to setup new 2FA'
113
+ } ;
114
+ }
115
+
116
+ const secret = decodeHex ( twoFaChallenge ) ;
117
+ const isValid = await new TOTPController ( ) . verify ( twoFactorCode , secret ) ;
118
+
119
+ if ( ! isValid ) {
120
+ return {
121
+ success : false ,
122
+ error : 'Invalid 2FA code'
123
+ } ;
124
+ }
125
+
126
+ const { accountId, publicId, recoveryCode } = await db . transaction (
127
+ async ( tx ) => {
128
+ try {
129
+ // making sure someone doesn't bypass the client side validation
130
+ const { available, error } = await validateUsername ( tx , username ) ;
131
+ if ( ! available ) {
132
+ throw new TRPCError ( {
133
+ code : 'FORBIDDEN' ,
134
+ message : `Username Error : ${ error } `
135
+ } ) ;
136
+ }
137
+
138
+ const passwordHash = await new Argon2id ( ) . hash ( password ) ;
139
+ const publicId = typeIdGenerator ( 'account' ) ;
140
+
141
+ const recoveryCode = nanoIdToken ( ) ;
142
+ const hashedRecoveryCode = await new Argon2id ( ) . hash ( recoveryCode ) ;
143
+
144
+ const newUser = await tx . insert ( accounts ) . values ( {
145
+ username,
146
+ publicId,
147
+ passwordHash,
148
+ twoFactorEnabled : true ,
149
+ twoFactorSecret : twoFaChallenge ,
150
+ recoveryCode : hashedRecoveryCode
151
+ } ) ;
152
+
153
+ return {
154
+ accountId : Number ( newUser . insertId ) ,
155
+ publicId,
156
+ recoveryCode
157
+ } ;
158
+ } catch ( err ) {
159
+ tx . rollback ( ) ;
160
+ console . error ( err ) ;
161
+ throw err ;
162
+ }
163
+ }
164
+ ) ;
165
+
166
+ const cookie = await createLuciaSessionCookie ( ctx . event , {
167
+ accountId,
168
+ username,
169
+ publicId
170
+ } ) ;
171
+
172
+ setCookie ( ctx . event , cookie . name , cookie . value , cookie . attributes ) ;
173
+ deleteCookie ( ctx . event , 'un-2fa-challenge' ) ;
174
+
175
+ await db
176
+ . update ( accounts )
177
+ . set ( { lastLoginAt : new Date ( ) } )
178
+ . where ( eq ( accounts . id , accountId ) ) ;
179
+
180
+ return { success : true , error : null , recoveryCode } ;
181
+ } ) ,
182
+
80
183
signInWithPassword : publicRateLimitedProcedure . signInWithPassword
81
184
. input (
82
185
z . object ( {
@@ -98,6 +201,18 @@ export const passwordRouter = router({
98
201
passwordHash : true ,
99
202
twoFactorSecret : true ,
100
203
twoFactorEnabled : true
204
+ } ,
205
+ with : {
206
+ orgMemberships : {
207
+ with : {
208
+ org : {
209
+ columns : {
210
+ shortcode : true ,
211
+ id : true
212
+ }
213
+ }
214
+ }
215
+ }
101
216
}
102
217
} ) ;
103
218
@@ -192,7 +307,11 @@ export const passwordRouter = router({
192
307
. set ( { lastLoginAt : new Date ( ) } )
193
308
. where ( eq ( accounts . id , userResponse . id ) ) ;
194
309
195
- return { success : true } ;
310
+ const defaultOrg = userResponse . orgMemberships . sort (
311
+ ( a , b ) => a . id - b . id
312
+ ) [ 0 ] ?. org . shortcode ;
313
+
314
+ return { success : true , defaultOrg } ;
196
315
}
197
316
198
317
throw new TRPCError ( {
0 commit comments