@@ -10,12 +10,15 @@ import {
10
10
approvalAuthenticators ,
11
11
usersTable ,
12
12
} from "../schema" ;
13
- import { eq , and , Simplify } from "drizzle-orm" ;
13
+ import { eq , and } from "drizzle-orm" ;
14
14
import { TRPCError } from "@trpc/server" ;
15
15
import { db } from "../db" ;
16
- import { startAuthentication } from "@simplewebauthn/browser" ;
17
- import { generateAuthenticationOptions } from "@simplewebauthn/server" ;
18
- import { isoUint8Array } from "@simplewebauthn/server/helpers" ;
16
+ import {
17
+ generateAuthenticationOptions ,
18
+ verifyAuthenticationResponse ,
19
+ } from "@simplewebauthn/server" ;
20
+ import { isoBase64URL , isoUint8Array } from "@simplewebauthn/server/helpers" ;
21
+ import { fromBase64URLString } from "./deviceProcedures" ;
19
22
20
23
const ApprovalRequestStatus = z . enum ( [ "pending" , "approved" , "rejected" ] ) ;
21
24
@@ -78,18 +81,19 @@ export const approvalProcedures = router({
78
81
. from ( approvalAuthenticators )
79
82
. where ( eq ( approvalAuthenticators . userId , ctx . user . id ) ) ;
80
83
81
- if ( authenticators . length === 0 )
84
+ if ( authenticators . length === 0 ) {
82
85
throw new TRPCError ( {
83
86
code : "NOT_FOUND" ,
84
87
message : "WebAuthn is not configured for this account" ,
85
88
} ) ;
89
+ }
86
90
87
91
const options = await generateAuthenticationOptions ( {
88
92
rpID : process . env . WEBAUTHN_RP_ID ! ,
89
93
allowCredentials : authenticators . map ( ( authenticator ) => ( {
90
- id : authenticator . credentialID ,
94
+ id : fromBase64URLString ( authenticator . credentialID ) ,
91
95
} ) ) ,
92
- challenge : isoUint8Array . fromUTF8String ( input . requestId . toString ( ) ) ,
96
+ challenge : `approval-request: ${ input . requestId . toString ( ) } ` ,
93
97
timeout : 60000 ,
94
98
userVerification : "required" ,
95
99
} ) ;
@@ -212,34 +216,115 @@ export const approvalProcedures = router({
212
216
} ) ,
213
217
214
218
approveRequest : protectedProcedure
215
- . input ( z . object ( { requestId : z . number ( ) } ) )
219
+ . input ( z . object ( { requestId : z . number ( ) , result : z . any ( ) } ) )
216
220
. mutation ( async ( { input, ctx } ) => {
221
+ const dbCredentialId = Buffer . from ( input . result . id ) . toString ( "base64url" ) ;
222
+
223
+ const [ authenticator ] = await db
224
+ . select ( )
225
+ . from ( approvalAuthenticators )
226
+ . where (
227
+ and (
228
+ eq ( approvalAuthenticators . userId , ctx . user . id ) ,
229
+ eq ( approvalAuthenticators . credentialID , dbCredentialId ) ,
230
+ ) ,
231
+ )
232
+ . limit ( 1 ) ;
233
+ if ( ! authenticator )
234
+ throw new TRPCError ( {
235
+ code : "FORBIDDEN" ,
236
+ message : "Authenticator not found" ,
237
+ } ) ;
238
+
239
+ const verificationResult = await verifyAuthenticationResponse ( {
240
+ response : input . result ,
241
+ expectedChallenge : isoBase64URL . fromUTF8String (
242
+ `approval-request:${ input . requestId . toString ( ) } ` ,
243
+ ) ,
244
+ expectedOrigin : process . env . WEBAUTHN_ORIGIN ! ,
245
+ expectedRPID : process . env . WEBAUTHN_RP_ID ! ,
246
+ credential : {
247
+ id : fromBase64URLString ( authenticator . credentialID ) ,
248
+ publicKey : isoBase64URL . toBuffer (
249
+ authenticator . credentialPublicKey ,
250
+ "base64url" ,
251
+ ) ,
252
+ counter : authenticator . counter ,
253
+ } ,
254
+ } ) . catch ( ( e ) => {
255
+ console . error ( e ) ;
256
+ throw new TRPCError ( {
257
+ code : "FORBIDDEN" ,
258
+ message : "Invalid authentication" ,
259
+ } ) ;
260
+ } ) ;
261
+
262
+ if ( ! verificationResult . verified ) {
263
+ throw new TRPCError ( {
264
+ code : "FORBIDDEN" ,
265
+ message : "Invalid authentication" ,
266
+ } ) ;
267
+ }
268
+
269
+ const [ approvalGroup ] = await db
270
+ . select ( {
271
+ id : approvalGroupsTable . id ,
272
+ } )
273
+ . from ( approvalGroupsTable )
274
+ . innerJoin (
275
+ approvalGroupMembersTable ,
276
+ eq ( approvalGroupsTable . id , approvalGroupMembersTable . groupId ) ,
277
+ )
278
+ . where ( eq ( approvalGroupMembersTable . userId , ctx . user . id ) )
279
+ . limit ( 1 ) ;
280
+
281
+ if ( ! approvalGroup ) {
282
+ throw new TRPCError ( {
283
+ code : "NOT_FOUND" ,
284
+ message : "Approval group not found" ,
285
+ } ) ;
286
+ }
287
+
217
288
// Add the user's approval
218
289
await db
219
290
. insert ( approvalsTable )
220
- . values ( { requestId : input . requestId , userId : ctx . user . id } ) ;
291
+ . values ( {
292
+ requestId : input . requestId ,
293
+ groupId : approvalGroup . id ,
294
+ userId : ctx . user . id ,
295
+ } )
296
+ . onConflictDoNothing ( ) ;
221
297
222
298
// Check if the request should be approved
223
- const request = await db
299
+ const [ request ] = await db
224
300
. select ( )
225
301
. from ( approvalRequestsTable )
226
302
. where ( eq ( approvalRequestsTable . id , input . requestId ) )
227
303
. limit ( 1 ) ;
228
304
305
+ if ( ! request ) {
306
+ throw new TRPCError ( {
307
+ code : "NOT_FOUND" ,
308
+ message : "Request not found" ,
309
+ } ) ;
310
+ }
311
+
229
312
const approvalGroups = await db
230
313
. select ( )
231
314
. from ( approvalGroupsTable )
232
- . where ( eq ( approvalGroupsTable . packageId , request [ 0 ] . packageId ) ) ;
315
+ . where ( eq ( approvalGroupsTable . packageId , request . packageId ) ) ;
233
316
317
+ //
234
318
const approvedGroups = await db
235
- . select ( )
236
- . from ( approvalsTable )
319
+ . select ( { id : approvalGroupsTable . id } )
320
+ . from ( approvalGroupsTable )
237
321
. innerJoin (
238
- approvalGroupMembersTable ,
239
- eq ( approvalsTable . userId , approvalGroupMembersTable . userId ) ,
322
+ approvalsTable ,
323
+ eq ( approvalGroupsTable . id , approvalsTable . groupId ) ,
240
324
)
241
- . where ( eq ( approvalsTable . requestId , input . requestId ) )
242
- . groupBy ( approvalGroupMembersTable . groupId ) ;
325
+ . where ( eq ( approvalsTable . requestId , input . requestId ) ) ;
326
+ console . log ( "approvedGroups" , approvedGroups ) ;
327
+ console . log ( "approvalGroups" , approvalGroups ) ;
243
328
244
329
if ( approvedGroups . length === approvalGroups . length ) {
245
330
// All groups have at least one approval, update the request status
0 commit comments