@trigger.dev/sdk, webapp: Adding more granular error messages around unauthorized requests
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@trigger.dev/sdk": patch
|
||||
---
|
||||
|
||||
Adding more granular error messages around unauthorized requests
|
||||
@@ -62,12 +62,29 @@ export async function action({ request, params }: ActionArgs) {
|
||||
|
||||
const service = new IndexEndpointService();
|
||||
|
||||
const { data, ...index } = await service.call(
|
||||
endpoint.id,
|
||||
"API",
|
||||
parsedBody.data.reason,
|
||||
parsedBody.data.data
|
||||
);
|
||||
try {
|
||||
const { data, ...index } = await service.call(
|
||||
endpoint.id,
|
||||
"API",
|
||||
parsedBody.data.reason,
|
||||
parsedBody.data.data
|
||||
);
|
||||
|
||||
return json(index);
|
||||
return json(index);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
logger.error("Error indexing endpoint", {
|
||||
url: request.url,
|
||||
error: {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
},
|
||||
});
|
||||
|
||||
return json({ error: error.message }, { status: 400 });
|
||||
}
|
||||
|
||||
return json({ error: "Something went wrong" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
import {
|
||||
ApiEventLog,
|
||||
DeliverEventResponseSchema,
|
||||
ErrorWithStackSchema,
|
||||
HttpSourceRequest,
|
||||
HttpSourceResponseSchema,
|
||||
IndexEndpointResponseSchema,
|
||||
PongResponse,
|
||||
PongResponseSchema,
|
||||
PreprocessRunBody,
|
||||
PreprocessRunResponseSchema,
|
||||
RegisterTriggerBody,
|
||||
RegisterTriggerBodySchema,
|
||||
RunJobBody,
|
||||
} from "@trigger.dev/internal";
|
||||
import {
|
||||
DeliverEventResponseSchema,
|
||||
ErrorWithStackSchema,
|
||||
IndexEndpointResponseSchema,
|
||||
HttpSourceResponseSchema,
|
||||
PongResponseSchema,
|
||||
RunJobResponseSchema,
|
||||
} from "@trigger.dev/internal";
|
||||
import { safeBodyFromResponse } from "~/utils/json";
|
||||
import { logger } from "./logger.server";
|
||||
|
||||
export class EndpointApiError extends Error {
|
||||
@@ -53,10 +52,19 @@ export class EndpointApi {
|
||||
}
|
||||
|
||||
if (response.status === 401) {
|
||||
const body = await safeBodyFromResponse(response, ErrorWithStackSchema);
|
||||
|
||||
if (body) {
|
||||
return {
|
||||
ok: false,
|
||||
error: body.message,
|
||||
} as const;
|
||||
}
|
||||
|
||||
return {
|
||||
ok: false,
|
||||
error: `Trigger API key is invalid`,
|
||||
};
|
||||
} as const;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -89,6 +97,22 @@ export class EndpointApi {
|
||||
throw new Error(`Could not connect to endpoint ${this.url}`);
|
||||
}
|
||||
|
||||
if (response.status === 401) {
|
||||
const body = await safeBodyFromResponse(response, ErrorWithStackSchema);
|
||||
|
||||
if (body) {
|
||||
return {
|
||||
ok: false,
|
||||
error: body.message,
|
||||
} as const;
|
||||
}
|
||||
|
||||
return {
|
||||
ok: false,
|
||||
error: `Trigger API key is invalid`,
|
||||
} as const;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Could not connect to endpoint ${this.url}. Status code: ${response.status}`
|
||||
@@ -101,7 +125,12 @@ export class EndpointApi {
|
||||
body: anyBody,
|
||||
});
|
||||
|
||||
return IndexEndpointResponseSchema.parse(anyBody);
|
||||
const data = IndexEndpointResponseSchema.parse(anyBody);
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
data,
|
||||
} as const;
|
||||
}
|
||||
|
||||
async deliverEvent(event: ApiEventLog) {
|
||||
|
||||
@@ -33,8 +33,14 @@ export class IndexEndpointService {
|
||||
endpoint.slug
|
||||
);
|
||||
|
||||
const indexResponse = await client.indexEndpoint();
|
||||
|
||||
if (!indexResponse.ok) {
|
||||
throw new Error(indexResponse.error);
|
||||
}
|
||||
|
||||
const { jobs, sources, dynamicTriggers, dynamicSchedules } =
|
||||
await client.indexEndpoint();
|
||||
indexResponse.data;
|
||||
|
||||
const queueName = `endpoint-${endpoint.id}`;
|
||||
|
||||
|
||||
@@ -25,3 +25,21 @@ export async function safeJsonFromResponse(response: Response) {
|
||||
const json = await response.text();
|
||||
return safeJsonParse(json);
|
||||
}
|
||||
|
||||
export async function safeBodyFromResponse<T>(
|
||||
response: Response,
|
||||
schema: z.Schema<T>
|
||||
): Promise<T | undefined> {
|
||||
const json = await response.text();
|
||||
const unknownJson = safeJsonParse(json);
|
||||
|
||||
if (!unknownJson) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedJson = schema.safeParse(unknownJson);
|
||||
|
||||
if (parsedJson.success) {
|
||||
return parsedJson.data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,20 +111,45 @@ export class TriggerClient {
|
||||
|
||||
const apiKey = request.headers.get("x-trigger-api-key");
|
||||
|
||||
if (!this.authorized(apiKey)) {
|
||||
return {
|
||||
status: 401,
|
||||
body: {
|
||||
message: "Unauthorized",
|
||||
},
|
||||
};
|
||||
const authorization = this.authorized(apiKey);
|
||||
|
||||
switch (authorization) {
|
||||
case "authorized": {
|
||||
break;
|
||||
}
|
||||
case "missing-client": {
|
||||
return {
|
||||
status: 401,
|
||||
body: {
|
||||
message: "Unauthorized: client missing apiKey",
|
||||
},
|
||||
};
|
||||
}
|
||||
case "missing-header": {
|
||||
return {
|
||||
status: 401,
|
||||
body: {
|
||||
message: "Unauthorized: missing x-trigger-api-key header",
|
||||
},
|
||||
};
|
||||
}
|
||||
case "unauthorized": {
|
||||
return {
|
||||
status: 401,
|
||||
body: {
|
||||
message: `Forbidden: client apiKey mismatch: Expected ${
|
||||
this.#options.apiKey
|
||||
}, got ${apiKey}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (request.method !== "POST") {
|
||||
return {
|
||||
status: 405,
|
||||
body: {
|
||||
message: "Method not allowed",
|
||||
message: "Method not allowed (only POST is allowed)",
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -529,14 +554,20 @@ export class TriggerClient {
|
||||
return this.#client.unregisterSchedule(this.id, id, key);
|
||||
}
|
||||
|
||||
authorized(apiKey?: string | null) {
|
||||
authorized(
|
||||
apiKey?: string | null
|
||||
): "authorized" | "unauthorized" | "missing-client" | "missing-header" {
|
||||
if (typeof apiKey !== "string") {
|
||||
return "missing-header";
|
||||
}
|
||||
|
||||
const localApiKey = this.#options.apiKey ?? process.env.TRIGGER_API_KEY;
|
||||
|
||||
if (!localApiKey) {
|
||||
return false;
|
||||
return "missing-client";
|
||||
}
|
||||
|
||||
return apiKey === localApiKey;
|
||||
return apiKey === localApiKey ? "authorized" : "unauthorized";
|
||||
}
|
||||
|
||||
apiKey() {
|
||||
|
||||
Reference in New Issue
Block a user