@@ -10,7 +10,8 @@ import type {
1010 RoleBaseAccessController ,
1111} from "@trigger.dev/plugins" ;
1212import type { PrismaClient } from "@trigger.dev/database" ;
13- import { buildFallbackAbility , permissiveAbility } from "./ability.js" ;
13+ import { validateJWT } from "@trigger.dev/core/v3/jwt" ;
14+ import { buildFallbackAbility , buildJwtAbility , permissiveAbility } from "./ability.js" ;
1415
1516export class RoleBaseAccessFallback {
1617 constructor ( private readonly prisma : PrismaClient ) { }
@@ -28,12 +29,55 @@ class RoleBaseAccessFallbackController implements RoleBaseAccessController {
2829 private readonly helpers : { getSessionUserId : ( request : Request ) => Promise < string | null > }
2930 ) { }
3031
31- async authenticateBearer ( request : Request ) : Promise < BearerAuthResult > {
32- const apiKey = request . headers . get ( "Authorization" ) ?. replace ( / ^ B e a r e r / , "" ) . trim ( ) ;
33- if ( ! apiKey ) return { ok : false , status : 401 , error : "Invalid or Missing API key" } ;
32+ async authenticateBearer (
33+ request : Request ,
34+ options ?: { allowJWT ?: boolean }
35+ ) : Promise < BearerAuthResult > {
36+ const rawToken = request . headers . get ( "Authorization" ) ?. replace ( / ^ B e a r e r / , "" ) . trim ( ) ;
37+ if ( ! rawToken ) return { ok : false , status : 401 , error : "Invalid or Missing API key" } ;
38+
39+ if ( options ?. allowJWT && isPublicJWT ( rawToken ) ) {
40+ const envId = extractJWTSub ( rawToken ) ;
41+ if ( ! envId ) return { ok : false , status : 401 , error : "Invalid Public Access Token" } ;
42+
43+ const env = await this . prisma . runtimeEnvironment . findFirst ( {
44+ where : { id : envId } ,
45+ include : {
46+ project : true ,
47+ organization : true ,
48+ parentEnvironment : { select : { apiKey : true } } ,
49+ } ,
50+ } ) ;
51+ if ( ! env || env . project . deletedAt !== null ) {
52+ return { ok : false , status : 401 , error : "Invalid Public Access Token" } ;
53+ }
54+
55+ const signingKey = env . parentEnvironment ?. apiKey ?? env . apiKey ;
56+ const result = await validateJWT ( rawToken , signingKey ) ;
57+ if ( ! result . ok ) return { ok : false , status : 401 , error : "Public Access Token is invalid" } ;
58+
59+ const scopes = Array . isArray ( result . payload . scopes )
60+ ? ( result . payload . scopes as string [ ] )
61+ : [ ] ;
62+ const realtime = result . payload . realtime as { skipColumns ?: string [ ] } | undefined ;
63+ const oneTimeUse = result . payload . otu === true ;
64+
65+ return {
66+ ok : true ,
67+ environment : toRbacEnvironment ( env ) ,
68+ subject : {
69+ type : "publicJWT" ,
70+ environmentId : env . id ,
71+ organizationId : env . organizationId ,
72+ projectId : env . projectId ,
73+ } ,
74+ ability : buildJwtAbility ( scopes ) ,
75+ jwt : { realtime, oneTimeUse } ,
76+ } ;
77+ }
3478
3579 const env = await this . prisma . runtimeEnvironment . findFirst ( {
36- where : { apiKey } ,
80+ where : { apiKey : rawToken } ,
3781 include : {
3882 project : true ,
3983 organization : true ,
@@ -87,9 +131,10 @@ class RoleBaseAccessFallbackController implements RoleBaseAccessController {
87131
88132 async authenticateAuthorizeBearer (
89133 request : Request ,
90- check : { action : string ; resource : RbacResource }
134+ check : { action : string ; resource : RbacResource } ,
135+ options ?: { allowJWT ?: boolean }
91136 ) : Promise < BearerAuthResult > {
92- const auth = await this . authenticateBearer ( request ) ;
137+ const auth = await this . authenticateBearer ( request , options ) ;
93138 if ( ! auth . ok ) return auth ;
94139 if ( ! auth . ability . can ( check . action , check . resource ) ) {
95140 return { ok : false , status : 403 , error : "Unauthorized" } ;
@@ -143,6 +188,30 @@ class RoleBaseAccessFallbackController implements RoleBaseAccessController {
143188 async removeTokenRole ( ) : Promise < void > { }
144189}
145190
191+ function isPublicJWT ( token : string ) : boolean {
192+ const parts = token . split ( "." ) ;
193+ if ( parts . length !== 3 ) return false ;
194+ try {
195+ const payload = JSON . parse ( Buffer . from ( parts [ 1 ] . replace ( / - / g, "+" ) . replace ( / _ / g, "/" ) , "base64" ) . toString ( "utf8" ) ) ;
196+ return payload !== null && typeof payload === "object" && payload . pub === true ;
197+ } catch {
198+ return false ;
199+ }
200+ }
201+
202+ function extractJWTSub ( token : string ) : string | undefined {
203+ const parts = token . split ( "." ) ;
204+ if ( parts . length !== 3 ) return undefined ;
205+ try {
206+ const payload = JSON . parse ( Buffer . from ( parts [ 1 ] . replace ( / - / g, "+" ) . replace ( / _ / g, "/" ) , "base64" ) . toString ( "utf8" ) ) ;
207+ return payload !== null && typeof payload === "object" && typeof payload . sub === "string"
208+ ? payload . sub
209+ : undefined ;
210+ } catch {
211+ return undefined ;
212+ }
213+ }
214+
146215function toRbacEnvironment (
147216 env : {
148217 id : string ;
0 commit comments