-
Notifications
You must be signed in to change notification settings - Fork 813
Expand file tree
/
Copy pathapi-key-org.ts
More file actions
93 lines (80 loc) · 2.93 KB
/
api-key-org.ts
File metadata and controls
93 lines (80 loc) · 2.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import type { auth as betterAuth } from "@superset/auth/server";
import { db } from "@superset/db/client";
import { members } from "@superset/db/schema";
import { and, eq } from "drizzle-orm";
const API_KEY_HEADER = "x-api-key";
const BEARER_API_KEY_PREFIX = "sk_live_";
function extractApiKey(req: Request): string | undefined {
const headerKey = req.headers.get(API_KEY_HEADER);
if (headerKey) return headerKey;
const authorization = req.headers.get("authorization");
const match = authorization?.match(/^Bearer\s+(.+)$/i);
const bearer = match?.[1];
if (bearer?.startsWith(BEARER_API_KEY_PREFIX)) return bearer;
return undefined;
}
function parseApiKeyMetadata(
metadata: unknown,
): Record<string, unknown> | null {
if (!metadata) return null;
if (typeof metadata === "string") {
try {
const parsed = JSON.parse(metadata);
return parsed && typeof parsed === "object"
? (parsed as Record<string, unknown>)
: null;
} catch {
return null;
}
}
return typeof metadata === "object"
? (metadata as Record<string, unknown>)
: null;
}
export type ApiKeyResolution =
| { kind: "not-api-key" }
| { kind: "no-organization-metadata" }
| { kind: "ok"; organizationId: string }
| { kind: "invalid" };
/**
* Resolve the organization an api-key-authed request should operate
* against. api keys created via `apiKeyRouter.create` store their
* intended org in `metadata.organizationId`. Without this, the
* api-key-synthesized session has no `activeOrganizationId` and
* falls through to the default newest-membership resolver, which
* silently routes requests to the wrong org when a user belongs to
* more than one.
*
* Kinds:
* - `not-api-key` — request is not api-key authed; caller should use session as-is
* - `no-organization-metadata` — legacy key without org metadata; caller should use session as-is
* - `ok` — key's org is valid and user is still a member; caller should override active org
* - `invalid` — key verification failed OR user is no longer a member of the key's org; caller should deny the request
*/
export async function resolveApiKey(
req: Request,
auth: typeof betterAuth,
userId: string,
): Promise<ApiKeyResolution> {
const apiKey = extractApiKey(req);
if (!apiKey) return { kind: "not-api-key" };
try {
const result = await auth.api.verifyApiKey({ body: { key: apiKey } });
if (!result.valid || !result.key) return { kind: "invalid" };
const metadata = parseApiKeyMetadata(result.key.metadata);
const organizationId = metadata?.organizationId;
if (typeof organizationId !== "string") {
return { kind: "no-organization-metadata" };
}
const membership = await db.query.members.findFirst({
where: and(
eq(members.userId, userId),
eq(members.organizationId, organizationId),
),
});
if (!membership) return { kind: "invalid" };
return { kind: "ok", organizationId };
} catch {
return { kind: "invalid" };
}
}