5
5
getToken as getJWT ,
6
6
} from "@thirdweb-dev/auth/fastify" ;
7
7
import { AsyncWallet } from "@thirdweb-dev/wallets/evm/wallets/async" ;
8
+ import { createHash } from "crypto" ;
8
9
import { FastifyInstance } from "fastify" ;
9
10
import { FastifyRequest } from "fastify/types/request" ;
10
11
import jsonwebtoken from "jsonwebtoken" ;
@@ -102,7 +103,9 @@ export const withAuth = async (server: FastifyInstance) => {
102
103
server . decorateRequest ( "user" , null ) ;
103
104
104
105
// Add auth validation middleware to check for authenticated requests
105
- server . addHook ( "onRequest" , async ( req , res ) => {
106
+ // Note: in the onRequest hook, request.body will always be undefined, because the body parsing happens before the preValidation hook.
107
+ // https://fastify.dev/docs/latest/Reference/Hooks/#onrequest
108
+ server . addHook ( "preValidation" , async ( req , res ) => {
106
109
let message =
107
110
"Please provide a valid access token or other authentication. See: https://portal.thirdweb.com/engine/features/access-tokens" ;
108
111
@@ -162,7 +165,7 @@ export const onRequest = async ({
162
165
} else if ( payload . iss === THIRDWEB_DASHBOARD_ISSUER ) {
163
166
return await handleDashboardAuth ( jwt ) ;
164
167
} else {
165
- return await handleKeypairAuth ( jwt , payload . iss ) ;
168
+ return await handleKeypairAuth ( jwt , req , payload . iss ) ;
166
169
}
167
170
}
168
171
}
@@ -258,11 +261,14 @@ const handleWebsocketAuth = async (
258
261
* Auth via keypair.
259
262
* Allow a request that provides a JWT signed by an ES256 private key
260
263
* matching the configured public key.
264
+ * @param jwt string
261
265
* @param req FastifyRequest
266
+ * @param iss string
262
267
* @returns AuthResponse
263
268
*/
264
269
const handleKeypairAuth = async (
265
270
jwt : string ,
271
+ req : FastifyRequest ,
266
272
iss : string ,
267
273
) : Promise < AuthResponse > => {
268
274
// The keypair auth feature must be explicitly enabled.
@@ -279,10 +285,21 @@ const handleKeypairAuth = async (
279
285
}
280
286
281
287
// The JWT is valid if `verify` did not throw.
282
- jsonwebtoken . verify ( jwt , keypair . publicKey , {
288
+ const payload = jsonwebtoken . verify ( jwt , keypair . publicKey , {
283
289
algorithms : [ keypair . algorithm as jsonwebtoken . Algorithm ] ,
284
290
} ) as jsonwebtoken . JwtPayload ;
285
291
292
+ // If `bodyHash` is provided, it must match a hash of the POST request body.
293
+ if (
294
+ req . method === "POST" &&
295
+ payload ?. bodyHash &&
296
+ payload . bodyHash !== hashRequestBody ( req )
297
+ ) {
298
+ error =
299
+ "The request body does not match the hash in the access token. See: https://portal.thirdweb.com/engine/features/keypair-authentication" ;
300
+ throw error ;
301
+ }
302
+
286
303
return { isAuthed : true } ;
287
304
} catch ( e ) {
288
305
if ( e instanceof jsonwebtoken . TokenExpiredError ) {
@@ -421,3 +438,9 @@ const handleAuthWebhooks = async (
421
438
422
439
return { isAuthed : false } ;
423
440
} ;
441
+
442
+ const hashRequestBody = ( req : FastifyRequest ) : string => {
443
+ return createHash ( "sha256" )
444
+ . update ( JSON . stringify ( req . body ) , "utf8" )
445
+ . digest ( "hex" ) ;
446
+ } ;
0 commit comments