@kispi/chat 0.1.2 → 0.2.0

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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  errorFrom
3
- } from "../chunk-T3QOUFOM.js";
3
+ } from "../chunk-5QQRM7BH.js";
4
4
 
5
5
  // src/server/api.ts
6
6
  var ServerRest = class {
@@ -36,12 +36,15 @@ var ServerRest = class {
36
36
  }
37
37
  };
38
38
 
39
+ // src/server/guest.ts
40
+ import { createHmac as createHmac2, randomBytes } from "crypto";
41
+
39
42
  // src/server/jwt.ts
40
43
  import { createHmac, timingSafeEqual } from "crypto";
41
44
  function signHS256(header, claims, secret) {
42
45
  const signingInput = `${b64url(JSON.stringify(header))}.${b64url(JSON.stringify(claims))}`;
43
- const mac = createHmac("sha256", secret).update(signingInput).digest();
44
- return `${signingInput}.${mac.toString("base64url")}`;
46
+ const mac2 = createHmac("sha256", secret).update(signingInput).digest();
47
+ return `${signingInput}.${mac2.toString("base64url")}`;
45
48
  }
46
49
  function macMatches(expected, given) {
47
50
  return expected.length === given.length && timingSafeEqual(expected, given);
@@ -50,6 +53,32 @@ function b64url(s) {
50
53
  return Buffer.from(s, "utf8").toString("base64url");
51
54
  }
52
55
 
56
+ // src/server/guest.ts
57
+ var GuestIdPrefix = "g_";
58
+ var CredentialVersion = "g1";
59
+ var MacLabel = "kispi-chat/guest/v1\n";
60
+ var MinGuestSecretLength = 32;
61
+ function newGuestId() {
62
+ return `${GuestIdPrefix}${randomBytes(16).toString("base64url")}`;
63
+ }
64
+ function signGuestCredential(secret, userId) {
65
+ return `${CredentialVersion}.${userId}.${mac(secret, userId).toString("base64url")}`;
66
+ }
67
+ function verifyGuestCredential(secrets, credential) {
68
+ const parts = credential.split(".");
69
+ if (parts.length !== 3) return void 0;
70
+ const [version, userId, given] = parts;
71
+ if (version !== CredentialVersion || !userId.startsWith(GuestIdPrefix)) return void 0;
72
+ const givenMac = Buffer.from(given, "base64url");
73
+ for (const [i, secret] of secrets.entries()) {
74
+ if (macMatches(mac(secret, userId), givenMac)) return { userId, secretIndex: i };
75
+ }
76
+ return void 0;
77
+ }
78
+ function mac(secret, userId) {
79
+ return createHmac2("sha256", secret).update(MacLabel + userId).digest();
80
+ }
81
+
53
82
  // src/server/token.ts
54
83
  var MaxTokenTtlSeconds = 24 * 60 * 60;
55
84
  var DefaultTokenTtlSeconds = 60 * 60;
@@ -81,7 +110,7 @@ function tokenClaims(input, now) {
81
110
  }
82
111
 
83
112
  // src/server/webhooks.ts
84
- import { createHmac as createHmac2 } from "crypto";
113
+ import { createHmac as createHmac3 } from "crypto";
85
114
  var SignatureToleranceMs = 5 * 60 * 1e3;
86
115
  var EventBeforePublish = "before_publish";
87
116
  function verifyWebhook(secret, headers, rawBody, options = {}) {
@@ -119,7 +148,7 @@ function verifyWebhook(secret, headers, rawBody, options = {}) {
119
148
  if (age > tolerance) {
120
149
  throw new Error(`webhook: signature is ${Math.round(age / 1e3)}s old, tolerance is ${tolerance / 1e3}s`);
121
150
  }
122
- const expected = createHmac2("sha256", secret).update(`${timestamp}
151
+ const expected = createHmac3("sha256", secret).update(`${timestamp}
123
152
  ${event}
124
153
  ${delivery}
125
154
  ${rawBody}`).digest();
@@ -153,13 +182,29 @@ function createChatServer(options) {
153
182
  throw new TypeError("keyId is required: it becomes the token's kid, which tells the server which key signed it");
154
183
  }
155
184
  const rest = new ServerRest(options.url, options.secretKey, options.fetch ?? globalThis.fetch.bind(globalThis));
185
+ const guestSecrets = options.guestSecret === void 0 ? [] : typeof options.guestSecret === "string" ? [options.guestSecret] : options.guestSecret;
186
+ for (const secret of guestSecrets) {
187
+ if (secret.length < MinGuestSecretLength) {
188
+ throw new RangeError(`guestSecret must be at least ${MinGuestSecretLength} characters`);
189
+ }
190
+ }
191
+ const sign = (input) => signHS256({ alg: "HS256", typ: "JWT", kid: options.keyId }, tokenClaims(input, /* @__PURE__ */ new Date()), options.secretKey);
156
192
  return {
157
- token(input) {
158
- return signHS256(
159
- { alg: "HS256", typ: "JWT", kid: options.keyId },
160
- tokenClaims(input, /* @__PURE__ */ new Date()),
161
- options.secretKey
162
- );
193
+ token: sign,
194
+ guest(input) {
195
+ const signer = guestSecrets[0];
196
+ if (signer === void 0) {
197
+ throw new TypeError("guestSecret was not given to createChatServer, so guest credentials cannot be signed");
198
+ }
199
+ const { credential, ...claims } = input;
200
+ const verified = credential ? verifyGuestCredential(guestSecrets, credential) : void 0;
201
+ const userId = verified?.userId ?? newGuestId();
202
+ return {
203
+ userId,
204
+ credential: verified !== void 0 && verified.secretIndex === 0 ? credential : signGuestCredential(signer, userId),
205
+ token: sign({ ...claims, userId }),
206
+ created: verified === void 0
207
+ };
163
208
  },
164
209
  rooms: {
165
210
  ensure: (input) => rest.call("PUT", "/v1/rooms", input),
@@ -250,6 +295,7 @@ function createChatServer(options) {
250
295
  }
251
296
  export {
252
297
  DefaultTokenTtlSeconds,
298
+ GuestIdPrefix,
253
299
  MaxTokenTtlSeconds,
254
300
  SignatureToleranceMs,
255
301
  createChatServer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kispi/chat",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Client SDK for the chat server: one WebSocket, many rooms, ordered history.",
5
5
  "license": "MIT",
6
6
  "type": "module",