Skip to content

Commit d6d5f62

Browse files
mikewuuseambot
andauthored
feat: add /bridge/v1/bridge_client_sessions/* endpoints (#238)
* add new bridge_client_session endpoints: create, get * fix addBridgeClientSession() method * fix seeders, add tests * ci: Generate code --------- Co-authored-by: Seam Bot <seambot@getseam.com>
1 parent 8108c90 commit d6d5f62

File tree

15 files changed

+367
-1
lines changed

15 files changed

+367
-1
lines changed

src/lib/database/schema.ts

+5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
AcsUser,
99
ActionAttempt,
1010
ApiKey,
11+
BridgeClientSession,
1112
ClientSession,
1213
ConnectedAccount,
1314
ConnectWebview,
@@ -49,6 +50,7 @@ export interface DatabaseState {
4950
enrollment_automations: EnrollmentAutomation[]
5051
connect_webviews: ConnectWebview[]
5152
client_sessions: ClientSession[]
53+
bridge_client_sessions: BridgeClientSession[]
5254
connected_accounts: ConnectedAccount[]
5355
devices: Device[]
5456
events: Event[]
@@ -105,6 +107,9 @@ export interface DatabaseMethods {
105107
addClientSession: (
106108
params: Pick<ClientSession, "workspace_id"> & Partial<ClientSession>,
107109
) => ClientSession
110+
addBridgeClientSession: (
111+
params: Partial<BridgeClientSession>,
112+
) => BridgeClientSession
108113
addUserIdentity: (params: {
109114
workspace_id: WorkspaceId
110115
user_identity_id?: string

src/lib/database/seed.ts

+14
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export interface Seed {
2424
john_user_key: "john_user_key"
2525
visionline_acs_system_1: "visionline_acs_system_1"
2626
seam_at1_token: "seam_at1_longtoken"
27+
bridge_client_session_token: "bcs1_token"
2728
}
2829

2930
export const seed: Seed = {
@@ -48,6 +49,7 @@ export const seed: Seed = {
4849
john_user_id: "john_user_id",
4950
john_user_key: "john_user_key",
5051
visionline_acs_system_1: "visionline_acs_system_1",
52+
bridge_client_session_token: "bcs1_token",
5153
} as const
5254

5355
export const seedDatabase = (db: Database): Seed => {
@@ -235,6 +237,18 @@ export const seedDatabase = (db: Database): Seed => {
235237
api_key_id: api_key_1.api_key_id,
236238
})
237239

240+
db.addBridgeClientSession({
241+
bridge_client_session_id: "bcs1",
242+
bridge_client_session_token: "bcs1_token",
243+
pairing_code: "123456",
244+
pairing_code_expires_at: new Date().toISOString(),
245+
tailscale_hostname: "bcs1_tailscale_host",
246+
tailscale_auth_key: ["bcs1_tailscale_key"],
247+
bridge_client_name: "bridge_1",
248+
bridge_client_time_zone: "America/Los_Angeles",
249+
bridge_client_machine_identifier_key: "bcs1_machine",
250+
})
251+
238252
const connected_account = db.addConnectedAccount({
239253
provider: "assa_abloy_credential_service",
240254
workspace_id: seed.seed_workspace_1,

src/lib/database/store.ts

+29-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { immer } from "zustand/middleware/immer"
77
import { createStore, type StoreApi } from "zustand/vanilla"
88
import { hoist } from "zustand-hoist"
99

10-
import type { UserSession } from "lib/zod/index.ts"
10+
import type { BridgeClientSession, UserSession } from "lib/zod/index.ts"
1111

1212
import {
1313
ACS_ACCESS_GROUP_EXTERNAL_TYPE_TO_DISPLAY_NAME,
@@ -71,6 +71,7 @@ const initializer = immer<Database>((set, get) => ({
7171
simulatedWorkspaceOutages: {},
7272
simulatedEvents: {},
7373
client_sessions: [],
74+
bridge_client_sessions: [],
7475
assa_abloy_credential_services: [],
7576
assa_abloy_cards: [],
7677
endpoints: [],
@@ -298,6 +299,33 @@ const initializer = immer<Database>((set, get) => ({
298299
return new_cst
299300
},
300301

302+
addBridgeClientSession(params) {
303+
const bridge_client_session_id = get()._getNextId("bcs")
304+
305+
const bridge_client_session: BridgeClientSession = {
306+
created_at: new Date().toISOString(),
307+
bridge_client_session_id,
308+
bridge_client_session_token: `${bridge_client_session_id}_token`,
309+
pairing_code: "123456",
310+
pairing_code_expires_at: new Date().toISOString(),
311+
tailscale_hostname: `${bridge_client_session_id}_tailscale_host`,
312+
tailscale_auth_key: [`${bridge_client_session_id}_tailscale_auth`],
313+
bridge_client_name: `${bridge_client_session_id}_bridge`,
314+
bridge_client_time_zone: "America/Los_Angeles",
315+
bridge_client_machine_identifier_key: `${bridge_client_session_id}_key`,
316+
...params,
317+
}
318+
319+
set({
320+
bridge_client_sessions: [
321+
...get().bridge_client_sessions,
322+
bridge_client_session,
323+
],
324+
})
325+
326+
return bridge_client_session
327+
},
328+
301329
addUserIdentity(params) {
302330
const user_identity_id = params.user_identity_id ?? get()._getNextId("uid")
303331
const new_user_identity: UserIdentity = {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { type Middleware, UnauthorizedException } from "nextlove"
2+
import type { AuthenticatedRequest } from "src/types/authenticated-request.ts"
3+
4+
import type { Database } from "lib/database/index.ts"
5+
6+
export const withBridgeClientSession: Middleware<
7+
{
8+
auth: Extract<
9+
AuthenticatedRequest["auth"],
10+
{ type: "bridge_client_session" }
11+
>
12+
},
13+
{
14+
db: Database
15+
}
16+
> = (next) => async (req, res) => {
17+
const session_token = req.headers?.authorization?.split("Bearer ")?.[1]
18+
19+
if (session_token == null) {
20+
throw new UnauthorizedException({
21+
type: "unauthorized",
22+
message: "Missing Bridge client session auth token",
23+
})
24+
}
25+
26+
const bridge_client_session = req.db.bridge_client_sessions.find(
27+
(bridge_client_session) =>
28+
bridge_client_session.bridge_client_session_token === session_token,
29+
)
30+
31+
if (bridge_client_session == null) {
32+
throw new UnauthorizedException({
33+
type: "unauthorized",
34+
message: "Invalid Session",
35+
})
36+
}
37+
38+
req.auth = {
39+
type: "bridge_client_session",
40+
bridge_client_session: {
41+
bridge_client_session_id: bridge_client_session.bridge_client_session_id,
42+
},
43+
}
44+
45+
return next(req, res)
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import type { Middleware } from "nextlove"
2+
import type { AuthenticatedRequest } from "src/types/authenticated-request.ts"
3+
4+
import type { Database } from "lib/database/index.ts"
5+
6+
/**
7+
* Middleware to check the certified client
8+
* Certified clients are not yet implemented, this middleware is a placeholder.
9+
* Once implemented, this middleware will be used to verify the authenticity of the client (implementation tbd)
10+
*/
11+
export const withCertifiedClient: Middleware<
12+
{
13+
auth: Extract<AuthenticatedRequest["auth"], { type: "certified_client" }>
14+
},
15+
{
16+
db: Database
17+
}
18+
> = (next) => async (req, res) => {
19+
req.auth = {
20+
type: "certified_client",
21+
}
22+
23+
return next(req, res)
24+
}

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

+14
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { withAccessToken } from "./with-access-token.ts"
44
import { withAdminAuth } from "./with-admin-auth.ts"
55
import { withApiKey } from "./with-api-key.ts"
66
import { withBaseUrl } from "./with-base-url.ts"
7+
import { withBridgeClientSession } from "./with-bridge-client-session.ts"
8+
import { withCertifiedClient } from "./with-certified-client.ts"
79
import { withClientSession } from "./with-client-session.ts"
810
import { withCors } from "./with-cors.ts"
911
import { withDb } from "./with-db.ts"
@@ -43,6 +45,11 @@ export const withRouteSpec = createWithRouteSpec({
4345
scheme: "bearer",
4446
bearerFormat: "Console Session Token",
4547
},
48+
bridge_client_session: {
49+
type: "http",
50+
scheme: "bearer",
51+
bearerFormat: "Bridge Client Session Token",
52+
},
4653
api_key: {
4754
type: "http",
4855
scheme: "bearer",
@@ -53,6 +60,11 @@ export const withRouteSpec = createWithRouteSpec({
5360
in: "header",
5461
name: "seam-publishable-key",
5562
},
63+
certified_client: {
64+
type: "http",
65+
scheme: "bearer",
66+
bearerFormat: "Certified Client Token",
67+
},
5668
},
5769

5870
authMiddlewareMap: {
@@ -66,8 +78,10 @@ export const withRouteSpec = createWithRouteSpec({
6678
console_session_without_workspace: withSessionAuth({
6779
is_workspace_id_required: false,
6880
}),
81+
bridge_client_session: withBridgeClientSession,
6982
api_key: withApiKey,
7083
client_session: withClientSession,
7184
publishable_key: withPublishableKey,
85+
certified_client: withCertifiedClient,
7286
},
7387
} as const)

src/lib/zod/bridge_client_session.ts

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { z } from "zod"
2+
3+
export const bridge_client_session = z.object({
4+
created_at: z.string().datetime(),
5+
bridge_client_session_id: z.string(),
6+
bridge_client_session_token: z.string(),
7+
pairing_code: z.string().length(6),
8+
pairing_code_expires_at: z.string().datetime(),
9+
tailscale_hostname: z.string(),
10+
tailscale_auth_key: z.array(z.string()),
11+
bridge_client_name: z.string(),
12+
bridge_client_time_zone: z.string(),
13+
bridge_client_machine_identifier_key: z.string(),
14+
})
15+
16+
export type BridgeClientSession = z.infer<typeof bridge_client_session>

src/lib/zod/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export * from "./access_token.ts"
33
export * from "./acs/index.ts"
44
export * from "./action_attempt.ts"
55
export * from "./api_key.ts"
6+
export * from "./bridge_client_session.ts"
67
export * from "./client_session.ts"
78
export * from "./common.ts"
89
export * from "./connect_webview.ts"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { z } from "zod"
2+
3+
import { withRouteSpec } from "lib/middleware/index.ts"
4+
import { bridge_client_session } from "lib/zod/index.ts"
5+
6+
export default withRouteSpec({
7+
description: `
8+
---
9+
title: Create a Bridge Client Session
10+
response_key: bridge_client_session
11+
undocumented: Seam Bridge Client only.
12+
---
13+
Creates a new bridge client session.
14+
`,
15+
auth: "certified_client",
16+
methods: ["POST"],
17+
jsonBody: z.object({
18+
bridge_client_name: z.string(),
19+
bridge_client_time_zone: z.string(),
20+
bridge_client_machine_identifier_key: z.string(),
21+
}),
22+
jsonResponse: z.object({
23+
bridge_client_session,
24+
}),
25+
} as const)(async (req, res) => {
26+
const { db, body } = req
27+
const {
28+
bridge_client_name,
29+
bridge_client_time_zone,
30+
bridge_client_machine_identifier_key,
31+
} = body
32+
33+
const bridge_client_session_token = `${bridge_client_name}_token`
34+
const pairing_code = "123456"
35+
36+
const pairing_code_expires_at = new Date(
37+
new Date().getTime() + 3 * 60 * 1000,
38+
).toISOString()
39+
40+
const bridge_client_session = db.addBridgeClientSession({
41+
bridge_client_name,
42+
bridge_client_time_zone,
43+
bridge_client_machine_identifier_key,
44+
bridge_client_session_token,
45+
pairing_code,
46+
pairing_code_expires_at,
47+
})
48+
49+
res.json({
50+
bridge_client_session,
51+
})
52+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { NotFoundException } from "nextlove"
2+
import { z } from "zod"
3+
4+
import { withRouteSpec } from "lib/middleware/index.ts"
5+
import { bridge_client_session } from "lib/zod/index.ts"
6+
7+
export default withRouteSpec({
8+
description: `
9+
---
10+
title: Get a Bridge Client Session
11+
response_key: bridge_client_session
12+
undocumented: Seam Bridge Client only.
13+
---
14+
Returns the bridge client session associated with the session token used.
15+
`,
16+
auth: ["bridge_client_session"],
17+
methods: ["GET", "POST"],
18+
jsonResponse: z.object({
19+
bridge_client_session,
20+
}),
21+
} as const)(async (req, res) => {
22+
const { db, auth } = req
23+
24+
const bridge_client_session = db.bridge_client_sessions.find(
25+
(bridge_client_session) =>
26+
bridge_client_session.bridge_client_session_id ===
27+
auth.bridge_client_session.bridge_client_session_id,
28+
)
29+
30+
if (bridge_client_session == null) {
31+
throw new NotFoundException({
32+
type: "bridge_client_session_not_found",
33+
message: "Bridge client session not found",
34+
})
35+
}
36+
37+
res.status(200).json({ bridge_client_session })
38+
})

0 commit comments

Comments
 (0)