Skip to content

Commit 5e585ea

Browse files
committed
Improve realtime & frontend authentication errors
1 parent 4833881 commit 5e585ea

File tree

7 files changed

+558
-375
lines changed

7 files changed

+558
-375
lines changed

apps/webapp/app/services/authorization.server.ts

+26-8
Original file line numberDiff line numberDiff line change
@@ -35,36 +35,45 @@ export type AuthorizationEntity = {
3535
* checkAuthorization(entity, "read", { tasks: ["task_5678"] }); // Returns true
3636
* ```
3737
*/
38+
export type AuthorizationResult = { authorized: true } | { authorized: false; reason: string };
39+
40+
/**
41+
* Checks if the given entity is authorized to perform a specific action on a resource.
42+
*/
3843
export function checkAuthorization(
3944
entity: AuthorizationEntity,
4045
action: AuthorizationAction,
4146
resource: AuthorizationResources,
4247
superScopes?: string[]
43-
) {
48+
): AuthorizationResult {
4449
// "PRIVATE" is a secret key and has access to everything
4550
if (entity.type === "PRIVATE") {
46-
return true;
51+
return { authorized: true };
4752
}
4853

4954
// "PUBLIC" is a deprecated key and has no access
5055
if (entity.type === "PUBLIC") {
51-
return false;
56+
return { authorized: false, reason: "PUBLIC type is deprecated and has no access" };
5257
}
5358

5459
// If the entity has no permissions, deny access
5560
if (!entity.scopes || entity.scopes.length === 0) {
56-
return false;
61+
return {
62+
authorized: false,
63+
reason:
64+
"Public Access Token has no permissions. See https://trigger.dev/docs/frontend/overview#authentication for more information.",
65+
};
5766
}
5867

5968
// If the resource object is empty, deny access
6069
if (Object.keys(resource).length === 0) {
61-
return false;
70+
return { authorized: false, reason: "Resource object is empty" };
6271
}
6372

6473
// Check for any of the super scopes
6574
if (superScopes && superScopes.length > 0) {
6675
if (superScopes.some((permission) => entity.scopes?.includes(permission))) {
67-
return true;
76+
return { authorized: true };
6877
}
6978
}
7079

@@ -94,10 +103,19 @@ export function checkAuthorization(
94103

95104
// If any resource is not authorized, return false
96105
if (!resourceAuthorized) {
97-
return false;
106+
return {
107+
authorized: false,
108+
reason: `Public Access Token is missing required permissions. Permissions required for ${resourceValues
109+
.map((v) => `'${action}:${resourceType}:${v}'`)
110+
.join(", ")} but token has the following permissions: ${entity.scopes
111+
.map((s) => `'${s}'`)
112+
.join(
113+
", "
114+
)}. See https://trigger.dev/docs/frontend/overview#authentication for more information.`,
115+
};
98116
}
99117
}
100118

101119
// All resources are authorized
102-
return true;
120+
return { authorized: true };
103121
}

apps/webapp/app/services/realtime/jwtAuth.server.ts

+35-7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { json } from "@remix-run/server-runtime";
12
import { validateJWT } from "@trigger.dev/core/v3/jwt";
23
import { findEnvironmentById } from "~/models/runtimeEnvironment.server";
34

@@ -9,24 +10,51 @@ export async function validatePublicJwtKey(token: string) {
910
const sub = extractJWTSub(token);
1011

1112
if (!sub) {
12-
return;
13+
throw json({ error: "Invalid Public Access Token, missing subject." }, { status: 401 });
1314
}
1415

1516
const environment = await findEnvironmentById(sub);
1617

1718
if (!environment) {
18-
return;
19+
throw json({ error: "Invalid Public Access Token, environment not found." }, { status: 401 });
1920
}
2021

21-
const claims = await validateJWT(token, environment.apiKey);
22-
23-
if (!claims) {
24-
return;
22+
const result = await validateJWT(token, environment.apiKey);
23+
24+
if (!result.ok) {
25+
switch (result.code) {
26+
case "ERR_JWT_EXPIRED": {
27+
throw json(
28+
{
29+
error:
30+
"Public Access Token has expired. See https://trigger.dev/docs/frontend/overview#authentication for more information.",
31+
},
32+
{ status: 401 }
33+
);
34+
}
35+
case "ERR_JWT_CLAIM_INVALID": {
36+
throw json(
37+
{
38+
error: `Public Access Token is invalid: ${result.error}. See https://trigger.dev/docs/frontend/overview#authentication for more information.`,
39+
},
40+
{ status: 401 }
41+
);
42+
}
43+
default: {
44+
throw json(
45+
{
46+
error:
47+
"Public Access Token is invalid. See https://trigger.dev/docs/frontend/overview#authentication for more information.",
48+
},
49+
{ status: 401 }
50+
);
51+
}
52+
}
2553
}
2654

2755
return {
2856
environment,
29-
claims,
57+
claims: result.payload,
3058
};
3159
}
3260

0 commit comments

Comments
 (0)