Skip to content

Commit 09eccb5

Browse files
mikewuuseambot
andauthored
feat: add publishable_key_auth (#234)
* define "publishable_key" auth * update client_sessions/create to use auth array * ci: Format code --------- Co-authored-by: Seam Bot <seambot@getseam.com>
1 parent 1604ff1 commit 09eccb5

File tree

5 files changed

+109
-102
lines changed

5 files changed

+109
-102
lines changed
+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import {
2+
BadRequestException,
3+
type Middleware,
4+
UnauthorizedException,
5+
} from "nextlove"
6+
import type { AuthenticatedRequest } from "src/types/authenticated-request.ts"
7+
8+
import type { Database } from "lib/database/schema.ts"
9+
10+
export const withPublishableKey: Middleware<
11+
{
12+
auth: Extract<AuthenticatedRequest["auth"], { type: "publishable_key" }>
13+
},
14+
{
15+
db: Database
16+
}
17+
> = (next) => async (req, res) => {
18+
const is_token_auth = req.headers.authorization != null
19+
if (is_token_auth) {
20+
throw new BadRequestException({
21+
type: "token_auth_not_accepted",
22+
message: "Token auth is not accepted for this request.",
23+
})
24+
}
25+
26+
const publishable_key = req.headers["seam-publishable-key"]
27+
if (publishable_key == null) {
28+
throw new UnauthorizedException({
29+
type: "publishable_key_header_required",
30+
message: "Seam-Publishable-Key header required.",
31+
})
32+
}
33+
34+
if (typeof publishable_key !== "string") {
35+
throw new UnauthorizedException({
36+
type: "invalid_publishable_key",
37+
message: "seam-publishable-key must be a string",
38+
})
39+
}
40+
41+
const { user_identifier_key } = req.body
42+
43+
if ((user_identifier_key?.trim() ?? "").length === 0) {
44+
throw new UnauthorizedException({
45+
type: "publishable_key_requires_user_identifier_key",
46+
message: "user_identifier_key is required with publishable_key",
47+
})
48+
}
49+
50+
const workspace = req.db.workspaces.find(
51+
(w) => w.publishable_key === publishable_key,
52+
)
53+
54+
if (workspace == null) {
55+
throw new UnauthorizedException({
56+
type: "workspace_not_found",
57+
message: "Cannot find workspace associated with this publishable_key",
58+
})
59+
}
60+
61+
req.auth = {
62+
type: "publishable_key",
63+
workspace_id: workspace.workspace_id,
64+
publishable_key,
65+
sandbox: workspace.is_sandbox,
66+
}
67+
68+
return next(req, res)
69+
}

src/lib/middleware/with-route-spec.ts

+7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { withBaseUrl } from "./with-base-url.ts"
77
import { withClientSession } from "./with-client-session.ts"
88
import { withCors } from "./with-cors.ts"
99
import { withDb } from "./with-db.ts"
10+
import { withPublishableKey } from "./with-publishable-key.ts"
1011
import { withRequestId } from "./with-request-id.ts"
1112
import { withSessionAuth } from "./with-session-auth.ts"
1213

@@ -47,6 +48,11 @@ export const withRouteSpec = createWithRouteSpec({
4748
scheme: "bearer",
4849
bearerFormat: "API Key",
4950
},
51+
publishable_key: {
52+
type: "apiKey",
53+
in: "header",
54+
name: "seam-publishable-key",
55+
},
5056
},
5157

5258
authMiddlewareMap: {
@@ -62,5 +68,6 @@ export const withRouteSpec = createWithRouteSpec({
6268
}),
6369
api_key: withApiKey,
6470
client_session: withClientSession,
71+
publishable_key: withPublishableKey,
6572
},
6673
} as const)

src/pages/api/client_sessions/create.ts

+22-92
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,34 @@
1-
import {
2-
BadRequestException,
3-
HttpException,
4-
UnauthorizedException,
5-
} from "nextlove"
1+
import { BadRequestException, HttpException } from "nextlove"
62
import { z } from "zod"
73

84
import { withRouteSpec } from "lib/middleware/index.ts"
95

106
import { client_session } from "lib/zod/client_session.ts"
117

128
export default withRouteSpec({
13-
auth: "none",
9+
auth: [
10+
"api_key",
11+
"pat_with_workspace",
12+
"console_session_with_workspace",
13+
"publishable_key",
14+
],
1415
methods: ["POST", "PUT"],
1516
middlewares: [],
16-
jsonBody: z.union([
17-
z.any(),
18-
z
19-
.object({
20-
connected_account_ids: z.array(z.string()).optional(),
21-
connect_webview_ids: z.array(z.string()).optional(),
22-
user_identifier_key: z.string().optional(),
23-
})
24-
.optional(),
25-
]),
17+
jsonBody: z.object({
18+
connected_account_ids: z.array(z.string()).optional(),
19+
connect_webview_ids: z.array(z.string()).optional(),
20+
user_identifier_key: z.string().optional(),
21+
}),
2622
jsonResponse: z.object({
2723
client_session,
2824
ok: z.literal(true),
2925
}),
3026
} as const)(async (req, res) => {
31-
const user_identifier_key =
32-
req.body?.user_identifier_key ??
33-
(req.headers["user-identifier-key"] as string | undefined) ??
34-
(req.headers["seam-user-identifier-key"] as string | undefined)
35-
36-
const publishable_key = req.headers["seam-publishable-key"] as
37-
| string
38-
| undefined
39-
40-
const token =
41-
req.headers["seam-api-key"] ??
42-
req.headers.authorization?.split("Bearer ")?.[1]
43-
44-
if (publishable_key == null && token == null) {
45-
throw new BadRequestException({
46-
type: "seam_api_or_publishable_key_header_required",
47-
message: "Seam-Api-Key or Seam-Publishable-Key header required",
48-
})
49-
}
50-
51-
if (publishable_key != null && user_identifier_key == null) {
52-
throw new UnauthorizedException({
53-
type: "missing_user_identifier_key",
54-
message:
55-
"You must provide a user_identifier_key when using a publishable key",
56-
})
57-
}
58-
59-
if (token == null && user_identifier_key == null) {
60-
throw new UnauthorizedException({
61-
type: "missing_user_identifier_key",
62-
message: "You must provide a user_identifier_key when using an api key",
63-
})
64-
}
65-
66-
const { connect_webview_ids, connected_account_ids } = req.body
27+
const { connect_webview_ids, connected_account_ids, user_identifier_key } =
28+
req.body
6729

6830
if (
69-
publishable_key != null &&
31+
req.auth.type === "publishable_key" &&
7032
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
7133
(connect_webview_ids != null || connected_account_ids != null)
7234
) {
@@ -77,39 +39,11 @@ export default withRouteSpec({
7739
})
7840
}
7941

80-
let workspace_id: string | null = null
81-
let api_key
82-
if (token != null && publishable_key == null) {
83-
api_key = req.db.api_keys.find((a) => a.token === token)
84-
85-
if (api_key == null) {
86-
throw new BadRequestException({
87-
type: "invalid_api_key",
88-
message: "Invalid api key",
89-
})
90-
}
91-
workspace_id = api_key.workspace_id
92-
}
93-
94-
if (publishable_key != null && token == null) {
95-
const workspace = req.db.workspaces.find(
96-
(w) => w.publishable_key === publishable_key,
97-
)
98-
99-
if (workspace == null) {
100-
throw new BadRequestException({
101-
type: "Workspace not found",
102-
message: "Workspace not found",
103-
})
104-
}
105-
workspace_id = workspace.workspace_id
106-
}
107-
10842
if (user_identifier_key != null) {
10943
const existing_cs = req.db.client_sessions.find(
11044
(cst) =>
11145
cst.user_identifier_key === user_identifier_key &&
112-
cst.workspace_id === workspace_id,
46+
cst.workspace_id === req.auth.workspace_id,
11347
)
11448

11549
if (existing_cs != null) {
@@ -128,20 +62,16 @@ export default withRouteSpec({
12862
}
12963
}
13064

131-
if (workspace_id == null) {
132-
throw new BadRequestException({
133-
type: "workspace_id_not_found",
134-
message: "Workspace id not found",
135-
})
136-
}
137-
13865
const client_session = req.db.addClientSession({
139-
workspace_id,
66+
workspace_id: req.auth.workspace_id,
14067
connect_webview_ids,
14168
connected_account_ids,
14269
user_identifier_key,
143-
publishable_key,
144-
api_key_id: api_key?.api_key_id,
70+
publishable_key:
71+
req.auth.type === "publishable_key"
72+
? req.auth.publishable_key
73+
: undefined,
74+
api_key_id: req.auth.type === "api_key" ? req.auth.api_key_id : undefined,
14575
})
14676
const device_count = req.db.devices.filter(
14777
(d) =>

src/route-types.ts

+5-10
Original file line numberDiff line numberDiff line change
@@ -1542,16 +1542,11 @@ export type Routes = {
15421542
route: "/client_sessions/create"
15431543
method: "POST" | "PUT"
15441544
queryParams: {}
1545-
jsonBody:
1546-
| any
1547-
| (
1548-
| {
1549-
connected_account_ids?: string[] | undefined
1550-
connect_webview_ids?: string[] | undefined
1551-
user_identifier_key?: string | undefined
1552-
}
1553-
| undefined
1554-
)
1545+
jsonBody: {
1546+
connected_account_ids?: string[] | undefined
1547+
connect_webview_ids?: string[] | undefined
1548+
user_identifier_key?: string | undefined
1549+
}
15551550
commonParams: {}
15561551
formData: {}
15571552
jsonResponse: {

src/types/authenticated-request.ts

+6
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,10 @@ export type AuthenticatedRequest = Request & {
4545
user_id: string
4646
user_session_id: string
4747
}
48+
| {
49+
type: "publishable_key"
50+
publishable_key: string
51+
workspace_id: string
52+
sandbox: boolean
53+
}
4854
}

0 commit comments

Comments
 (0)