@ifc-lite/collab-server 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bin.js +102 -2
- package/dist/bin.js.map +1 -1
- package/dist/blob-route.d.ts +24 -0
- package/dist/blob-route.d.ts.map +1 -1
- package/dist/blob-route.js +82 -0
- package/dist/blob-route.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/room-manager.d.ts +16 -0
- package/dist/room-manager.d.ts.map +1 -1
- package/dist/room-manager.js +29 -0
- package/dist/room-manager.js.map +1 -1
- package/dist/room-token.d.ts +160 -0
- package/dist/room-token.d.ts.map +1 -0
- package/dist/room-token.js +387 -0
- package/dist/room-token.js.map +1 -0
- package/dist/server.d.ts +45 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +79 -0
- package/dist/server.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import type * as http from 'node:http';
|
|
2
|
+
import type { AuthenticateFn, Principal, Role } from './auth.js';
|
|
3
|
+
export interface RoomTokenClaims {
|
|
4
|
+
/** Room id this token grants access to. */
|
|
5
|
+
room: string;
|
|
6
|
+
/** Granted access role. */
|
|
7
|
+
role: Role;
|
|
8
|
+
/** Issued-at (seconds since epoch). */
|
|
9
|
+
iat: number;
|
|
10
|
+
/** Expiry (seconds since epoch). */
|
|
11
|
+
exp: number;
|
|
12
|
+
/** Unique token id — enables single-link revocation. */
|
|
13
|
+
jti: string;
|
|
14
|
+
/** Key id (for rotation). Mirrors the JWS header `kid`. */
|
|
15
|
+
kid?: string;
|
|
16
|
+
}
|
|
17
|
+
/** Resolve a secret, optionally by `kid`, for verification (key rotation). */
|
|
18
|
+
export type SecretResolver = string | ((kid: string | undefined) => string | undefined);
|
|
19
|
+
export interface SignRoomTokenOptions {
|
|
20
|
+
roomId: string;
|
|
21
|
+
role: Role;
|
|
22
|
+
/** Signing secret (must match the verifier's secret for this `kid`). */
|
|
23
|
+
secret: string;
|
|
24
|
+
/** Token lifetime in seconds (default 7 days). */
|
|
25
|
+
ttlSeconds?: number;
|
|
26
|
+
/** Key id written to the JWS header for rotation. */
|
|
27
|
+
kid?: string;
|
|
28
|
+
/** Override the unique token id (default: random UUID). */
|
|
29
|
+
jti?: string;
|
|
30
|
+
/** Injectable clock (ms) for deterministic tests. */
|
|
31
|
+
now?: () => number;
|
|
32
|
+
}
|
|
33
|
+
/** Mint a signed room token. */
|
|
34
|
+
export declare function signRoomToken(opts: SignRoomTokenOptions): string;
|
|
35
|
+
export interface VerifyRoomTokenOptions {
|
|
36
|
+
secret: SecretResolver;
|
|
37
|
+
/** When set, require the token's `room` claim to equal this value. */
|
|
38
|
+
room?: string;
|
|
39
|
+
/** Injectable clock (ms) for deterministic tests. */
|
|
40
|
+
now?: () => number;
|
|
41
|
+
/** Allowed clock skew in seconds (default 30). */
|
|
42
|
+
clockToleranceSec?: number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Verify a room token. Returns the decoded claims, or `null` for any failure
|
|
46
|
+
* (bad shape, wrong/unknown key, tampered signature, expired, room mismatch).
|
|
47
|
+
* Never throws on malformed input.
|
|
48
|
+
*/
|
|
49
|
+
export declare function verifyRoomToken(token: string, opts: VerifyRoomTokenOptions): RoomTokenClaims | null;
|
|
50
|
+
export interface RoomTokenAuthenticatorOptions {
|
|
51
|
+
secret: SecretResolver;
|
|
52
|
+
/** Deny-list check for revoked `jti`s ("revoke this link"). */
|
|
53
|
+
isRevoked?: (jti: string) => boolean | Promise<boolean>;
|
|
54
|
+
now?: () => number;
|
|
55
|
+
clockToleranceSec?: number;
|
|
56
|
+
/**
|
|
57
|
+
* Map verified claims to a `Principal`. Default: a fresh anonymous
|
|
58
|
+
* per-connection userId (the real identity lives in awareness) with the
|
|
59
|
+
* token's role, expiry, and `{ jti, room }` audit metadata.
|
|
60
|
+
*/
|
|
61
|
+
principalFor?: (claims: RoomTokenClaims) => Principal;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Build an `AuthenticateFn` that authorizes a websocket connection from a
|
|
65
|
+
* room token. Pass to `startCollabServer({ authenticate })`.
|
|
66
|
+
*/
|
|
67
|
+
export declare function createRoomTokenAuthenticator(opts: RoomTokenAuthenticatorOptions): AuthenticateFn;
|
|
68
|
+
export interface MintRequestBody {
|
|
69
|
+
roomId: string;
|
|
70
|
+
role: Role;
|
|
71
|
+
/** Requested lifetime in seconds (clamped by `maxTtlSeconds`). */
|
|
72
|
+
ttlSeconds?: number;
|
|
73
|
+
}
|
|
74
|
+
export interface TokenEndpointOptions {
|
|
75
|
+
/** Secret used to sign newly minted tokens. */
|
|
76
|
+
secret: string;
|
|
77
|
+
/**
|
|
78
|
+
* Authorize a mint request. Receives the requested grant and the verified
|
|
79
|
+
* claims of the caller's bearer token (or `null` if none/invalid). Return the
|
|
80
|
+
* role to actually grant, or `null` to deny (→ 403). This is where consumers
|
|
81
|
+
* encode policy — e.g. "only an `admin` token for this room may mint", or
|
|
82
|
+
* "anyone may create a fresh room and become its owner".
|
|
83
|
+
*/
|
|
84
|
+
authorize: (request: MintRequestBody, context: {
|
|
85
|
+
bearerClaims: RoomTokenClaims | null;
|
|
86
|
+
}) => Promise<Role | null> | Role | null;
|
|
87
|
+
/** Secret(s) used to verify the caller's bearer token (default: `secret`). */
|
|
88
|
+
verifySecret?: SecretResolver;
|
|
89
|
+
/**
|
|
90
|
+
* Deny-list check for revoked `jti`s. A revoked bearer (e.g. a kicked
|
|
91
|
+
* admin's token) must not keep minting links even though its signature +
|
|
92
|
+
* expiry still verify — when this returns true the bearer is treated as
|
|
93
|
+
* absent, so `authorize` sees `bearerClaims: null`.
|
|
94
|
+
*/
|
|
95
|
+
isRevoked?: (jti: string) => boolean | Promise<boolean>;
|
|
96
|
+
/** Lifetime for minted tokens when the request omits one (default 7 days). */
|
|
97
|
+
defaultTtlSeconds?: number;
|
|
98
|
+
/** Upper bound applied to a requested `ttlSeconds` (default 30 days). */
|
|
99
|
+
maxTtlSeconds?: number;
|
|
100
|
+
/** Key id stamped on minted tokens. */
|
|
101
|
+
kid?: string;
|
|
102
|
+
/** CORS `Access-Control-Allow-Origin` (default `*`). */
|
|
103
|
+
allowOrigin?: string;
|
|
104
|
+
/** Reject bodies larger than this (default 4 KB). */
|
|
105
|
+
maxBodyBytes?: number;
|
|
106
|
+
now?: () => number;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Handle `POST /collab/token` (and its CORS preflight). Returns `true` when the
|
|
110
|
+
* request matched this route (and a response was sent), `false` otherwise so
|
|
111
|
+
* the caller can fall through to its own 404.
|
|
112
|
+
*/
|
|
113
|
+
export declare function handleTokenMintRequest(req: http.IncomingMessage, res: http.ServerResponse, opts: TokenEndpointOptions): Promise<boolean>;
|
|
114
|
+
export interface RevokeRequestBody {
|
|
115
|
+
/** The share token to invalidate. */
|
|
116
|
+
token: string;
|
|
117
|
+
}
|
|
118
|
+
export interface RevokeEndpointOptions {
|
|
119
|
+
/** Secret used to verify the target + bearer tokens. */
|
|
120
|
+
secret: SecretResolver;
|
|
121
|
+
/** Add a `jti` to the server's deny-list. The authenticator's `isRevoked`
|
|
122
|
+
* should consult the same store so future joins with this token are rejected. */
|
|
123
|
+
recordRevocation: (jti: string, room: string) => void | Promise<void>;
|
|
124
|
+
/** Deny-list check — a bearer whose own `jti` was revoked (e.g. a kicked
|
|
125
|
+
* admin) must not be able to keep revoking other people's links. */
|
|
126
|
+
isRevoked?: (jti: string) => boolean | Promise<boolean>;
|
|
127
|
+
allowOrigin?: string;
|
|
128
|
+
maxBodyBytes?: number;
|
|
129
|
+
now?: () => number;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Handle `POST /collab/revoke` (and its CORS preflight). The caller must
|
|
133
|
+
* present an `admin` bearer token for the same room as the token being revoked.
|
|
134
|
+
* Returns `true` when this route matched (and a response was sent).
|
|
135
|
+
*/
|
|
136
|
+
export declare function handleRevokeRequest(req: http.IncomingMessage, res: http.ServerResponse, opts: RevokeEndpointOptions): Promise<boolean>;
|
|
137
|
+
export interface KickRequestBody {
|
|
138
|
+
roomId: string;
|
|
139
|
+
/** Awareness clientId of the peer to disconnect. */
|
|
140
|
+
clientId: number;
|
|
141
|
+
}
|
|
142
|
+
export interface KickEndpointOptions {
|
|
143
|
+
/** Secret used to verify the admin bearer token. */
|
|
144
|
+
secret: SecretResolver;
|
|
145
|
+
/** Force-disconnect a peer by awareness clientId; returns whether one matched. */
|
|
146
|
+
kick: (roomId: string, clientId: number) => boolean | Promise<boolean>;
|
|
147
|
+
/** Deny-list check — a bearer whose own `jti` was revoked (e.g. an admin who
|
|
148
|
+
* was themselves kicked) must not be able to keep kicking peers. */
|
|
149
|
+
isRevoked?: (jti: string) => boolean | Promise<boolean>;
|
|
150
|
+
allowOrigin?: string;
|
|
151
|
+
maxBodyBytes?: number;
|
|
152
|
+
now?: () => number;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Handle `POST /collab/kick` (and its CORS preflight). The caller must present
|
|
156
|
+
* an `admin` bearer token for the target room. Returns `true` when this route
|
|
157
|
+
* matched (and a response was sent).
|
|
158
|
+
*/
|
|
159
|
+
export declare function handleKickRequest(req: http.IncomingMessage, res: http.ServerResponse, opts: KickEndpointOptions): Promise<boolean>;
|
|
160
|
+
//# sourceMappingURL=room-token.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"room-token.d.ts","sourceRoot":"","sources":["../src/room-token.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
/**
|
|
5
|
+
* Room tokens — accountless, link-based access control (plan §3).
|
|
6
|
+
*
|
|
7
|
+
* Sharing is a signed capability, not an account: a short JWT (HS256, a
|
|
8
|
+
* server-held secret) carries the room id and the granted role
|
|
9
|
+
* (`viewer` / `commenter` / `editor` / `admin`). Anyone with the link
|
|
10
|
+
* presents the token as `?token=` on connect; the server verifies the
|
|
11
|
+
* signature + expiry and derives the `Principal.role` from it. The display
|
|
12
|
+
* identity (handle / color) travels separately over awareness — the token
|
|
13
|
+
* collects no PII.
|
|
14
|
+
*
|
|
15
|
+
* HS256 is implemented directly on `node:crypto` so the reference server
|
|
16
|
+
* needs no JWT dependency. `kid` supports key rotation ("revoke all links"
|
|
17
|
+
* rotates the active key); `jti` supports single-link revocation via an
|
|
18
|
+
* `isRevoked` deny-list.
|
|
19
|
+
*/
|
|
20
|
+
import { createHmac, randomUUID, timingSafeEqual } from 'node:crypto';
|
|
21
|
+
const VALID_ROLES = new Set([
|
|
22
|
+
'viewer',
|
|
23
|
+
'commenter',
|
|
24
|
+
'editor',
|
|
25
|
+
'admin',
|
|
26
|
+
]);
|
|
27
|
+
const DEFAULT_TTL_SECONDS = 7 * 24 * 60 * 60; // 7 days
|
|
28
|
+
const DEFAULT_CLOCK_TOLERANCE_SEC = 30;
|
|
29
|
+
function b64urlEncode(input) {
|
|
30
|
+
const buf = typeof input === 'string' ? Buffer.from(input, 'utf8') : input;
|
|
31
|
+
return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
32
|
+
}
|
|
33
|
+
function b64urlToBuffer(input) {
|
|
34
|
+
const b64 = input.replace(/-/g, '+').replace(/_/g, '/');
|
|
35
|
+
const pad = b64.length % 4 === 0 ? '' : '='.repeat(4 - (b64.length % 4));
|
|
36
|
+
return Buffer.from(b64 + pad, 'base64');
|
|
37
|
+
}
|
|
38
|
+
function hmac(signingInput, secret) {
|
|
39
|
+
return createHmac('sha256', secret).update(signingInput).digest();
|
|
40
|
+
}
|
|
41
|
+
function resolveSecret(secret, kid) {
|
|
42
|
+
return typeof secret === 'function' ? secret(kid) : secret;
|
|
43
|
+
}
|
|
44
|
+
/** Mint a signed room token. */
|
|
45
|
+
export function signRoomToken(opts) {
|
|
46
|
+
if (!VALID_ROLES.has(opts.role)) {
|
|
47
|
+
throw new Error(`signRoomToken: invalid role "${opts.role}"`);
|
|
48
|
+
}
|
|
49
|
+
if (!opts.roomId)
|
|
50
|
+
throw new Error('signRoomToken: roomId is required');
|
|
51
|
+
if (!opts.secret)
|
|
52
|
+
throw new Error('signRoomToken: secret is required');
|
|
53
|
+
const nowSec = Math.floor((opts.now ? opts.now() : Date.now()) / 1000);
|
|
54
|
+
const claims = {
|
|
55
|
+
room: opts.roomId,
|
|
56
|
+
role: opts.role,
|
|
57
|
+
iat: nowSec,
|
|
58
|
+
exp: nowSec + (opts.ttlSeconds ?? DEFAULT_TTL_SECONDS),
|
|
59
|
+
jti: opts.jti ?? randomUUID(),
|
|
60
|
+
...(opts.kid ? { kid: opts.kid } : {}),
|
|
61
|
+
};
|
|
62
|
+
const header = { alg: 'HS256', typ: 'JWT', ...(opts.kid ? { kid: opts.kid } : {}) };
|
|
63
|
+
const signingInput = `${b64urlEncode(JSON.stringify(header))}.${b64urlEncode(JSON.stringify(claims))}`;
|
|
64
|
+
const sig = b64urlEncode(hmac(signingInput, opts.secret));
|
|
65
|
+
return `${signingInput}.${sig}`;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Verify a room token. Returns the decoded claims, or `null` for any failure
|
|
69
|
+
* (bad shape, wrong/unknown key, tampered signature, expired, room mismatch).
|
|
70
|
+
* Never throws on malformed input.
|
|
71
|
+
*/
|
|
72
|
+
export function verifyRoomToken(token, opts) {
|
|
73
|
+
if (typeof token !== 'string' || token.length === 0)
|
|
74
|
+
return null;
|
|
75
|
+
const parts = token.split('.');
|
|
76
|
+
if (parts.length !== 3)
|
|
77
|
+
return null;
|
|
78
|
+
const [headerB64, payloadB64, sigB64] = parts;
|
|
79
|
+
let header;
|
|
80
|
+
let claims;
|
|
81
|
+
try {
|
|
82
|
+
header = JSON.parse(b64urlToBuffer(headerB64).toString('utf8'));
|
|
83
|
+
claims = JSON.parse(b64urlToBuffer(payloadB64).toString('utf8'));
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
if (header.alg !== 'HS256')
|
|
89
|
+
return null;
|
|
90
|
+
const kid = typeof header.kid === 'string' ? header.kid : undefined;
|
|
91
|
+
const secret = resolveSecret(opts.secret, kid);
|
|
92
|
+
if (!secret)
|
|
93
|
+
return null;
|
|
94
|
+
// Constant-time signature comparison.
|
|
95
|
+
const expected = hmac(`${headerB64}.${payloadB64}`, secret);
|
|
96
|
+
const provided = b64urlToBuffer(sigB64);
|
|
97
|
+
if (provided.length !== expected.length || !timingSafeEqual(provided, expected)) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
// Structural + role validation.
|
|
101
|
+
if (typeof claims.room !== 'string' ||
|
|
102
|
+
typeof claims.exp !== 'number' ||
|
|
103
|
+
typeof claims.iat !== 'number' ||
|
|
104
|
+
typeof claims.jti !== 'string' ||
|
|
105
|
+
!VALID_ROLES.has(claims.role)) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
const nowSec = Math.floor((opts.now ? opts.now() : Date.now()) / 1000);
|
|
109
|
+
const tolerance = opts.clockToleranceSec ?? DEFAULT_CLOCK_TOLERANCE_SEC;
|
|
110
|
+
if (claims.exp + tolerance < nowSec)
|
|
111
|
+
return null; // expired
|
|
112
|
+
if (claims.iat - tolerance > nowSec)
|
|
113
|
+
return null; // issued in the future
|
|
114
|
+
if (opts.room !== undefined && claims.room !== opts.room)
|
|
115
|
+
return null;
|
|
116
|
+
return claims;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Build an `AuthenticateFn` that authorizes a websocket connection from a
|
|
120
|
+
* room token. Pass to `startCollabServer({ authenticate })`.
|
|
121
|
+
*/
|
|
122
|
+
export function createRoomTokenAuthenticator(opts) {
|
|
123
|
+
return async (token, roomId) => {
|
|
124
|
+
const claims = verifyRoomToken(token ?? '', {
|
|
125
|
+
secret: opts.secret,
|
|
126
|
+
room: roomId,
|
|
127
|
+
now: opts.now,
|
|
128
|
+
clockToleranceSec: opts.clockToleranceSec,
|
|
129
|
+
});
|
|
130
|
+
if (!claims)
|
|
131
|
+
return null;
|
|
132
|
+
if (opts.isRevoked && (await opts.isRevoked(claims.jti)))
|
|
133
|
+
return null;
|
|
134
|
+
if (opts.principalFor)
|
|
135
|
+
return opts.principalFor(claims);
|
|
136
|
+
return {
|
|
137
|
+
userId: `anon-${randomUUID()}`,
|
|
138
|
+
role: claims.role,
|
|
139
|
+
expiresAt: claims.exp * 1000,
|
|
140
|
+
meta: { jti: claims.jti, room: claims.room },
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function readJsonBody(req, maxBytes) {
|
|
145
|
+
return new Promise((resolve, reject) => {
|
|
146
|
+
const chunks = [];
|
|
147
|
+
let size = 0;
|
|
148
|
+
req.on('data', (chunk) => {
|
|
149
|
+
size += chunk.length;
|
|
150
|
+
if (size > maxBytes) {
|
|
151
|
+
reject(new Error('body-too-large'));
|
|
152
|
+
req.destroy();
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
chunks.push(chunk);
|
|
156
|
+
});
|
|
157
|
+
req.on('end', () => {
|
|
158
|
+
try {
|
|
159
|
+
resolve(JSON.parse(Buffer.concat(chunks).toString('utf8')));
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
reject(new Error('invalid-json'));
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
req.on('error', reject);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
function bearerToken(req) {
|
|
169
|
+
const header = req.headers['authorization'];
|
|
170
|
+
if (typeof header !== 'string')
|
|
171
|
+
return undefined;
|
|
172
|
+
const m = header.match(/^Bearer\s+(.+)$/i);
|
|
173
|
+
return m ? m[1] : undefined;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Handle `POST /collab/token` (and its CORS preflight). Returns `true` when the
|
|
177
|
+
* request matched this route (and a response was sent), `false` otherwise so
|
|
178
|
+
* the caller can fall through to its own 404.
|
|
179
|
+
*/
|
|
180
|
+
export async function handleTokenMintRequest(req, res, opts) {
|
|
181
|
+
const url = new URL(req.url ?? '/', 'http://localhost');
|
|
182
|
+
if (url.pathname !== '/collab/token')
|
|
183
|
+
return false;
|
|
184
|
+
const allowOrigin = opts.allowOrigin ?? '*';
|
|
185
|
+
const cors = {
|
|
186
|
+
'access-control-allow-origin': allowOrigin,
|
|
187
|
+
'access-control-allow-methods': 'POST, OPTIONS',
|
|
188
|
+
'access-control-allow-headers': 'content-type, authorization',
|
|
189
|
+
};
|
|
190
|
+
if (req.method === 'OPTIONS') {
|
|
191
|
+
res.writeHead(204, cors);
|
|
192
|
+
res.end();
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
if (req.method !== 'POST') {
|
|
196
|
+
res.writeHead(405, { ...cors, 'content-type': 'application/json' });
|
|
197
|
+
res.end(JSON.stringify({ error: 'method-not-allowed' }));
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
let body;
|
|
201
|
+
try {
|
|
202
|
+
body = await readJsonBody(req, opts.maxBodyBytes ?? 4096);
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
const reason = err instanceof Error ? err.message : 'bad-request';
|
|
206
|
+
res.writeHead(reason === 'body-too-large' ? 413 : 400, {
|
|
207
|
+
...cors,
|
|
208
|
+
'content-type': 'application/json',
|
|
209
|
+
});
|
|
210
|
+
res.end(JSON.stringify({ error: reason }));
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
const reqBody = body;
|
|
214
|
+
if (typeof reqBody?.roomId !== 'string' ||
|
|
215
|
+
!reqBody.roomId ||
|
|
216
|
+
typeof reqBody.role !== 'string' ||
|
|
217
|
+
!VALID_ROLES.has(reqBody.role)) {
|
|
218
|
+
res.writeHead(400, { ...cors, 'content-type': 'application/json' });
|
|
219
|
+
res.end(JSON.stringify({ error: 'invalid-request' }));
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
const mintReq = {
|
|
223
|
+
roomId: reqBody.roomId,
|
|
224
|
+
role: reqBody.role,
|
|
225
|
+
ttlSeconds: typeof reqBody.ttlSeconds === 'number' ? reqBody.ttlSeconds : undefined,
|
|
226
|
+
};
|
|
227
|
+
let bearerClaims = verifyRoomToken(bearerToken(req) ?? '', {
|
|
228
|
+
secret: opts.verifySecret ?? opts.secret,
|
|
229
|
+
now: opts.now,
|
|
230
|
+
});
|
|
231
|
+
// A revoked bearer (kicked admin / revoked link) is treated as no bearer at
|
|
232
|
+
// all, so the mint policy can't be exercised with a denied credential.
|
|
233
|
+
if (bearerClaims && opts.isRevoked && (await opts.isRevoked(bearerClaims.jti))) {
|
|
234
|
+
bearerClaims = null;
|
|
235
|
+
}
|
|
236
|
+
let grantedRole;
|
|
237
|
+
try {
|
|
238
|
+
grantedRole = await opts.authorize(mintReq, { bearerClaims });
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
// eslint-disable-next-line no-console
|
|
242
|
+
console.error('[collab-server] token authorize threw:', err);
|
|
243
|
+
res.writeHead(500, { ...cors, 'content-type': 'application/json' });
|
|
244
|
+
res.end(JSON.stringify({ error: 'authorize-error' }));
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
if (!grantedRole) {
|
|
248
|
+
res.writeHead(403, { ...cors, 'content-type': 'application/json' });
|
|
249
|
+
res.end(JSON.stringify({ error: 'forbidden' }));
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
const maxTtl = opts.maxTtlSeconds ?? 30 * 24 * 60 * 60;
|
|
253
|
+
const ttlSeconds = Math.min(mintReq.ttlSeconds ?? opts.defaultTtlSeconds ?? DEFAULT_TTL_SECONDS, maxTtl);
|
|
254
|
+
const token = signRoomToken({
|
|
255
|
+
roomId: mintReq.roomId,
|
|
256
|
+
role: grantedRole,
|
|
257
|
+
secret: opts.secret,
|
|
258
|
+
ttlSeconds,
|
|
259
|
+
kid: opts.kid,
|
|
260
|
+
now: opts.now,
|
|
261
|
+
});
|
|
262
|
+
const expSec = Math.floor((opts.now ? opts.now() : Date.now()) / 1000) + ttlSeconds;
|
|
263
|
+
res.writeHead(200, { ...cors, 'content-type': 'application/json' });
|
|
264
|
+
res.end(JSON.stringify({ token, roomId: mintReq.roomId, role: grantedRole, exp: expSec }));
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Handle `POST /collab/revoke` (and its CORS preflight). The caller must
|
|
269
|
+
* present an `admin` bearer token for the same room as the token being revoked.
|
|
270
|
+
* Returns `true` when this route matched (and a response was sent).
|
|
271
|
+
*/
|
|
272
|
+
export async function handleRevokeRequest(req, res, opts) {
|
|
273
|
+
const url = new URL(req.url ?? '/', 'http://localhost');
|
|
274
|
+
if (url.pathname !== '/collab/revoke')
|
|
275
|
+
return false;
|
|
276
|
+
const allowOrigin = opts.allowOrigin ?? '*';
|
|
277
|
+
const cors = {
|
|
278
|
+
'access-control-allow-origin': allowOrigin,
|
|
279
|
+
'access-control-allow-methods': 'POST, OPTIONS',
|
|
280
|
+
'access-control-allow-headers': 'content-type, authorization',
|
|
281
|
+
};
|
|
282
|
+
if (req.method === 'OPTIONS') {
|
|
283
|
+
res.writeHead(204, cors);
|
|
284
|
+
res.end();
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
if (req.method !== 'POST') {
|
|
288
|
+
res.writeHead(405, { ...cors, 'content-type': 'application/json' });
|
|
289
|
+
res.end(JSON.stringify({ error: 'method-not-allowed' }));
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
let body;
|
|
293
|
+
try {
|
|
294
|
+
body = await readJsonBody(req, opts.maxBodyBytes ?? 4096);
|
|
295
|
+
}
|
|
296
|
+
catch (err) {
|
|
297
|
+
const reason = err instanceof Error ? err.message : 'bad-request';
|
|
298
|
+
res.writeHead(reason === 'body-too-large' ? 413 : 400, { ...cors, 'content-type': 'application/json' });
|
|
299
|
+
res.end(JSON.stringify({ error: reason }));
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
const target = verifyRoomToken(body?.token ?? '', {
|
|
303
|
+
secret: opts.secret,
|
|
304
|
+
now: opts.now,
|
|
305
|
+
});
|
|
306
|
+
if (!target) {
|
|
307
|
+
res.writeHead(400, { ...cors, 'content-type': 'application/json' });
|
|
308
|
+
res.end(JSON.stringify({ error: 'invalid-token' }));
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
// Only a non-revoked admin token for the *same room* may revoke.
|
|
312
|
+
const bearer = verifyRoomToken(bearerToken(req) ?? '', {
|
|
313
|
+
secret: opts.secret,
|
|
314
|
+
room: target.room,
|
|
315
|
+
now: opts.now,
|
|
316
|
+
});
|
|
317
|
+
if (!bearer ||
|
|
318
|
+
bearer.role !== 'admin' ||
|
|
319
|
+
(opts.isRevoked && (await opts.isRevoked(bearer.jti)))) {
|
|
320
|
+
res.writeHead(403, { ...cors, 'content-type': 'application/json' });
|
|
321
|
+
res.end(JSON.stringify({ error: 'forbidden' }));
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
await opts.recordRevocation(target.jti, target.room);
|
|
325
|
+
res.writeHead(200, { ...cors, 'content-type': 'application/json' });
|
|
326
|
+
res.end(JSON.stringify({ revoked: true, jti: target.jti }));
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Handle `POST /collab/kick` (and its CORS preflight). The caller must present
|
|
331
|
+
* an `admin` bearer token for the target room. Returns `true` when this route
|
|
332
|
+
* matched (and a response was sent).
|
|
333
|
+
*/
|
|
334
|
+
export async function handleKickRequest(req, res, opts) {
|
|
335
|
+
const url = new URL(req.url ?? '/', 'http://localhost');
|
|
336
|
+
if (url.pathname !== '/collab/kick')
|
|
337
|
+
return false;
|
|
338
|
+
const allowOrigin = opts.allowOrigin ?? '*';
|
|
339
|
+
const cors = {
|
|
340
|
+
'access-control-allow-origin': allowOrigin,
|
|
341
|
+
'access-control-allow-methods': 'POST, OPTIONS',
|
|
342
|
+
'access-control-allow-headers': 'content-type, authorization',
|
|
343
|
+
};
|
|
344
|
+
if (req.method === 'OPTIONS') {
|
|
345
|
+
res.writeHead(204, cors);
|
|
346
|
+
res.end();
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
if (req.method !== 'POST') {
|
|
350
|
+
res.writeHead(405, { ...cors, 'content-type': 'application/json' });
|
|
351
|
+
res.end(JSON.stringify({ error: 'method-not-allowed' }));
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
let body;
|
|
355
|
+
try {
|
|
356
|
+
body = await readJsonBody(req, opts.maxBodyBytes ?? 4096);
|
|
357
|
+
}
|
|
358
|
+
catch (err) {
|
|
359
|
+
const reason = err instanceof Error ? err.message : 'bad-request';
|
|
360
|
+
res.writeHead(reason === 'body-too-large' ? 413 : 400, { ...cors, 'content-type': 'application/json' });
|
|
361
|
+
res.end(JSON.stringify({ error: reason }));
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
364
|
+
const reqBody = body;
|
|
365
|
+
if (typeof reqBody?.roomId !== 'string' || !reqBody.roomId || typeof reqBody.clientId !== 'number') {
|
|
366
|
+
res.writeHead(400, { ...cors, 'content-type': 'application/json' });
|
|
367
|
+
res.end(JSON.stringify({ error: 'invalid-request' }));
|
|
368
|
+
return true;
|
|
369
|
+
}
|
|
370
|
+
const bearer = verifyRoomToken(bearerToken(req) ?? '', {
|
|
371
|
+
secret: opts.secret,
|
|
372
|
+
room: reqBody.roomId,
|
|
373
|
+
now: opts.now,
|
|
374
|
+
});
|
|
375
|
+
if (!bearer ||
|
|
376
|
+
bearer.role !== 'admin' ||
|
|
377
|
+
(opts.isRevoked && (await opts.isRevoked(bearer.jti)))) {
|
|
378
|
+
res.writeHead(403, { ...cors, 'content-type': 'application/json' });
|
|
379
|
+
res.end(JSON.stringify({ error: 'forbidden' }));
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
const kicked = await opts.kick(reqBody.roomId, reqBody.clientId);
|
|
383
|
+
res.writeHead(200, { ...cors, 'content-type': 'application/json' });
|
|
384
|
+
res.end(JSON.stringify({ kicked }));
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
//# sourceMappingURL=room-token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"room-token.js","sourceRoot":"","sources":["../src/room-token.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
CHANGED
|
@@ -10,7 +10,25 @@ import { type AuditSink } from './audit-log.js';
|
|
|
10
10
|
import { type RateLimitOptions } from './rate-limit.js';
|
|
11
11
|
import { type VerifyMessageFn } from './room-manager.js';
|
|
12
12
|
import { type BlobAuthorizeFn, type ServerBlobStorage } from './blob-route.js';
|
|
13
|
+
import { type TokenEndpointOptions, type RevokeEndpointOptions, type KickEndpointOptions } from './room-token.js';
|
|
13
14
|
import { MetricsRegistry } from './metrics.js';
|
|
15
|
+
/**
|
|
16
|
+
* Cross-origin policy for the HTTP routes (`/blobs`, `/collab/*`, `/healthz`,
|
|
17
|
+
* `/metrics`). The viewer is typically served from a different origin than the
|
|
18
|
+
* collab-server, so a browser `fetch()`/`PUT` to the blob route needs
|
|
19
|
+
* `Access-Control-Allow-*` headers and an `OPTIONS` preflight response —
|
|
20
|
+
* without them the WebSocket doc syncs but geometry blobs are blocked, so
|
|
21
|
+
* recipients see an empty model.
|
|
22
|
+
*/
|
|
23
|
+
export interface CorsOptions {
|
|
24
|
+
/**
|
|
25
|
+
* Allowed origin(s). `'*'` or omitted reflects whatever `Origin` the
|
|
26
|
+
* request carries (permissive — fine for dev and same-trust deployments).
|
|
27
|
+
* An explicit string or array restricts to those origins; a non-matching
|
|
28
|
+
* origin gets no CORS headers (and the browser blocks the request).
|
|
29
|
+
*/
|
|
30
|
+
origin?: '*' | string | string[];
|
|
31
|
+
}
|
|
14
32
|
export interface StartCollabServerOptions {
|
|
15
33
|
port?: number;
|
|
16
34
|
host?: string;
|
|
@@ -62,6 +80,33 @@ export interface StartCollabServerOptions {
|
|
|
62
80
|
* `reject` with the reason and drops the message.
|
|
63
81
|
*/
|
|
64
82
|
verifyMessage?: VerifyMessageFn;
|
|
83
|
+
/**
|
|
84
|
+
* Enable the `POST /collab/token` mint route for link-based sharing.
|
|
85
|
+
* Omit to leave the route disabled (404). Pair with
|
|
86
|
+
* `authenticate: createRoomTokenAuthenticator({ secret })` so connections
|
|
87
|
+
* are verified against the same secret.
|
|
88
|
+
*/
|
|
89
|
+
tokenEndpoint?: TokenEndpointOptions;
|
|
90
|
+
/**
|
|
91
|
+
* Enable the `POST /collab/revoke` route so an admin can invalidate a share
|
|
92
|
+
* link (its `jti` is added to a deny-list the authenticator's `isRevoked`
|
|
93
|
+
* consults). Omit to leave the route disabled (404).
|
|
94
|
+
*/
|
|
95
|
+
revokeEndpoint?: RevokeEndpointOptions;
|
|
96
|
+
/**
|
|
97
|
+
* Enable the `POST /collab/kick` route so an admin can force-disconnect a
|
|
98
|
+
* peer by awareness clientId. The server binds the kick to its room manager;
|
|
99
|
+
* only the verifying `secret` is supplied here. Omit to disable (404).
|
|
100
|
+
*/
|
|
101
|
+
kickEndpoint?: Pick<KickEndpointOptions, 'secret' | 'isRevoked'>;
|
|
102
|
+
/**
|
|
103
|
+
* Cross-origin access for the HTTP routes. Default: enabled with origin
|
|
104
|
+
* reflection (permissive). Pass an allow-list to restrict, or `false` to
|
|
105
|
+
* disable entirely (e.g. when a reverse proxy owns CORS). Only applies to
|
|
106
|
+
* the server this function creates — ignored when you pass your own
|
|
107
|
+
* `server`.
|
|
108
|
+
*/
|
|
109
|
+
cors?: CorsOptions | false;
|
|
65
110
|
}
|
|
66
111
|
export interface CollabServerHandle {
|
|
67
112
|
readonly url: string;
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAIA;;GAEG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,eAAe,EAAkB,MAAM,IAAI,CAAC;AACrD,OAAO,EAAE,WAAW,EAAuB,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAsC,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACxF,OAAO,EAAkC,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,WAAW,CAAC;AAChG,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAkB,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/D,MAAM,WAAW,wBAAwB;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,YAAY,CAAC,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC;IACrB,wDAAwD;IACxD,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,qEAAqE;IACrE,SAAS,CAAC,EAAE,gBAAgB,GAAG,CAAC,CAAC,SAAS,EAAE,SAAS,KAAK,gBAAgB,CAAC,CAAC;IAC5E;;;;OAIG;IACH,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,yDAAyD;IACzD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IACvC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oFAAoF;IACpF,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B;;;;OAIG;IACH,aAAa,CAAC,EAAE,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|