@korajs/auth 0.3.3 → 0.5.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.
- package/README.md +81 -28
- package/dist/{chunk-FSU4SK32.js → chunk-IO2MCCG2.js} +35 -40
- package/dist/chunk-IO2MCCG2.js.map +1 -0
- package/dist/{chunk-HOZXDR6Y.js → chunk-L7GXPS74.js} +3 -9
- package/dist/chunk-L7GXPS74.js.map +1 -0
- package/dist/index.cjs +978 -606
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +77 -118
- package/dist/index.d.ts +77 -118
- package/dist/index.js +686 -313
- package/dist/index.js.map +1 -1
- package/dist/org-client-q2u55qod.d.cts +665 -0
- package/dist/org-client-q2u55qod.d.ts +665 -0
- package/dist/{password-hash-HDH6VQCQ.js → password-hash-QRBG6BNJ.js} +2 -2
- package/dist/react.cjs +79 -0
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +17 -1
- package/dist/react.d.ts +17 -1
- package/dist/react.js +79 -0
- package/dist/react.js.map +1 -1
- package/dist/server.cjs +2524 -1672
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +415 -224
- package/dist/server.d.ts +415 -224
- package/dist/server.js +2325 -1484
- package/dist/server.js.map +1 -1
- package/package.json +5 -5
- package/dist/chunk-FSU4SK32.js.map +0 -1
- package/dist/chunk-HOZXDR6Y.js.map +0 -1
- package/dist/org-client-BVTLKcIk.d.cts +0 -355
- package/dist/org-client-BVTLKcIk.d.ts +0 -355
- /package/dist/{password-hash-HDH6VQCQ.js.map → password-hash-QRBG6BNJ.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
AuthClient: () => AuthClient,
|
|
24
|
+
AuthDeviceIdentityError: () => AuthDeviceIdentityError,
|
|
24
25
|
AuthError: () => AuthError,
|
|
25
26
|
AutoLockManager: () => AutoLockManager,
|
|
26
27
|
CryptoUnavailableError: () => CryptoUnavailableError,
|
|
@@ -41,8 +42,14 @@ __export(index_exports, {
|
|
|
41
42
|
TokenStore: () => TokenStore,
|
|
42
43
|
authenticateWithPasskey: () => authenticateWithPasskey,
|
|
43
44
|
computePublicKeyThumbprint: () => computePublicKeyThumbprint,
|
|
45
|
+
createAuthTokenStorage: () => createAuthTokenStorage,
|
|
44
46
|
createDeviceKeyStore: () => createDeviceKeyStore,
|
|
47
|
+
createKoraAuth: () => createKoraAuth,
|
|
48
|
+
createKoraAuthSync: () => createKoraAuthSync,
|
|
49
|
+
createMemoryAuthTokenStorage: () => createMemoryAuthTokenStorage,
|
|
45
50
|
createPasskeyCredential: () => createPasskeyCredential,
|
|
51
|
+
createPersistentDeviceIdentity: () => createPersistentDeviceIdentity,
|
|
52
|
+
createWebStorageAuthTokenStorage: () => createWebStorageAuthTokenStorage,
|
|
46
53
|
decryptData: () => decryptData,
|
|
47
54
|
deriveEncryptionKey: () => deriveEncryptionKey,
|
|
48
55
|
encryptData: () => encryptData,
|
|
@@ -86,18 +93,48 @@ function decodeJwtPayload(token) {
|
|
|
86
93
|
}
|
|
87
94
|
function isTokenExpired(token) {
|
|
88
95
|
const payload = decodeJwtPayload(token);
|
|
89
|
-
if (!payload || typeof payload
|
|
96
|
+
if (!payload || typeof payload.exp !== "number") {
|
|
90
97
|
return true;
|
|
91
98
|
}
|
|
92
99
|
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
93
|
-
return payload
|
|
100
|
+
return payload.exp <= nowSeconds + EXPIRY_BUFFER_SECONDS;
|
|
94
101
|
}
|
|
95
102
|
function getUserIdFromToken(token) {
|
|
96
103
|
const payload = decodeJwtPayload(token);
|
|
97
|
-
if (!payload || typeof payload
|
|
104
|
+
if (!payload || typeof payload.sub !== "string") {
|
|
98
105
|
return null;
|
|
99
106
|
}
|
|
100
|
-
return payload
|
|
107
|
+
return payload.sub;
|
|
108
|
+
}
|
|
109
|
+
function getDefaultFetch() {
|
|
110
|
+
if (typeof globalThis.fetch !== "function") {
|
|
111
|
+
return async () => {
|
|
112
|
+
throw new AuthError(
|
|
113
|
+
"No fetch implementation is available in this runtime. Pass `fetch` to AuthClientConfig.",
|
|
114
|
+
"AUTH_FETCH_UNAVAILABLE"
|
|
115
|
+
);
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return globalThis.fetch.bind(globalThis);
|
|
119
|
+
}
|
|
120
|
+
function normalizeAuthUser(user) {
|
|
121
|
+
return {
|
|
122
|
+
id: user.id,
|
|
123
|
+
email: user.email,
|
|
124
|
+
name: user.name ?? null
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function canRedirectCurrentWindow() {
|
|
128
|
+
return typeof globalThis.window !== "undefined" && typeof globalThis.window.location?.assign === "function";
|
|
129
|
+
}
|
|
130
|
+
function redirectCurrentWindow(url) {
|
|
131
|
+
if (!canRedirectCurrentWindow()) {
|
|
132
|
+
throw new AuthError(
|
|
133
|
+
"OAuth redirect is not available in this runtime. Pass redirect: false and open the returned URL with your platform browser API.",
|
|
134
|
+
"AUTH_OAUTH_REDIRECT_UNAVAILABLE"
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
globalThis.window.location.assign(url);
|
|
101
138
|
}
|
|
102
139
|
function createTokenStorage(prefix) {
|
|
103
140
|
let useLocalStorage = false;
|
|
@@ -152,6 +189,8 @@ function createTokenStorage(prefix) {
|
|
|
152
189
|
var AuthClient = class {
|
|
153
190
|
serverUrl;
|
|
154
191
|
storage;
|
|
192
|
+
fetchFn;
|
|
193
|
+
deviceIdentity;
|
|
155
194
|
listeners = /* @__PURE__ */ new Set();
|
|
156
195
|
_state = "loading";
|
|
157
196
|
_user = null;
|
|
@@ -165,7 +204,9 @@ var AuthClient = class {
|
|
|
165
204
|
constructor(config) {
|
|
166
205
|
this.serverUrl = config.serverUrl.replace(/\/+$/, "");
|
|
167
206
|
const prefix = config.storageKey ?? "kora_auth";
|
|
168
|
-
this.storage = createTokenStorage(prefix);
|
|
207
|
+
this.storage = config.storage ?? createTokenStorage(prefix);
|
|
208
|
+
this.fetchFn = config.fetch ?? getDefaultFetch();
|
|
209
|
+
this.deviceIdentity = config.deviceIdentity;
|
|
169
210
|
}
|
|
170
211
|
// -----------------------------------------------------------------------
|
|
171
212
|
// Public getters
|
|
@@ -197,8 +238,8 @@ var AuthClient = class {
|
|
|
197
238
|
return;
|
|
198
239
|
}
|
|
199
240
|
this._initialized = true;
|
|
200
|
-
const accessToken = this.storage.getAccessToken();
|
|
201
|
-
const refreshToken = this.storage.getRefreshToken();
|
|
241
|
+
const accessToken = await this.storage.getAccessToken();
|
|
242
|
+
const refreshToken = await this.storage.getRefreshToken();
|
|
202
243
|
if (!accessToken || !refreshToken) {
|
|
203
244
|
this.setState("unauthenticated", null);
|
|
204
245
|
return;
|
|
@@ -215,7 +256,7 @@ var AuthClient = class {
|
|
|
215
256
|
}
|
|
216
257
|
} catch {
|
|
217
258
|
}
|
|
218
|
-
this.storage.clear();
|
|
259
|
+
await this.storage.clear();
|
|
219
260
|
this.setState("unauthenticated", null);
|
|
220
261
|
}
|
|
221
262
|
// -----------------------------------------------------------------------
|
|
@@ -229,12 +270,13 @@ var AuthClient = class {
|
|
|
229
270
|
* @throws {AuthError} If the request fails or the server returns an error
|
|
230
271
|
*/
|
|
231
272
|
async signUp(params) {
|
|
273
|
+
const body = await this.withDeviceIdentity(params);
|
|
232
274
|
const response = await this.request("/auth/signup", {
|
|
233
275
|
method: "POST",
|
|
234
|
-
body
|
|
276
|
+
body
|
|
235
277
|
});
|
|
236
278
|
const tokens = "tokens" in response ? response.tokens : response;
|
|
237
|
-
this.storage.setTokens(tokens.accessToken, tokens.refreshToken);
|
|
279
|
+
await this.storage.setTokens(tokens.accessToken, tokens.refreshToken);
|
|
238
280
|
const user = await this.fetchUserProfile(tokens.accessToken);
|
|
239
281
|
this.setState("authenticated", user);
|
|
240
282
|
return user;
|
|
@@ -247,16 +289,88 @@ var AuthClient = class {
|
|
|
247
289
|
* @throws {AuthError} If the credentials are invalid or the request fails
|
|
248
290
|
*/
|
|
249
291
|
async signIn(params) {
|
|
292
|
+
const body = await this.withDeviceIdentity(params);
|
|
250
293
|
const response = await this.request("/auth/signin", {
|
|
251
294
|
method: "POST",
|
|
252
|
-
body
|
|
295
|
+
body
|
|
253
296
|
});
|
|
254
297
|
const tokens = "tokens" in response ? response.tokens : response;
|
|
255
|
-
this.storage.setTokens(tokens.accessToken, tokens.refreshToken);
|
|
298
|
+
await this.storage.setTokens(tokens.accessToken, tokens.refreshToken);
|
|
256
299
|
const user = await this.fetchUserProfile(tokens.accessToken);
|
|
257
300
|
this.setState("authenticated", user);
|
|
258
301
|
return user;
|
|
259
302
|
}
|
|
303
|
+
/**
|
|
304
|
+
* Create an OAuth authorization URL and optionally redirect the current window.
|
|
305
|
+
*
|
|
306
|
+
* For web apps, call this from a button click and keep the default redirect behavior.
|
|
307
|
+
* For desktop/mobile, pass `redirect: false`, open the returned URL with the runtime's
|
|
308
|
+
* browser API, then call `completeOAuthSignIn()` after receiving the callback.
|
|
309
|
+
*/
|
|
310
|
+
async signInWithOAuth(provider, options = {}) {
|
|
311
|
+
const result = await this.createOAuthAuthorization(provider, options);
|
|
312
|
+
if (options.redirect ?? canRedirectCurrentWindow()) {
|
|
313
|
+
redirectCurrentWindow(result.url);
|
|
314
|
+
}
|
|
315
|
+
return result;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Complete an OAuth sign-in callback and store the issued Kora tokens.
|
|
319
|
+
*/
|
|
320
|
+
async completeOAuthSignIn(provider, params) {
|
|
321
|
+
const body = await this.withDeviceIdentity(params);
|
|
322
|
+
const response = await this.request(
|
|
323
|
+
`/auth/oauth/${encodeURIComponent(provider)}/callback`,
|
|
324
|
+
{
|
|
325
|
+
method: "POST",
|
|
326
|
+
body: { ...body }
|
|
327
|
+
}
|
|
328
|
+
);
|
|
329
|
+
await this.storage.setTokens(response.tokens.accessToken, response.tokens.refreshToken);
|
|
330
|
+
const user = normalizeAuthUser(response.user);
|
|
331
|
+
this.setState("authenticated", user);
|
|
332
|
+
return user;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Create an OAuth authorization URL for linking another provider to the current user.
|
|
336
|
+
*/
|
|
337
|
+
async getOAuthAuthorizationUrl(provider, options = {}) {
|
|
338
|
+
return this.createOAuthAuthorization(provider, { ...options, redirect: false });
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Link an OAuth provider to the current authenticated user.
|
|
342
|
+
*/
|
|
343
|
+
async linkOAuth(provider, params) {
|
|
344
|
+
const token = await this.requireAccessToken();
|
|
345
|
+
return this.request(`/auth/oauth/${encodeURIComponent(provider)}/link`, {
|
|
346
|
+
method: "POST",
|
|
347
|
+
body: {
|
|
348
|
+
code: params.code,
|
|
349
|
+
state: params.state
|
|
350
|
+
},
|
|
351
|
+
token
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* List OAuth accounts linked to the current authenticated user.
|
|
356
|
+
*/
|
|
357
|
+
async listLinkedAccounts() {
|
|
358
|
+
const token = await this.requireAccessToken();
|
|
359
|
+
return this.request("/auth/oauth/links", {
|
|
360
|
+
method: "GET",
|
|
361
|
+
token
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Unlink an OAuth provider from the current authenticated user.
|
|
366
|
+
*/
|
|
367
|
+
async unlinkOAuth(provider) {
|
|
368
|
+
const token = await this.requireAccessToken();
|
|
369
|
+
await this.request(`/auth/oauth/${encodeURIComponent(provider)}/link`, {
|
|
370
|
+
method: "DELETE",
|
|
371
|
+
token
|
|
372
|
+
});
|
|
373
|
+
}
|
|
260
374
|
/**
|
|
261
375
|
* Sign out the current user.
|
|
262
376
|
*
|
|
@@ -265,9 +379,9 @@ var AuthClient = class {
|
|
|
265
379
|
* stolen refresh tokens cannot be used after the user explicitly signs out.
|
|
266
380
|
*/
|
|
267
381
|
async signOut() {
|
|
268
|
-
const accessToken = this.storage.getAccessToken();
|
|
269
|
-
const refreshToken = this.storage.getRefreshToken();
|
|
270
|
-
this.storage.clear();
|
|
382
|
+
const accessToken = await this.storage.getAccessToken();
|
|
383
|
+
const refreshToken = await this.storage.getRefreshToken();
|
|
384
|
+
await this.storage.clear();
|
|
271
385
|
this._refreshPromise = null;
|
|
272
386
|
this.setState("unauthenticated", null);
|
|
273
387
|
if (accessToken) {
|
|
@@ -291,11 +405,11 @@ var AuthClient = class {
|
|
|
291
405
|
* authenticated and refresh is not possible
|
|
292
406
|
*/
|
|
293
407
|
async getAccessToken() {
|
|
294
|
-
const accessToken = this.storage.getAccessToken();
|
|
408
|
+
const accessToken = await this.storage.getAccessToken();
|
|
295
409
|
if (accessToken && !isTokenExpired(accessToken)) {
|
|
296
410
|
return accessToken;
|
|
297
411
|
}
|
|
298
|
-
const refreshToken = this.storage.getRefreshToken();
|
|
412
|
+
const refreshToken = await this.storage.getRefreshToken();
|
|
299
413
|
if (!refreshToken) {
|
|
300
414
|
return null;
|
|
301
415
|
}
|
|
@@ -378,7 +492,7 @@ var AuthClient = class {
|
|
|
378
492
|
name: null
|
|
379
493
|
});
|
|
380
494
|
} else {
|
|
381
|
-
this.storage.clear();
|
|
495
|
+
await this.storage.clear();
|
|
382
496
|
this.setState("unauthenticated", null);
|
|
383
497
|
}
|
|
384
498
|
}
|
|
@@ -391,10 +505,47 @@ var AuthClient = class {
|
|
|
391
505
|
method: "GET",
|
|
392
506
|
token: accessToken
|
|
393
507
|
});
|
|
508
|
+
return normalizeAuthUser(profile);
|
|
509
|
+
}
|
|
510
|
+
async createOAuthAuthorization(provider, options) {
|
|
511
|
+
const params = new URLSearchParams();
|
|
512
|
+
const withIdentity = await this.withDeviceIdentity({
|
|
513
|
+
deviceId: options.deviceId,
|
|
514
|
+
devicePublicKey: options.devicePublicKey
|
|
515
|
+
});
|
|
516
|
+
if (options.returnTo) {
|
|
517
|
+
params.set("returnTo", options.returnTo);
|
|
518
|
+
}
|
|
519
|
+
if (withIdentity.deviceId) {
|
|
520
|
+
params.set("deviceId", withIdentity.deviceId);
|
|
521
|
+
}
|
|
522
|
+
if (withIdentity.devicePublicKey) {
|
|
523
|
+
params.set("devicePublicKey", withIdentity.devicePublicKey);
|
|
524
|
+
}
|
|
525
|
+
if (options.metadata) {
|
|
526
|
+
for (const [key, value] of Object.entries(options.metadata)) {
|
|
527
|
+
if (value !== void 0 && value !== null) {
|
|
528
|
+
params.set(key, String(value));
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
const query = params.toString();
|
|
533
|
+
return this.request(
|
|
534
|
+
`/auth/oauth/${encodeURIComponent(provider)}${query ? `?${query}` : ""}`,
|
|
535
|
+
{
|
|
536
|
+
method: "GET"
|
|
537
|
+
}
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
async withDeviceIdentity(params) {
|
|
541
|
+
if (!this.deviceIdentity || params.deviceId && params.devicePublicKey) {
|
|
542
|
+
return params;
|
|
543
|
+
}
|
|
544
|
+
const identity = await this.deviceIdentity.getDeviceIdentity();
|
|
394
545
|
return {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
546
|
+
...params,
|
|
547
|
+
deviceId: params.deviceId ?? identity.deviceId,
|
|
548
|
+
devicePublicKey: params.devicePublicKey ?? identity.devicePublicKey
|
|
398
549
|
};
|
|
399
550
|
}
|
|
400
551
|
/**
|
|
@@ -413,6 +564,13 @@ var AuthClient = class {
|
|
|
413
564
|
this._refreshPromise = null;
|
|
414
565
|
}
|
|
415
566
|
}
|
|
567
|
+
async requireAccessToken() {
|
|
568
|
+
const token = await this.getAccessToken();
|
|
569
|
+
if (!token) {
|
|
570
|
+
throw new AuthError("You must be signed in to perform this action.", "AUTH_REQUIRED");
|
|
571
|
+
}
|
|
572
|
+
return token;
|
|
573
|
+
}
|
|
416
574
|
/**
|
|
417
575
|
* Execute the token refresh network request.
|
|
418
576
|
*/
|
|
@@ -422,10 +580,10 @@ var AuthClient = class {
|
|
|
422
580
|
method: "POST",
|
|
423
581
|
body: { refreshToken }
|
|
424
582
|
});
|
|
425
|
-
this.storage.setTokens(response.accessToken, response.refreshToken);
|
|
583
|
+
await this.storage.setTokens(response.accessToken, response.refreshToken);
|
|
426
584
|
return response.accessToken;
|
|
427
585
|
} catch {
|
|
428
|
-
this.storage.clear();
|
|
586
|
+
await this.storage.clear();
|
|
429
587
|
this.setState("unauthenticated", null);
|
|
430
588
|
return null;
|
|
431
589
|
}
|
|
@@ -445,11 +603,11 @@ var AuthClient = class {
|
|
|
445
603
|
headers["Content-Type"] = "application/json";
|
|
446
604
|
}
|
|
447
605
|
if (options.token) {
|
|
448
|
-
headers
|
|
606
|
+
headers.Authorization = `Bearer ${options.token}`;
|
|
449
607
|
}
|
|
450
608
|
let response;
|
|
451
609
|
try {
|
|
452
|
-
response = await
|
|
610
|
+
response = await this.fetchFn(url, {
|
|
453
611
|
method: options.method,
|
|
454
612
|
headers,
|
|
455
613
|
body: options.body ? JSON.stringify(options.body) : void 0
|
|
@@ -466,414 +624,154 @@ var AuthClient = class {
|
|
|
466
624
|
let serverError;
|
|
467
625
|
try {
|
|
468
626
|
const body = await response.json();
|
|
469
|
-
if (typeof body
|
|
470
|
-
errorMessage = body
|
|
627
|
+
if (typeof body.error === "string") {
|
|
628
|
+
errorMessage = body.error;
|
|
471
629
|
serverError = errorMessage;
|
|
472
|
-
} else if (typeof body
|
|
473
|
-
errorMessage = body
|
|
630
|
+
} else if (typeof body.message === "string") {
|
|
631
|
+
errorMessage = body.message;
|
|
474
632
|
serverError = errorMessage;
|
|
475
633
|
}
|
|
476
634
|
} catch {
|
|
477
635
|
}
|
|
478
|
-
throw new AuthError(
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
);
|
|
636
|
+
throw new AuthError(errorMessage, "AUTH_SERVER_ERROR", {
|
|
637
|
+
path,
|
|
638
|
+
status: response.status,
|
|
639
|
+
serverError
|
|
640
|
+
});
|
|
483
641
|
}
|
|
484
642
|
const json = await response.json();
|
|
485
|
-
const data = json
|
|
643
|
+
const data = json.data !== void 0 ? json.data : json;
|
|
486
644
|
return data;
|
|
487
645
|
}
|
|
488
646
|
};
|
|
489
647
|
|
|
490
|
-
// src/client/
|
|
648
|
+
// src/client/device-session.ts
|
|
649
|
+
var import_core4 = require("@korajs/core");
|
|
650
|
+
|
|
651
|
+
// src/device/device-identity.ts
|
|
491
652
|
var import_core2 = require("@korajs/core");
|
|
492
|
-
var
|
|
493
|
-
constructor(
|
|
494
|
-
super(
|
|
495
|
-
|
|
653
|
+
var CryptoUnavailableError = class extends import_core2.KoraError {
|
|
654
|
+
constructor() {
|
|
655
|
+
super(
|
|
656
|
+
"Web Crypto API (crypto.subtle) is not available in this environment. Device identity requires crypto.subtle, which is available in modern browsers and Node.js 20+. If running in SSR, ensure your runtime provides the Web Crypto API.",
|
|
657
|
+
"CRYPTO_UNAVAILABLE"
|
|
658
|
+
);
|
|
659
|
+
this.name = "CryptoUnavailableError";
|
|
496
660
|
}
|
|
497
661
|
};
|
|
498
|
-
var
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
_activeOrgId = null;
|
|
503
|
-
_activeOrg = null;
|
|
504
|
-
_activeRole = null;
|
|
505
|
-
constructor(config) {
|
|
506
|
-
this.serverUrl = config.serverUrl.replace(/\/+$/, "");
|
|
507
|
-
this.getAccessToken = config.getAccessToken;
|
|
508
|
-
}
|
|
509
|
-
// --- Getters ---
|
|
510
|
-
/** Currently active organization ID */
|
|
511
|
-
get activeOrgId() {
|
|
512
|
-
return this._activeOrgId;
|
|
662
|
+
var DeviceIdentityError = class extends import_core2.KoraError {
|
|
663
|
+
constructor(message, context) {
|
|
664
|
+
super(message, "DEVICE_IDENTITY_ERROR", context);
|
|
665
|
+
this.name = "DeviceIdentityError";
|
|
513
666
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
667
|
+
};
|
|
668
|
+
function toBase64Url(buffer) {
|
|
669
|
+
const bytes = new Uint8Array(buffer);
|
|
670
|
+
let binary = "";
|
|
671
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
672
|
+
binary += String.fromCharCode(bytes[i]);
|
|
517
673
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
674
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
675
|
+
}
|
|
676
|
+
function fromBase64Url(str) {
|
|
677
|
+
let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
678
|
+
const paddingNeeded = (4 - base64.length % 4) % 4;
|
|
679
|
+
base64 += "=".repeat(paddingNeeded);
|
|
680
|
+
const binary = atob(base64);
|
|
681
|
+
const bytes = new Uint8Array(binary.length);
|
|
682
|
+
for (let i = 0; i < binary.length; i++) {
|
|
683
|
+
bytes[i] = binary.charCodeAt(i);
|
|
521
684
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
return this.request("/orgs", {
|
|
528
|
-
method: "POST",
|
|
529
|
-
body: params
|
|
530
|
-
});
|
|
685
|
+
return bytes;
|
|
686
|
+
}
|
|
687
|
+
function assertCryptoAvailable() {
|
|
688
|
+
if (typeof globalThis.crypto === "undefined" || typeof globalThis.crypto.subtle === "undefined") {
|
|
689
|
+
throw new CryptoUnavailableError();
|
|
531
690
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
691
|
+
}
|
|
692
|
+
var ECDSA_ALGORITHM = {
|
|
693
|
+
name: "ECDSA",
|
|
694
|
+
namedCurve: "P-256"
|
|
695
|
+
};
|
|
696
|
+
var ECDSA_SIGN_ALGORITHM = {
|
|
697
|
+
name: "ECDSA",
|
|
698
|
+
hash: { name: "SHA-256" }
|
|
699
|
+
};
|
|
700
|
+
async function generateDeviceKeyPair() {
|
|
701
|
+
assertCryptoAvailable();
|
|
702
|
+
try {
|
|
703
|
+
const keyPair = await globalThis.crypto.subtle.generateKey(
|
|
704
|
+
ECDSA_ALGORITHM,
|
|
705
|
+
// extractable: false makes the private key non-extractable.
|
|
706
|
+
// The public key is always extractable regardless of this flag.
|
|
707
|
+
false,
|
|
708
|
+
["sign", "verify"]
|
|
709
|
+
);
|
|
710
|
+
return keyPair;
|
|
711
|
+
} catch (cause) {
|
|
712
|
+
throw new DeviceIdentityError(
|
|
713
|
+
"Failed to generate ECDSA P-256 device key pair. Ensure the runtime supports the ECDSA algorithm with the P-256 curve.",
|
|
714
|
+
{ cause: cause instanceof Error ? cause.message : String(cause) }
|
|
715
|
+
);
|
|
537
716
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
717
|
+
}
|
|
718
|
+
async function exportPublicKeyJwk(keyPair) {
|
|
719
|
+
assertCryptoAvailable();
|
|
720
|
+
try {
|
|
721
|
+
const jwk = await globalThis.crypto.subtle.exportKey("jwk", keyPair.publicKey);
|
|
722
|
+
return jwk;
|
|
723
|
+
} catch (cause) {
|
|
724
|
+
throw new DeviceIdentityError(
|
|
725
|
+
"Failed to export public key as JWK. The key pair may be invalid or the public key may not support JWK export.",
|
|
726
|
+
{ cause: cause instanceof Error ? cause.message : String(cause) }
|
|
727
|
+
);
|
|
543
728
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
729
|
+
}
|
|
730
|
+
async function signChallenge(privateKey, challenge) {
|
|
731
|
+
assertCryptoAvailable();
|
|
732
|
+
try {
|
|
733
|
+
const encoded = new TextEncoder().encode(challenge);
|
|
734
|
+
const signatureBuffer = await globalThis.crypto.subtle.sign(
|
|
735
|
+
ECDSA_SIGN_ALGORITHM,
|
|
736
|
+
privateKey,
|
|
737
|
+
encoded
|
|
738
|
+
);
|
|
739
|
+
return toBase64Url(signatureBuffer);
|
|
740
|
+
} catch (cause) {
|
|
741
|
+
throw new DeviceIdentityError(
|
|
742
|
+
'Failed to sign challenge. Ensure the key is a valid ECDSA P-256 private key with "sign" usage.',
|
|
743
|
+
{ cause: cause instanceof Error ? cause.message : String(cause) }
|
|
744
|
+
);
|
|
556
745
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
await
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
*/
|
|
587
|
-
clearActiveOrg() {
|
|
588
|
-
this._activeOrgId = null;
|
|
589
|
-
this._activeOrg = null;
|
|
590
|
-
this._activeRole = null;
|
|
591
|
-
this.notifyListeners();
|
|
592
|
-
}
|
|
593
|
-
// --- Member Management ---
|
|
594
|
-
/**
|
|
595
|
-
* List members of an organization.
|
|
596
|
-
*/
|
|
597
|
-
async listMembers(orgId) {
|
|
598
|
-
return this.request(`/orgs/${orgId}/members`, { method: "GET" });
|
|
599
|
-
}
|
|
600
|
-
/**
|
|
601
|
-
* Remove a member from an organization.
|
|
602
|
-
*/
|
|
603
|
-
async removeMember(orgId, userId) {
|
|
604
|
-
await this.request(`/orgs/${orgId}/members/${userId}`, { method: "DELETE" });
|
|
605
|
-
}
|
|
606
|
-
/**
|
|
607
|
-
* Update a member's role.
|
|
608
|
-
*/
|
|
609
|
-
async updateMemberRole(orgId, userId, role) {
|
|
610
|
-
return this.request(`/orgs/${orgId}/members/${userId}/role`, {
|
|
611
|
-
method: "PATCH",
|
|
612
|
-
body: { role }
|
|
613
|
-
});
|
|
614
|
-
}
|
|
615
|
-
/**
|
|
616
|
-
* Transfer ownership to another member.
|
|
617
|
-
*/
|
|
618
|
-
async transferOwnership(orgId, newOwnerId) {
|
|
619
|
-
await this.request(`/orgs/${orgId}/transfer`, {
|
|
620
|
-
method: "POST",
|
|
621
|
-
body: { newOwnerId }
|
|
622
|
-
});
|
|
623
|
-
}
|
|
624
|
-
/**
|
|
625
|
-
* Leave an organization (remove yourself).
|
|
626
|
-
*/
|
|
627
|
-
async leaveOrg(orgId) {
|
|
628
|
-
await this.request(`/orgs/${orgId}/leave`, { method: "POST" });
|
|
629
|
-
if (this._activeOrgId === orgId) {
|
|
630
|
-
this._activeOrgId = null;
|
|
631
|
-
this._activeOrg = null;
|
|
632
|
-
this._activeRole = null;
|
|
633
|
-
this.notifyListeners();
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
// --- Invitations ---
|
|
637
|
-
/**
|
|
638
|
-
* Invite a user to an organization by email.
|
|
639
|
-
*/
|
|
640
|
-
async inviteMember(orgId, params) {
|
|
641
|
-
return this.request(`/orgs/${orgId}/invitations`, {
|
|
642
|
-
method: "POST",
|
|
643
|
-
body: params
|
|
644
|
-
});
|
|
645
|
-
}
|
|
646
|
-
/**
|
|
647
|
-
* Accept an invitation by token.
|
|
648
|
-
*/
|
|
649
|
-
async acceptInvitation(token) {
|
|
650
|
-
return this.request("/invitations/accept", {
|
|
651
|
-
method: "POST",
|
|
652
|
-
body: { token }
|
|
653
|
-
});
|
|
654
|
-
}
|
|
655
|
-
/**
|
|
656
|
-
* List pending invitations for an organization.
|
|
657
|
-
*/
|
|
658
|
-
async listInvitations(orgId) {
|
|
659
|
-
return this.request(`/orgs/${orgId}/invitations`, { method: "GET" });
|
|
660
|
-
}
|
|
661
|
-
/**
|
|
662
|
-
* Revoke a pending invitation.
|
|
663
|
-
*/
|
|
664
|
-
async revokeInvitation(orgId, invitationId) {
|
|
665
|
-
await this.request(`/orgs/${orgId}/invitations/${invitationId}`, { method: "DELETE" });
|
|
666
|
-
}
|
|
667
|
-
/**
|
|
668
|
-
* List pending invitations for the current user's email.
|
|
669
|
-
*/
|
|
670
|
-
async listMyInvitations(email) {
|
|
671
|
-
return this.request(
|
|
672
|
-
`/invitations?email=${encodeURIComponent(email)}`,
|
|
673
|
-
{ method: "GET" }
|
|
674
|
-
);
|
|
675
|
-
}
|
|
676
|
-
// --- Subscriptions ---
|
|
677
|
-
/**
|
|
678
|
-
* Subscribe to active org changes.
|
|
679
|
-
* @returns Unsubscribe function
|
|
680
|
-
*/
|
|
681
|
-
onOrgChange(callback) {
|
|
682
|
-
this.listeners.add(callback);
|
|
683
|
-
return () => {
|
|
684
|
-
this.listeners.delete(callback);
|
|
685
|
-
};
|
|
686
|
-
}
|
|
687
|
-
// --- Internal ---
|
|
688
|
-
notifyListeners() {
|
|
689
|
-
for (const listener of this.listeners) {
|
|
690
|
-
try {
|
|
691
|
-
listener(this._activeOrgId);
|
|
692
|
-
} catch {
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
async request(path, options) {
|
|
697
|
-
const token = await this.getAccessToken();
|
|
698
|
-
if (!token) {
|
|
699
|
-
throw new OrgClientError(
|
|
700
|
-
"Not authenticated. Sign in before performing organization operations.",
|
|
701
|
-
"ORG_NOT_AUTHENTICATED"
|
|
702
|
-
);
|
|
703
|
-
}
|
|
704
|
-
const url = `${this.serverUrl}${path}`;
|
|
705
|
-
const headers = {
|
|
706
|
-
Authorization: `Bearer ${token}`
|
|
707
|
-
};
|
|
708
|
-
if (options.body) {
|
|
709
|
-
headers["Content-Type"] = "application/json";
|
|
710
|
-
}
|
|
711
|
-
let response;
|
|
712
|
-
try {
|
|
713
|
-
response = await fetch(url, {
|
|
714
|
-
method: options.method,
|
|
715
|
-
headers,
|
|
716
|
-
body: options.body ? JSON.stringify(options.body) : void 0
|
|
717
|
-
});
|
|
718
|
-
} catch (cause) {
|
|
719
|
-
throw new OrgClientError(
|
|
720
|
-
`Network request to ${path} failed.`,
|
|
721
|
-
"ORG_NETWORK_ERROR",
|
|
722
|
-
{ path, cause: cause instanceof Error ? cause.message : String(cause) }
|
|
723
|
-
);
|
|
724
|
-
}
|
|
725
|
-
if (!response.ok) {
|
|
726
|
-
let errorMessage = `Server returned HTTP ${response.status}`;
|
|
727
|
-
try {
|
|
728
|
-
const body = await response.json();
|
|
729
|
-
if (typeof body["error"] === "string") {
|
|
730
|
-
errorMessage = body["error"];
|
|
731
|
-
}
|
|
732
|
-
} catch {
|
|
733
|
-
}
|
|
734
|
-
throw new OrgClientError(errorMessage, "ORG_SERVER_ERROR", {
|
|
735
|
-
path,
|
|
736
|
-
status: response.status
|
|
737
|
-
});
|
|
738
|
-
}
|
|
739
|
-
const text = await response.text();
|
|
740
|
-
if (text.length === 0) return void 0;
|
|
741
|
-
try {
|
|
742
|
-
const body = JSON.parse(text);
|
|
743
|
-
if (body && typeof body === "object" && "data" in body) {
|
|
744
|
-
return body.data;
|
|
745
|
-
}
|
|
746
|
-
return body;
|
|
747
|
-
} catch {
|
|
748
|
-
return void 0;
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
};
|
|
752
|
-
|
|
753
|
-
// src/device/device-identity.ts
|
|
754
|
-
var import_core3 = require("@korajs/core");
|
|
755
|
-
var CryptoUnavailableError = class extends import_core3.KoraError {
|
|
756
|
-
constructor() {
|
|
757
|
-
super(
|
|
758
|
-
"Web Crypto API (crypto.subtle) is not available in this environment. Device identity requires crypto.subtle, which is available in modern browsers and Node.js 20+. If running in SSR, ensure your runtime provides the Web Crypto API.",
|
|
759
|
-
"CRYPTO_UNAVAILABLE"
|
|
760
|
-
);
|
|
761
|
-
this.name = "CryptoUnavailableError";
|
|
762
|
-
}
|
|
763
|
-
};
|
|
764
|
-
var DeviceIdentityError = class extends import_core3.KoraError {
|
|
765
|
-
constructor(message, context) {
|
|
766
|
-
super(message, "DEVICE_IDENTITY_ERROR", context);
|
|
767
|
-
this.name = "DeviceIdentityError";
|
|
768
|
-
}
|
|
769
|
-
};
|
|
770
|
-
function toBase64Url(buffer) {
|
|
771
|
-
const bytes = new Uint8Array(buffer);
|
|
772
|
-
let binary = "";
|
|
773
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
774
|
-
binary += String.fromCharCode(bytes[i]);
|
|
775
|
-
}
|
|
776
|
-
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
777
|
-
}
|
|
778
|
-
function fromBase64Url(str) {
|
|
779
|
-
let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
780
|
-
const paddingNeeded = (4 - base64.length % 4) % 4;
|
|
781
|
-
base64 += "=".repeat(paddingNeeded);
|
|
782
|
-
const binary = atob(base64);
|
|
783
|
-
const bytes = new Uint8Array(binary.length);
|
|
784
|
-
for (let i = 0; i < binary.length; i++) {
|
|
785
|
-
bytes[i] = binary.charCodeAt(i);
|
|
786
|
-
}
|
|
787
|
-
return bytes;
|
|
788
|
-
}
|
|
789
|
-
function assertCryptoAvailable() {
|
|
790
|
-
if (typeof globalThis.crypto === "undefined" || typeof globalThis.crypto.subtle === "undefined") {
|
|
791
|
-
throw new CryptoUnavailableError();
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
var ECDSA_ALGORITHM = {
|
|
795
|
-
name: "ECDSA",
|
|
796
|
-
namedCurve: "P-256"
|
|
797
|
-
};
|
|
798
|
-
var ECDSA_SIGN_ALGORITHM = {
|
|
799
|
-
name: "ECDSA",
|
|
800
|
-
hash: { name: "SHA-256" }
|
|
801
|
-
};
|
|
802
|
-
async function generateDeviceKeyPair() {
|
|
803
|
-
assertCryptoAvailable();
|
|
804
|
-
try {
|
|
805
|
-
const keyPair = await globalThis.crypto.subtle.generateKey(
|
|
806
|
-
ECDSA_ALGORITHM,
|
|
807
|
-
// extractable: false makes the private key non-extractable.
|
|
808
|
-
// The public key is always extractable regardless of this flag.
|
|
809
|
-
false,
|
|
810
|
-
["sign", "verify"]
|
|
811
|
-
);
|
|
812
|
-
return keyPair;
|
|
813
|
-
} catch (cause) {
|
|
814
|
-
throw new DeviceIdentityError(
|
|
815
|
-
"Failed to generate ECDSA P-256 device key pair. Ensure the runtime supports the ECDSA algorithm with the P-256 curve.",
|
|
816
|
-
{ cause: cause instanceof Error ? cause.message : String(cause) }
|
|
817
|
-
);
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
async function exportPublicKeyJwk(keyPair) {
|
|
821
|
-
assertCryptoAvailable();
|
|
822
|
-
try {
|
|
823
|
-
const jwk = await globalThis.crypto.subtle.exportKey("jwk", keyPair.publicKey);
|
|
824
|
-
return jwk;
|
|
825
|
-
} catch (cause) {
|
|
826
|
-
throw new DeviceIdentityError(
|
|
827
|
-
"Failed to export public key as JWK. The key pair may be invalid or the public key may not support JWK export.",
|
|
828
|
-
{ cause: cause instanceof Error ? cause.message : String(cause) }
|
|
829
|
-
);
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
async function signChallenge(privateKey, challenge) {
|
|
833
|
-
assertCryptoAvailable();
|
|
834
|
-
try {
|
|
835
|
-
const encoded = new TextEncoder().encode(challenge);
|
|
836
|
-
const signatureBuffer = await globalThis.crypto.subtle.sign(
|
|
837
|
-
ECDSA_SIGN_ALGORITHM,
|
|
838
|
-
privateKey,
|
|
839
|
-
encoded
|
|
840
|
-
);
|
|
841
|
-
return toBase64Url(signatureBuffer);
|
|
842
|
-
} catch (cause) {
|
|
843
|
-
throw new DeviceIdentityError(
|
|
844
|
-
'Failed to sign challenge. Ensure the key is a valid ECDSA P-256 private key with "sign" usage.',
|
|
845
|
-
{ cause: cause instanceof Error ? cause.message : String(cause) }
|
|
846
|
-
);
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
async function verifyChallenge(publicKeyJwk, challenge, signature) {
|
|
850
|
-
assertCryptoAvailable();
|
|
851
|
-
try {
|
|
852
|
-
const publicKey = await globalThis.crypto.subtle.importKey(
|
|
853
|
-
"jwk",
|
|
854
|
-
publicKeyJwk,
|
|
855
|
-
ECDSA_ALGORITHM,
|
|
856
|
-
true,
|
|
857
|
-
["verify"]
|
|
858
|
-
);
|
|
859
|
-
const encoded = new TextEncoder().encode(challenge);
|
|
860
|
-
const signatureBytes = fromBase64Url(signature);
|
|
861
|
-
const isValid = await globalThis.crypto.subtle.verify(
|
|
862
|
-
ECDSA_SIGN_ALGORITHM,
|
|
863
|
-
publicKey,
|
|
864
|
-
signatureBytes,
|
|
865
|
-
encoded
|
|
866
|
-
);
|
|
867
|
-
return isValid;
|
|
868
|
-
} catch (cause) {
|
|
869
|
-
throw new DeviceIdentityError(
|
|
870
|
-
"Failed to verify challenge signature. The public key JWK or signature format may be invalid.",
|
|
871
|
-
{
|
|
872
|
-
cause: cause instanceof Error ? cause.message : String(cause),
|
|
873
|
-
publicKeyKty: publicKeyJwk.kty,
|
|
874
|
-
publicKeyCrv: publicKeyJwk.crv
|
|
875
|
-
}
|
|
876
|
-
);
|
|
746
|
+
}
|
|
747
|
+
async function verifyChallenge(publicKeyJwk, challenge, signature) {
|
|
748
|
+
assertCryptoAvailable();
|
|
749
|
+
try {
|
|
750
|
+
const publicKey = await globalThis.crypto.subtle.importKey(
|
|
751
|
+
"jwk",
|
|
752
|
+
publicKeyJwk,
|
|
753
|
+
ECDSA_ALGORITHM,
|
|
754
|
+
true,
|
|
755
|
+
["verify"]
|
|
756
|
+
);
|
|
757
|
+
const encoded = new TextEncoder().encode(challenge);
|
|
758
|
+
const signatureBytes = fromBase64Url(signature);
|
|
759
|
+
const isValid = await globalThis.crypto.subtle.verify(
|
|
760
|
+
ECDSA_SIGN_ALGORITHM,
|
|
761
|
+
publicKey,
|
|
762
|
+
signatureBytes,
|
|
763
|
+
encoded
|
|
764
|
+
);
|
|
765
|
+
return isValid;
|
|
766
|
+
} catch (cause) {
|
|
767
|
+
throw new DeviceIdentityError(
|
|
768
|
+
"Failed to verify challenge signature. The public key JWK or signature format may be invalid.",
|
|
769
|
+
{
|
|
770
|
+
cause: cause instanceof Error ? cause.message : String(cause),
|
|
771
|
+
publicKeyKty: publicKeyJwk.kty,
|
|
772
|
+
publicKeyCrv: publicKeyJwk.crv
|
|
773
|
+
}
|
|
774
|
+
);
|
|
877
775
|
}
|
|
878
776
|
}
|
|
879
777
|
async function computePublicKeyThumbprint(publicKeyJwk) {
|
|
@@ -905,16 +803,15 @@ async function computePublicKeyThumbprint(publicKeyJwk) {
|
|
|
905
803
|
const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", encoded);
|
|
906
804
|
return toBase64Url(hashBuffer);
|
|
907
805
|
} catch (cause) {
|
|
908
|
-
throw new DeviceIdentityError(
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
);
|
|
806
|
+
throw new DeviceIdentityError("Failed to compute SHA-256 thumbprint of the public key JWK.", {
|
|
807
|
+
cause: cause instanceof Error ? cause.message : String(cause)
|
|
808
|
+
});
|
|
912
809
|
}
|
|
913
810
|
}
|
|
914
811
|
|
|
915
812
|
// src/device/device-store.ts
|
|
916
|
-
var
|
|
917
|
-
var DeviceKeyStoreError = class extends
|
|
813
|
+
var import_core3 = require("@korajs/core");
|
|
814
|
+
var DeviceKeyStoreError = class extends import_core3.KoraError {
|
|
918
815
|
constructor(message, context) {
|
|
919
816
|
super(message, "DEVICE_KEY_STORE_ERROR", context);
|
|
920
817
|
this.name = "DeviceKeyStoreError";
|
|
@@ -962,10 +859,9 @@ var IndexedDBDeviceKeyStore = class {
|
|
|
962
859
|
request.onerror = () => {
|
|
963
860
|
this.dbPromise = null;
|
|
964
861
|
reject(
|
|
965
|
-
new DeviceKeyStoreError(
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
)
|
|
862
|
+
new DeviceKeyStoreError("Failed to open IndexedDB database for device key storage.", {
|
|
863
|
+
error: request.error?.message
|
|
864
|
+
})
|
|
969
865
|
);
|
|
970
866
|
};
|
|
971
867
|
request.onblocked = () => {
|
|
@@ -977,163 +873,636 @@ var IndexedDBDeviceKeyStore = class {
|
|
|
977
873
|
);
|
|
978
874
|
};
|
|
979
875
|
});
|
|
980
|
-
return this.dbPromise;
|
|
876
|
+
return this.dbPromise;
|
|
877
|
+
}
|
|
878
|
+
/** @inheritdoc */
|
|
879
|
+
async saveKeyPair(deviceId, keyPair) {
|
|
880
|
+
const db = await this.openDatabase();
|
|
881
|
+
return new Promise((resolve, reject) => {
|
|
882
|
+
try {
|
|
883
|
+
const tx = db.transaction(IDB_STORE_NAME, "readwrite");
|
|
884
|
+
const store = tx.objectStore(IDB_STORE_NAME);
|
|
885
|
+
store.put(keyPair, deviceId);
|
|
886
|
+
tx.oncomplete = () => {
|
|
887
|
+
resolve();
|
|
888
|
+
};
|
|
889
|
+
tx.onerror = () => {
|
|
890
|
+
reject(
|
|
891
|
+
new DeviceKeyStoreError(`Failed to save key pair for device "${deviceId}".`, {
|
|
892
|
+
deviceId,
|
|
893
|
+
error: tx.error?.message
|
|
894
|
+
})
|
|
895
|
+
);
|
|
896
|
+
};
|
|
897
|
+
} catch (cause) {
|
|
898
|
+
reject(
|
|
899
|
+
new DeviceKeyStoreError(`Failed to save key pair for device "${deviceId}".`, {
|
|
900
|
+
deviceId,
|
|
901
|
+
cause: cause instanceof Error ? cause.message : String(cause)
|
|
902
|
+
})
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
/** @inheritdoc */
|
|
908
|
+
async loadKeyPair(deviceId) {
|
|
909
|
+
const db = await this.openDatabase();
|
|
910
|
+
return new Promise((resolve, reject) => {
|
|
911
|
+
try {
|
|
912
|
+
const tx = db.transaction(IDB_STORE_NAME, "readonly");
|
|
913
|
+
const store = tx.objectStore(IDB_STORE_NAME);
|
|
914
|
+
const request = store.get(deviceId);
|
|
915
|
+
request.onsuccess = () => {
|
|
916
|
+
const result = request.result;
|
|
917
|
+
resolve(result ?? null);
|
|
918
|
+
};
|
|
919
|
+
request.onerror = () => {
|
|
920
|
+
reject(
|
|
921
|
+
new DeviceKeyStoreError(`Failed to load key pair for device "${deviceId}".`, {
|
|
922
|
+
deviceId,
|
|
923
|
+
error: request.error?.message
|
|
924
|
+
})
|
|
925
|
+
);
|
|
926
|
+
};
|
|
927
|
+
} catch (cause) {
|
|
928
|
+
reject(
|
|
929
|
+
new DeviceKeyStoreError(`Failed to load key pair for device "${deviceId}".`, {
|
|
930
|
+
deviceId,
|
|
931
|
+
cause: cause instanceof Error ? cause.message : String(cause)
|
|
932
|
+
})
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
});
|
|
981
936
|
}
|
|
982
937
|
/** @inheritdoc */
|
|
983
|
-
async
|
|
938
|
+
async deleteKeyPair(deviceId) {
|
|
984
939
|
const db = await this.openDatabase();
|
|
985
940
|
return new Promise((resolve, reject) => {
|
|
986
941
|
try {
|
|
987
942
|
const tx = db.transaction(IDB_STORE_NAME, "readwrite");
|
|
988
943
|
const store = tx.objectStore(IDB_STORE_NAME);
|
|
989
|
-
store.
|
|
944
|
+
store.delete(deviceId);
|
|
990
945
|
tx.oncomplete = () => {
|
|
991
946
|
resolve();
|
|
992
947
|
};
|
|
993
948
|
tx.onerror = () => {
|
|
949
|
+
reject(
|
|
950
|
+
new DeviceKeyStoreError(`Failed to delete key pair for device "${deviceId}".`, {
|
|
951
|
+
deviceId,
|
|
952
|
+
error: tx.error?.message
|
|
953
|
+
})
|
|
954
|
+
);
|
|
955
|
+
};
|
|
956
|
+
} catch (cause) {
|
|
957
|
+
reject(
|
|
958
|
+
new DeviceKeyStoreError(`Failed to delete key pair for device "${deviceId}".`, {
|
|
959
|
+
deviceId,
|
|
960
|
+
cause: cause instanceof Error ? cause.message : String(cause)
|
|
961
|
+
})
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
/** @inheritdoc */
|
|
967
|
+
async hasKeyPair(deviceId) {
|
|
968
|
+
const db = await this.openDatabase();
|
|
969
|
+
return new Promise((resolve, reject) => {
|
|
970
|
+
try {
|
|
971
|
+
const tx = db.transaction(IDB_STORE_NAME, "readonly");
|
|
972
|
+
const store = tx.objectStore(IDB_STORE_NAME);
|
|
973
|
+
const request = store.count(deviceId);
|
|
974
|
+
request.onsuccess = () => {
|
|
975
|
+
resolve(request.result > 0);
|
|
976
|
+
};
|
|
977
|
+
request.onerror = () => {
|
|
994
978
|
reject(
|
|
995
979
|
new DeviceKeyStoreError(
|
|
996
|
-
`Failed to
|
|
997
|
-
{ deviceId, error:
|
|
980
|
+
`Failed to check if key pair exists for device "${deviceId}".`,
|
|
981
|
+
{ deviceId, error: request.error?.message }
|
|
998
982
|
)
|
|
999
983
|
);
|
|
1000
984
|
};
|
|
1001
985
|
} catch (cause) {
|
|
1002
986
|
reject(
|
|
1003
|
-
new DeviceKeyStoreError(
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
cause: cause instanceof Error ? cause.message : String(cause)
|
|
1008
|
-
}
|
|
1009
|
-
)
|
|
987
|
+
new DeviceKeyStoreError(`Failed to check if key pair exists for device "${deviceId}".`, {
|
|
988
|
+
deviceId,
|
|
989
|
+
cause: cause instanceof Error ? cause.message : String(cause)
|
|
990
|
+
})
|
|
1010
991
|
);
|
|
1011
992
|
}
|
|
1012
993
|
});
|
|
1013
994
|
}
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
995
|
+
};
|
|
996
|
+
var InMemoryDeviceKeyStore = class {
|
|
997
|
+
store = /* @__PURE__ */ new Map();
|
|
998
|
+
/** @inheritdoc */
|
|
999
|
+
async saveKeyPair(deviceId, keyPair) {
|
|
1000
|
+
this.store.set(deviceId, keyPair);
|
|
1001
|
+
}
|
|
1002
|
+
/** @inheritdoc */
|
|
1003
|
+
async loadKeyPair(deviceId) {
|
|
1004
|
+
return this.store.get(deviceId) ?? null;
|
|
1005
|
+
}
|
|
1006
|
+
/** @inheritdoc */
|
|
1007
|
+
async deleteKeyPair(deviceId) {
|
|
1008
|
+
this.store.delete(deviceId);
|
|
1009
|
+
}
|
|
1010
|
+
/** @inheritdoc */
|
|
1011
|
+
async hasKeyPair(deviceId) {
|
|
1012
|
+
return this.store.has(deviceId);
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
function createDeviceKeyStore() {
|
|
1016
|
+
if (typeof globalThis.indexedDB !== "undefined") {
|
|
1017
|
+
return new IndexedDBDeviceKeyStore();
|
|
1018
|
+
}
|
|
1019
|
+
return new InMemoryDeviceKeyStore();
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// src/client/device-session.ts
|
|
1023
|
+
var DEFAULT_DEVICE_ID_KEY = "kora_auth_device_id";
|
|
1024
|
+
var AuthDeviceIdentityError = class extends import_core4.KoraError {
|
|
1025
|
+
constructor(message, context) {
|
|
1026
|
+
super(message, "AUTH_DEVICE_IDENTITY_ERROR", context);
|
|
1027
|
+
this.name = "AuthDeviceIdentityError";
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
function createPersistentDeviceIdentity(options) {
|
|
1031
|
+
const storage = options.storage;
|
|
1032
|
+
const keyStore = options.keyStore ?? createDefaultPersistentKeyStore();
|
|
1033
|
+
const deviceIdKey = options.deviceIdKey ?? DEFAULT_DEVICE_ID_KEY;
|
|
1034
|
+
const generateDeviceId = options.generateDeviceId ?? defaultDeviceId;
|
|
1035
|
+
return {
|
|
1036
|
+
async getDeviceIdentity() {
|
|
1037
|
+
let deviceId = await storage.getItem(deviceIdKey);
|
|
1038
|
+
if (!deviceId) {
|
|
1039
|
+
deviceId = generateDeviceId();
|
|
1040
|
+
await storage.setItem(deviceIdKey, deviceId);
|
|
1041
|
+
}
|
|
1042
|
+
let keyPair = await keyStore.loadKeyPair(deviceId);
|
|
1043
|
+
if (!keyPair) {
|
|
1044
|
+
keyPair = await generateDeviceKeyPair();
|
|
1045
|
+
await keyStore.saveKeyPair(deviceId, keyPair);
|
|
1046
|
+
}
|
|
1047
|
+
const publicKey = await exportPublicKeyJwk(keyPair);
|
|
1048
|
+
return {
|
|
1049
|
+
deviceId,
|
|
1050
|
+
devicePublicKey: JSON.stringify(publicKey)
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
function createDefaultPersistentKeyStore() {
|
|
1056
|
+
if (typeof globalThis.indexedDB !== "undefined") {
|
|
1057
|
+
return createDeviceKeyStore();
|
|
1058
|
+
}
|
|
1059
|
+
throw new AuthDeviceIdentityError(
|
|
1060
|
+
"No persistent device key store is available in this runtime. Pass `keyStore` to createPersistentDeviceIdentity()."
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
function defaultDeviceId() {
|
|
1064
|
+
if (typeof globalThis.crypto?.randomUUID === "function") {
|
|
1065
|
+
return globalThis.crypto.randomUUID();
|
|
1066
|
+
}
|
|
1067
|
+
const bytes = new Uint8Array(16);
|
|
1068
|
+
if (typeof globalThis.crypto?.getRandomValues === "function") {
|
|
1069
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
1070
|
+
} else {
|
|
1071
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1072
|
+
bytes[i] = Math.floor(Math.random() * 256);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
1076
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
1077
|
+
const hex = [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1078
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
// src/client/storage.ts
|
|
1082
|
+
function createAuthTokenStorage(options) {
|
|
1083
|
+
const prefix = options.prefix ?? "kora_auth";
|
|
1084
|
+
const accessKey = `${prefix}_access_token`;
|
|
1085
|
+
const refreshKey = `${prefix}_refresh_token`;
|
|
1086
|
+
const store = options.store;
|
|
1087
|
+
return {
|
|
1088
|
+
getAccessToken: () => store.getItem(accessKey),
|
|
1089
|
+
getRefreshToken: () => store.getItem(refreshKey),
|
|
1090
|
+
async setTokens(accessToken, refreshToken) {
|
|
1091
|
+
await store.setItem(accessKey, accessToken);
|
|
1092
|
+
await store.setItem(refreshKey, refreshToken);
|
|
1093
|
+
},
|
|
1094
|
+
async clear() {
|
|
1095
|
+
await store.removeItem(accessKey);
|
|
1096
|
+
await store.removeItem(refreshKey);
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
function createMemoryAuthTokenStorage() {
|
|
1101
|
+
let accessToken = null;
|
|
1102
|
+
let refreshToken = null;
|
|
1103
|
+
return {
|
|
1104
|
+
getAccessToken: () => accessToken,
|
|
1105
|
+
getRefreshToken: () => refreshToken,
|
|
1106
|
+
setTokens(access, refresh) {
|
|
1107
|
+
accessToken = access;
|
|
1108
|
+
refreshToken = refresh;
|
|
1109
|
+
},
|
|
1110
|
+
clear() {
|
|
1111
|
+
accessToken = null;
|
|
1112
|
+
refreshToken = null;
|
|
1113
|
+
}
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
function createWebStorageAuthTokenStorage(storage, prefix) {
|
|
1117
|
+
return createAuthTokenStorage({
|
|
1118
|
+
prefix,
|
|
1119
|
+
store: {
|
|
1120
|
+
getItem: (key) => storage.getItem(key),
|
|
1121
|
+
setItem: (key, value) => {
|
|
1122
|
+
storage.setItem(key, value);
|
|
1123
|
+
},
|
|
1124
|
+
removeItem: (key) => {
|
|
1125
|
+
storage.removeItem(key);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// src/client/quickstart.ts
|
|
1132
|
+
function createKoraAuth(options) {
|
|
1133
|
+
const storage = options.storage ?? (options.credentialStore ? createAuthTokenStorage({
|
|
1134
|
+
store: options.credentialStore,
|
|
1135
|
+
prefix: options.storageKey
|
|
1136
|
+
}) : tryCreateDefaultTokenStorage(options.storageKey));
|
|
1137
|
+
const deviceIdentity = options.deviceIdentity === false ? void 0 : options.deviceIdentity ?? tryCreateDefaultDeviceIdentity(options.credentialStore, options.deviceKeyStore);
|
|
1138
|
+
return new AuthClient({
|
|
1139
|
+
serverUrl: options.serverUrl,
|
|
1140
|
+
storageKey: options.storageKey,
|
|
1141
|
+
storage,
|
|
1142
|
+
fetch: options.fetch,
|
|
1143
|
+
deviceIdentity
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
1146
|
+
function tryCreateDefaultTokenStorage(storageKey) {
|
|
1147
|
+
const storage = tryGetBrowserStorage();
|
|
1148
|
+
return storage ? createWebStorageAuthTokenStorage(storage, storageKey) : void 0;
|
|
1149
|
+
}
|
|
1150
|
+
function tryCreateDefaultDeviceIdentity(credentialStore, deviceKeyStore) {
|
|
1151
|
+
const storage = credentialStore ?? tryGetBrowserStorage();
|
|
1152
|
+
if (!storage) {
|
|
1153
|
+
return void 0;
|
|
1154
|
+
}
|
|
1155
|
+
try {
|
|
1156
|
+
return createPersistentDeviceIdentity({
|
|
1157
|
+
storage,
|
|
1158
|
+
keyStore: deviceKeyStore
|
|
1159
|
+
});
|
|
1160
|
+
} catch {
|
|
1161
|
+
return void 0;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
function tryGetBrowserStorage() {
|
|
1165
|
+
try {
|
|
1166
|
+
if (typeof globalThis.localStorage === "undefined") {
|
|
1167
|
+
return null;
|
|
1168
|
+
}
|
|
1169
|
+
const key = "__kora_auth_quickstart_test__";
|
|
1170
|
+
globalThis.localStorage.setItem(key, "1");
|
|
1171
|
+
globalThis.localStorage.removeItem(key);
|
|
1172
|
+
return globalThis.localStorage;
|
|
1173
|
+
} catch {
|
|
1174
|
+
return null;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
// src/client/auth-sync.ts
|
|
1179
|
+
var import_core5 = require("@korajs/core");
|
|
1180
|
+
function decodeJwtPayload2(token) {
|
|
1181
|
+
const parts = token.split(".");
|
|
1182
|
+
if (parts.length !== 3) {
|
|
1183
|
+
return null;
|
|
1184
|
+
}
|
|
1185
|
+
const payloadSegment = parts[1];
|
|
1186
|
+
if (payloadSegment === void 0) {
|
|
1187
|
+
return null;
|
|
1188
|
+
}
|
|
1189
|
+
try {
|
|
1190
|
+
const base64 = payloadSegment.replace(/-/g, "+").replace(/_/g, "/");
|
|
1191
|
+
const padded = base64.padEnd(base64.length + (4 - base64.length % 4) % 4, "=");
|
|
1192
|
+
const json = typeof atob === "function" ? atob(padded) : Buffer.from(padded, "base64").toString("utf-8");
|
|
1193
|
+
const parsed = JSON.parse(json);
|
|
1194
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1195
|
+
return null;
|
|
1196
|
+
}
|
|
1197
|
+
return parsed;
|
|
1198
|
+
} catch {
|
|
1199
|
+
return null;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
function readDeviceIdFromClaims(claims) {
|
|
1203
|
+
const dev = claims.dev;
|
|
1204
|
+
return typeof dev === "string" && dev.length > 0 ? dev : void 0;
|
|
1205
|
+
}
|
|
1206
|
+
function createKoraAuthSync(options) {
|
|
1207
|
+
const { authClient, schema, scopeFromClaims } = options;
|
|
1208
|
+
const binding = {
|
|
1209
|
+
auth: async () => {
|
|
1210
|
+
const token = await authClient.getAccessToken();
|
|
1211
|
+
return { token: token ?? "" };
|
|
1212
|
+
}
|
|
1213
|
+
};
|
|
1214
|
+
if (schema) {
|
|
1215
|
+
binding.resolveScopeMap = async () => {
|
|
1216
|
+
const token = await authClient.getAccessToken();
|
|
1217
|
+
if (!token) {
|
|
1218
|
+
return void 0;
|
|
1219
|
+
}
|
|
1220
|
+
const claims = decodeJwtPayload2(token);
|
|
1221
|
+
if (!claims) {
|
|
1222
|
+
return void 0;
|
|
1223
|
+
}
|
|
1224
|
+
const scopeValues = scopeFromClaims ? scopeFromClaims(claims) : (0, import_core5.extractScopeValuesFromClaims)(schema, claims);
|
|
1225
|
+
return (0, import_core5.buildScopeMap)(schema, scopeValues);
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
binding.resolveNodeId = async () => {
|
|
1229
|
+
const token = await authClient.getAccessToken();
|
|
1230
|
+
if (!token) {
|
|
1231
|
+
return void 0;
|
|
1232
|
+
}
|
|
1233
|
+
const claims = decodeJwtPayload2(token);
|
|
1234
|
+
if (!claims) {
|
|
1235
|
+
return void 0;
|
|
1236
|
+
}
|
|
1237
|
+
return readDeviceIdFromClaims(claims);
|
|
1238
|
+
};
|
|
1239
|
+
if (authClient.onAuthChange) {
|
|
1240
|
+
binding.subscribe = (listener) => authClient.onAuthChange?.(() => listener()) ?? (() => {
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
return binding;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
// src/client/org-client.ts
|
|
1247
|
+
var import_core6 = require("@korajs/core");
|
|
1248
|
+
var OrgClientError = class extends import_core6.KoraError {
|
|
1249
|
+
constructor(message, code, context) {
|
|
1250
|
+
super(message, code, context);
|
|
1251
|
+
this.name = "OrgClientError";
|
|
1252
|
+
}
|
|
1253
|
+
};
|
|
1254
|
+
var OrgClient = class {
|
|
1255
|
+
serverUrl;
|
|
1256
|
+
getAccessToken;
|
|
1257
|
+
listeners = /* @__PURE__ */ new Set();
|
|
1258
|
+
_activeOrgId = null;
|
|
1259
|
+
_activeOrg = null;
|
|
1260
|
+
_activeRole = null;
|
|
1261
|
+
constructor(config) {
|
|
1262
|
+
this.serverUrl = config.serverUrl.replace(/\/+$/, "");
|
|
1263
|
+
this.getAccessToken = config.getAccessToken;
|
|
1264
|
+
}
|
|
1265
|
+
// --- Getters ---
|
|
1266
|
+
/** Currently active organization ID */
|
|
1267
|
+
get activeOrgId() {
|
|
1268
|
+
return this._activeOrgId;
|
|
1269
|
+
}
|
|
1270
|
+
/** Currently active organization */
|
|
1271
|
+
get activeOrg() {
|
|
1272
|
+
return this._activeOrg;
|
|
1273
|
+
}
|
|
1274
|
+
/** Current user's role in the active organization */
|
|
1275
|
+
get activeRole() {
|
|
1276
|
+
return this._activeRole;
|
|
1277
|
+
}
|
|
1278
|
+
// --- Organization Operations ---
|
|
1279
|
+
/**
|
|
1280
|
+
* Create a new organization.
|
|
1281
|
+
*/
|
|
1282
|
+
async createOrg(params) {
|
|
1283
|
+
return this.request("/orgs", {
|
|
1284
|
+
method: "POST",
|
|
1285
|
+
body: params
|
|
1286
|
+
});
|
|
1287
|
+
}
|
|
1288
|
+
/**
|
|
1289
|
+
* List all organizations the current user belongs to.
|
|
1290
|
+
*/
|
|
1291
|
+
async listOrgs() {
|
|
1292
|
+
return this.request("/orgs", { method: "GET" });
|
|
1293
|
+
}
|
|
1294
|
+
/**
|
|
1295
|
+
* Get an organization by ID.
|
|
1296
|
+
*/
|
|
1297
|
+
async getOrg(orgId) {
|
|
1298
|
+
return this.request(`/orgs/${orgId}`, { method: "GET" });
|
|
1299
|
+
}
|
|
1300
|
+
/**
|
|
1301
|
+
* Update an organization.
|
|
1302
|
+
*/
|
|
1303
|
+
async updateOrg(orgId, params) {
|
|
1304
|
+
const result = await this.request(`/orgs/${orgId}`, {
|
|
1305
|
+
method: "PATCH",
|
|
1306
|
+
body: params
|
|
1307
|
+
});
|
|
1308
|
+
if (this._activeOrgId === orgId) {
|
|
1309
|
+
this._activeOrg = result;
|
|
1310
|
+
}
|
|
1311
|
+
return result;
|
|
1312
|
+
}
|
|
1313
|
+
/**
|
|
1314
|
+
* Delete an organization.
|
|
1315
|
+
*/
|
|
1316
|
+
async deleteOrg(orgId) {
|
|
1317
|
+
await this.request(`/orgs/${orgId}`, { method: "DELETE" });
|
|
1318
|
+
if (this._activeOrgId === orgId) {
|
|
1319
|
+
this._activeOrgId = null;
|
|
1320
|
+
this._activeOrg = null;
|
|
1321
|
+
this._activeRole = null;
|
|
1322
|
+
this.notifyListeners();
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
// --- Org Switching ---
|
|
1326
|
+
/**
|
|
1327
|
+
* Switch the active organization context.
|
|
1328
|
+
* Fetches the org details and the user's membership/role.
|
|
1329
|
+
*/
|
|
1330
|
+
async switchOrg(orgId) {
|
|
1331
|
+
const org = await this.request(`/orgs/${orgId}`, { method: "GET" });
|
|
1332
|
+
const membership = await this.request(`/orgs/${orgId}/membership`, {
|
|
1333
|
+
method: "GET"
|
|
1334
|
+
});
|
|
1335
|
+
this._activeOrgId = orgId;
|
|
1336
|
+
this._activeOrg = org;
|
|
1337
|
+
this._activeRole = membership.role;
|
|
1338
|
+
this.notifyListeners();
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Clear the active organization (no org selected).
|
|
1342
|
+
*/
|
|
1343
|
+
clearActiveOrg() {
|
|
1344
|
+
this._activeOrgId = null;
|
|
1345
|
+
this._activeOrg = null;
|
|
1346
|
+
this._activeRole = null;
|
|
1347
|
+
this.notifyListeners();
|
|
1348
|
+
}
|
|
1349
|
+
// --- Member Management ---
|
|
1350
|
+
/**
|
|
1351
|
+
* List members of an organization.
|
|
1352
|
+
*/
|
|
1353
|
+
async listMembers(orgId) {
|
|
1354
|
+
return this.request(`/orgs/${orgId}/members`, { method: "GET" });
|
|
1355
|
+
}
|
|
1356
|
+
/**
|
|
1357
|
+
* Remove a member from an organization.
|
|
1358
|
+
*/
|
|
1359
|
+
async removeMember(orgId, userId) {
|
|
1360
|
+
await this.request(`/orgs/${orgId}/members/${userId}`, { method: "DELETE" });
|
|
1361
|
+
}
|
|
1362
|
+
/**
|
|
1363
|
+
* Update a member's role.
|
|
1364
|
+
*/
|
|
1365
|
+
async updateMemberRole(orgId, userId, role) {
|
|
1366
|
+
return this.request(`/orgs/${orgId}/members/${userId}/role`, {
|
|
1367
|
+
method: "PATCH",
|
|
1368
|
+
body: { role }
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1371
|
+
/**
|
|
1372
|
+
* Transfer ownership to another member.
|
|
1373
|
+
*/
|
|
1374
|
+
async transferOwnership(orgId, newOwnerId) {
|
|
1375
|
+
await this.request(`/orgs/${orgId}/transfer`, {
|
|
1376
|
+
method: "POST",
|
|
1377
|
+
body: { newOwnerId }
|
|
1045
1378
|
});
|
|
1046
1379
|
}
|
|
1047
|
-
/**
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
reject(
|
|
1068
|
-
new DeviceKeyStoreError(
|
|
1069
|
-
`Failed to delete key pair for device "${deviceId}".`,
|
|
1070
|
-
{
|
|
1071
|
-
deviceId,
|
|
1072
|
-
cause: cause instanceof Error ? cause.message : String(cause)
|
|
1073
|
-
}
|
|
1074
|
-
)
|
|
1075
|
-
);
|
|
1076
|
-
}
|
|
1380
|
+
/**
|
|
1381
|
+
* Leave an organization (remove yourself).
|
|
1382
|
+
*/
|
|
1383
|
+
async leaveOrg(orgId) {
|
|
1384
|
+
await this.request(`/orgs/${orgId}/leave`, { method: "POST" });
|
|
1385
|
+
if (this._activeOrgId === orgId) {
|
|
1386
|
+
this._activeOrgId = null;
|
|
1387
|
+
this._activeOrg = null;
|
|
1388
|
+
this._activeRole = null;
|
|
1389
|
+
this.notifyListeners();
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
// --- Invitations ---
|
|
1393
|
+
/**
|
|
1394
|
+
* Invite a user to an organization by email.
|
|
1395
|
+
*/
|
|
1396
|
+
async inviteMember(orgId, params) {
|
|
1397
|
+
return this.request(`/orgs/${orgId}/invitations`, {
|
|
1398
|
+
method: "POST",
|
|
1399
|
+
body: params
|
|
1077
1400
|
});
|
|
1078
1401
|
}
|
|
1079
|
-
/**
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
const request = store.count(deviceId);
|
|
1087
|
-
request.onsuccess = () => {
|
|
1088
|
-
resolve(request.result > 0);
|
|
1089
|
-
};
|
|
1090
|
-
request.onerror = () => {
|
|
1091
|
-
reject(
|
|
1092
|
-
new DeviceKeyStoreError(
|
|
1093
|
-
`Failed to check if key pair exists for device "${deviceId}".`,
|
|
1094
|
-
{ deviceId, error: request.error?.message }
|
|
1095
|
-
)
|
|
1096
|
-
);
|
|
1097
|
-
};
|
|
1098
|
-
} catch (cause) {
|
|
1099
|
-
reject(
|
|
1100
|
-
new DeviceKeyStoreError(
|
|
1101
|
-
`Failed to check if key pair exists for device "${deviceId}".`,
|
|
1102
|
-
{
|
|
1103
|
-
deviceId,
|
|
1104
|
-
cause: cause instanceof Error ? cause.message : String(cause)
|
|
1105
|
-
}
|
|
1106
|
-
)
|
|
1107
|
-
);
|
|
1108
|
-
}
|
|
1402
|
+
/**
|
|
1403
|
+
* Accept an invitation by token.
|
|
1404
|
+
*/
|
|
1405
|
+
async acceptInvitation(token) {
|
|
1406
|
+
return this.request("/invitations/accept", {
|
|
1407
|
+
method: "POST",
|
|
1408
|
+
body: { token }
|
|
1109
1409
|
});
|
|
1110
1410
|
}
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
this.store.set(deviceId, keyPair);
|
|
1411
|
+
/**
|
|
1412
|
+
* List pending invitations for an organization.
|
|
1413
|
+
*/
|
|
1414
|
+
async listInvitations(orgId) {
|
|
1415
|
+
return this.request(`/orgs/${orgId}/invitations`, { method: "GET" });
|
|
1117
1416
|
}
|
|
1118
|
-
/**
|
|
1119
|
-
|
|
1120
|
-
|
|
1417
|
+
/**
|
|
1418
|
+
* Revoke a pending invitation.
|
|
1419
|
+
*/
|
|
1420
|
+
async revokeInvitation(orgId, invitationId) {
|
|
1421
|
+
await this.request(`/orgs/${orgId}/invitations/${invitationId}`, { method: "DELETE" });
|
|
1121
1422
|
}
|
|
1122
|
-
/**
|
|
1123
|
-
|
|
1124
|
-
|
|
1423
|
+
/**
|
|
1424
|
+
* List pending invitations for the current user's email.
|
|
1425
|
+
*/
|
|
1426
|
+
async listMyInvitations(email) {
|
|
1427
|
+
return this.request(`/invitations?email=${encodeURIComponent(email)}`, {
|
|
1428
|
+
method: "GET"
|
|
1429
|
+
});
|
|
1125
1430
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1431
|
+
// --- Subscriptions ---
|
|
1432
|
+
/**
|
|
1433
|
+
* Subscribe to active org changes.
|
|
1434
|
+
* @returns Unsubscribe function
|
|
1435
|
+
*/
|
|
1436
|
+
onOrgChange(callback) {
|
|
1437
|
+
this.listeners.add(callback);
|
|
1438
|
+
return () => {
|
|
1439
|
+
this.listeners.delete(callback);
|
|
1440
|
+
};
|
|
1129
1441
|
}
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1442
|
+
// --- Internal ---
|
|
1443
|
+
notifyListeners() {
|
|
1444
|
+
for (const listener of this.listeners) {
|
|
1445
|
+
try {
|
|
1446
|
+
listener(this._activeOrgId);
|
|
1447
|
+
} catch {
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1134
1450
|
}
|
|
1135
|
-
|
|
1136
|
-
|
|
1451
|
+
async request(path, options) {
|
|
1452
|
+
const token = await this.getAccessToken();
|
|
1453
|
+
if (!token) {
|
|
1454
|
+
throw new OrgClientError(
|
|
1455
|
+
"Not authenticated. Sign in before performing organization operations.",
|
|
1456
|
+
"ORG_NOT_AUTHENTICATED"
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1459
|
+
const url = `${this.serverUrl}${path}`;
|
|
1460
|
+
const headers = {
|
|
1461
|
+
Authorization: `Bearer ${token}`
|
|
1462
|
+
};
|
|
1463
|
+
if (options.body) {
|
|
1464
|
+
headers["Content-Type"] = "application/json";
|
|
1465
|
+
}
|
|
1466
|
+
let response;
|
|
1467
|
+
try {
|
|
1468
|
+
response = await fetch(url, {
|
|
1469
|
+
method: options.method,
|
|
1470
|
+
headers,
|
|
1471
|
+
body: options.body ? JSON.stringify(options.body) : void 0
|
|
1472
|
+
});
|
|
1473
|
+
} catch (cause) {
|
|
1474
|
+
throw new OrgClientError(`Network request to ${path} failed.`, "ORG_NETWORK_ERROR", {
|
|
1475
|
+
path,
|
|
1476
|
+
cause: cause instanceof Error ? cause.message : String(cause)
|
|
1477
|
+
});
|
|
1478
|
+
}
|
|
1479
|
+
if (!response.ok) {
|
|
1480
|
+
let errorMessage = `Server returned HTTP ${response.status}`;
|
|
1481
|
+
try {
|
|
1482
|
+
const body = await response.json();
|
|
1483
|
+
if (typeof body.error === "string") {
|
|
1484
|
+
errorMessage = body.error;
|
|
1485
|
+
}
|
|
1486
|
+
} catch {
|
|
1487
|
+
}
|
|
1488
|
+
throw new OrgClientError(errorMessage, "ORG_SERVER_ERROR", {
|
|
1489
|
+
path,
|
|
1490
|
+
status: response.status
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
const text = await response.text();
|
|
1494
|
+
if (text.length === 0) return void 0;
|
|
1495
|
+
try {
|
|
1496
|
+
const body = JSON.parse(text);
|
|
1497
|
+
if (body && typeof body === "object" && "data" in body) {
|
|
1498
|
+
return body.data;
|
|
1499
|
+
}
|
|
1500
|
+
return body;
|
|
1501
|
+
} catch {
|
|
1502
|
+
return void 0;
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
};
|
|
1137
1506
|
|
|
1138
1507
|
// src/tokens/token-store.ts
|
|
1139
1508
|
var DEFAULT_STORAGE_KEY = "kora_auth_tokens";
|
|
@@ -1210,15 +1579,15 @@ var TokenStore = class {
|
|
|
1210
1579
|
return null;
|
|
1211
1580
|
}
|
|
1212
1581
|
const record = parsed;
|
|
1213
|
-
if (typeof record
|
|
1582
|
+
if (typeof record.accessToken !== "string" || typeof record.refreshToken !== "string") {
|
|
1214
1583
|
return null;
|
|
1215
1584
|
}
|
|
1216
1585
|
const tokens = {
|
|
1217
|
-
accessToken: record
|
|
1218
|
-
refreshToken: record
|
|
1586
|
+
accessToken: record.accessToken,
|
|
1587
|
+
refreshToken: record.refreshToken
|
|
1219
1588
|
};
|
|
1220
|
-
if (typeof record
|
|
1221
|
-
tokens.deviceCredential = record
|
|
1589
|
+
if (typeof record.deviceCredential === "string") {
|
|
1590
|
+
tokens.deviceCredential = record.deviceCredential;
|
|
1222
1591
|
}
|
|
1223
1592
|
return tokens;
|
|
1224
1593
|
} catch {
|
|
@@ -1258,17 +1627,17 @@ var TokenStore = class {
|
|
|
1258
1627
|
};
|
|
1259
1628
|
|
|
1260
1629
|
// src/tokens/encrypted-token-store.ts
|
|
1261
|
-
var
|
|
1630
|
+
var import_core8 = require("@korajs/core");
|
|
1262
1631
|
|
|
1263
1632
|
// src/encryption/database-encryption.ts
|
|
1264
|
-
var
|
|
1265
|
-
var EncryptionError = class extends
|
|
1633
|
+
var import_core7 = require("@korajs/core");
|
|
1634
|
+
var EncryptionError = class extends import_core7.KoraError {
|
|
1266
1635
|
constructor(message, context) {
|
|
1267
1636
|
super(message, "ENCRYPTION_ERROR", context);
|
|
1268
1637
|
this.name = "EncryptionError";
|
|
1269
1638
|
}
|
|
1270
1639
|
};
|
|
1271
|
-
var CryptoUnavailableError2 = class extends
|
|
1640
|
+
var CryptoUnavailableError2 = class extends import_core7.KoraError {
|
|
1272
1641
|
constructor() {
|
|
1273
1642
|
super(
|
|
1274
1643
|
"Web Crypto API (crypto.subtle) is not available in this environment. Database encryption requires crypto.subtle, which is available in modern browsers and Node.js 20+. If running in SSR, ensure your runtime provides the Web Crypto API.",
|
|
@@ -1376,7 +1745,7 @@ async function importKey(rawKey) {
|
|
|
1376
1745
|
}
|
|
1377
1746
|
|
|
1378
1747
|
// src/tokens/encrypted-token-store.ts
|
|
1379
|
-
var EncryptedTokenStoreError = class extends
|
|
1748
|
+
var EncryptedTokenStoreError = class extends import_core8.KoraError {
|
|
1380
1749
|
constructor(message, context) {
|
|
1381
1750
|
super(message, "ENCRYPTED_TOKEN_STORE_ERROR", context);
|
|
1382
1751
|
this.name = "EncryptedTokenStoreError";
|
|
@@ -1507,11 +1876,11 @@ var EncryptedTokenStore = class {
|
|
|
1507
1876
|
return null;
|
|
1508
1877
|
}
|
|
1509
1878
|
const record = parsed;
|
|
1510
|
-
if (typeof record
|
|
1879
|
+
if (typeof record.iv !== "string" || typeof record.data !== "string") {
|
|
1511
1880
|
return null;
|
|
1512
1881
|
}
|
|
1513
|
-
const iv = fromBase64Url2(record
|
|
1514
|
-
const ciphertext = fromBase64Url2(record
|
|
1882
|
+
const iv = fromBase64Url2(record.iv);
|
|
1883
|
+
const ciphertext = fromBase64Url2(record.data);
|
|
1515
1884
|
const plaintextBytes = await decryptData(this.key, ciphertext, iv);
|
|
1516
1885
|
const json = new TextDecoder().decode(plaintextBytes);
|
|
1517
1886
|
const tokenData = JSON.parse(json);
|
|
@@ -1519,15 +1888,15 @@ var EncryptedTokenStore = class {
|
|
|
1519
1888
|
return null;
|
|
1520
1889
|
}
|
|
1521
1890
|
const tokenRecord = tokenData;
|
|
1522
|
-
if (typeof tokenRecord
|
|
1891
|
+
if (typeof tokenRecord.accessToken !== "string" || typeof tokenRecord.refreshToken !== "string") {
|
|
1523
1892
|
return null;
|
|
1524
1893
|
}
|
|
1525
1894
|
const tokens = {
|
|
1526
|
-
accessToken: tokenRecord
|
|
1527
|
-
refreshToken: tokenRecord
|
|
1895
|
+
accessToken: tokenRecord.accessToken,
|
|
1896
|
+
refreshToken: tokenRecord.refreshToken
|
|
1528
1897
|
};
|
|
1529
|
-
if (typeof tokenRecord
|
|
1530
|
-
tokens.deviceCredential = tokenRecord
|
|
1898
|
+
if (typeof tokenRecord.deviceCredential === "string") {
|
|
1899
|
+
tokens.deviceCredential = tokenRecord.deviceCredential;
|
|
1531
1900
|
}
|
|
1532
1901
|
return tokens;
|
|
1533
1902
|
} catch {
|
|
@@ -1571,14 +1940,14 @@ var EncryptedTokenStore = class {
|
|
|
1571
1940
|
};
|
|
1572
1941
|
|
|
1573
1942
|
// src/passkey/passkey-client.ts
|
|
1574
|
-
var
|
|
1575
|
-
var PasskeyError = class extends
|
|
1943
|
+
var import_core9 = require("@korajs/core");
|
|
1944
|
+
var PasskeyError = class extends import_core9.KoraError {
|
|
1576
1945
|
constructor(message, context) {
|
|
1577
1946
|
super(message, "PASSKEY_ERROR", context);
|
|
1578
1947
|
this.name = "PasskeyError";
|
|
1579
1948
|
}
|
|
1580
1949
|
};
|
|
1581
|
-
var PasskeyUnsupportedError = class extends
|
|
1950
|
+
var PasskeyUnsupportedError = class extends import_core9.KoraError {
|
|
1582
1951
|
constructor() {
|
|
1583
1952
|
super(
|
|
1584
1953
|
"WebAuthn is not supported in this browser. Passkey authentication requires a modern browser with WebAuthn support.",
|
|
@@ -1753,9 +2122,7 @@ function extractPublicKeyFromAttestationObject(attestationObject) {
|
|
|
1753
2122
|
const topMap = decoded.value;
|
|
1754
2123
|
const authData = topMap.get("authData");
|
|
1755
2124
|
if (!(authData instanceof Uint8Array)) {
|
|
1756
|
-
throw new PasskeyError(
|
|
1757
|
-
"Invalid attestation object: authData is missing or not a byte string."
|
|
1758
|
-
);
|
|
2125
|
+
throw new PasskeyError("Invalid attestation object: authData is missing or not a byte string.");
|
|
1759
2126
|
}
|
|
1760
2127
|
let offset = 0;
|
|
1761
2128
|
offset += 32;
|
|
@@ -1777,53 +2144,54 @@ function extractPublicKeyFromAttestationObject(attestationObject) {
|
|
|
1777
2144
|
return authData.slice(offset, offset + coseKeyLength);
|
|
1778
2145
|
}
|
|
1779
2146
|
function decodeCbor(data, offset) {
|
|
1780
|
-
|
|
2147
|
+
let pos = offset;
|
|
2148
|
+
if (pos >= data.length) {
|
|
1781
2149
|
throw new PasskeyError("CBOR decode error: unexpected end of data.");
|
|
1782
2150
|
}
|
|
1783
|
-
const initialByte = data[
|
|
2151
|
+
const initialByte = data[pos];
|
|
1784
2152
|
const majorType = initialByte >> 5;
|
|
1785
2153
|
const additionalInfo = initialByte & 31;
|
|
1786
|
-
|
|
2154
|
+
pos += 1;
|
|
1787
2155
|
let argument;
|
|
1788
2156
|
if (additionalInfo < 24) {
|
|
1789
2157
|
argument = additionalInfo;
|
|
1790
2158
|
} else if (additionalInfo === 24) {
|
|
1791
|
-
argument = data[
|
|
1792
|
-
|
|
2159
|
+
argument = data[pos];
|
|
2160
|
+
pos += 1;
|
|
1793
2161
|
} else if (additionalInfo === 25) {
|
|
1794
|
-
argument = data[
|
|
1795
|
-
|
|
2162
|
+
argument = data[pos] << 8 | data[pos + 1];
|
|
2163
|
+
pos += 2;
|
|
1796
2164
|
} else if (additionalInfo === 26) {
|
|
1797
|
-
argument = data[
|
|
2165
|
+
argument = data[pos] << 24 | data[pos + 1] << 16 | data[pos + 2] << 8 | data[pos + 3];
|
|
1798
2166
|
argument = argument >>> 0;
|
|
1799
|
-
|
|
2167
|
+
pos += 4;
|
|
1800
2168
|
} else {
|
|
1801
2169
|
throw new PasskeyError(
|
|
1802
|
-
`CBOR decode error: unsupported additional info ${additionalInfo} at byte ${
|
|
2170
|
+
`CBOR decode error: unsupported additional info ${additionalInfo} at byte ${pos - 1}. This CBOR decoder only supports definite-length encodings.`
|
|
1803
2171
|
);
|
|
1804
2172
|
}
|
|
1805
2173
|
switch (majorType) {
|
|
1806
2174
|
// Major type 0: Unsigned integer
|
|
1807
2175
|
case 0:
|
|
1808
|
-
return { value: argument, offset };
|
|
2176
|
+
return { value: argument, offset: pos };
|
|
1809
2177
|
// Major type 1: Negative integer (-1 - argument)
|
|
1810
2178
|
case 1:
|
|
1811
|
-
return { value: -1 - argument, offset };
|
|
2179
|
+
return { value: -1 - argument, offset: pos };
|
|
1812
2180
|
// Major type 2: Byte string
|
|
1813
2181
|
case 2: {
|
|
1814
|
-
const bytes = data.slice(
|
|
1815
|
-
return { value: bytes, offset:
|
|
2182
|
+
const bytes = data.slice(pos, pos + argument);
|
|
2183
|
+
return { value: bytes, offset: pos + argument };
|
|
1816
2184
|
}
|
|
1817
2185
|
// Major type 3: Text string (UTF-8)
|
|
1818
2186
|
case 3: {
|
|
1819
|
-
const textBytes = data.slice(
|
|
2187
|
+
const textBytes = data.slice(pos, pos + argument);
|
|
1820
2188
|
const text = new TextDecoder().decode(textBytes);
|
|
1821
|
-
return { value: text, offset:
|
|
2189
|
+
return { value: text, offset: pos + argument };
|
|
1822
2190
|
}
|
|
1823
2191
|
// Major type 4: Array
|
|
1824
2192
|
case 4: {
|
|
1825
2193
|
const arr = [];
|
|
1826
|
-
let currentOffset =
|
|
2194
|
+
let currentOffset = pos;
|
|
1827
2195
|
for (let i = 0; i < argument; i++) {
|
|
1828
2196
|
const item = decodeCbor(data, currentOffset);
|
|
1829
2197
|
arr.push(item.value);
|
|
@@ -1834,7 +2202,7 @@ function decodeCbor(data, offset) {
|
|
|
1834
2202
|
// Major type 5: Map
|
|
1835
2203
|
case 5: {
|
|
1836
2204
|
const map = /* @__PURE__ */ new Map();
|
|
1837
|
-
let currentOffset =
|
|
2205
|
+
let currentOffset = pos;
|
|
1838
2206
|
for (let i = 0; i < argument; i++) {
|
|
1839
2207
|
const keyResult = decodeCbor(data, currentOffset);
|
|
1840
2208
|
const valResult = decodeCbor(data, keyResult.offset);
|
|
@@ -1845,14 +2213,14 @@ function decodeCbor(data, offset) {
|
|
|
1845
2213
|
}
|
|
1846
2214
|
default:
|
|
1847
2215
|
throw new PasskeyError(
|
|
1848
|
-
`CBOR decode error: unsupported major type ${majorType} at byte ${
|
|
2216
|
+
`CBOR decode error: unsupported major type ${majorType} at byte ${pos - 1}. This CBOR decoder only supports types 0-5 (integers, byte/text strings, arrays, maps).`
|
|
1849
2217
|
);
|
|
1850
2218
|
}
|
|
1851
2219
|
}
|
|
1852
2220
|
|
|
1853
2221
|
// src/encryption/key-derivation.ts
|
|
1854
|
-
var
|
|
1855
|
-
var KeyDerivationError = class extends
|
|
2222
|
+
var import_core10 = require("@korajs/core");
|
|
2223
|
+
var KeyDerivationError = class extends import_core10.KoraError {
|
|
1856
2224
|
constructor(message, context) {
|
|
1857
2225
|
super(message, "KEY_DERIVATION_ERROR", context);
|
|
1858
2226
|
this.name = "KeyDerivationError";
|
|
@@ -2032,10 +2400,10 @@ var AutoLockManager = class {
|
|
|
2032
2400
|
};
|
|
2033
2401
|
|
|
2034
2402
|
// src/encryption/operation-encryptor.ts
|
|
2035
|
-
var
|
|
2403
|
+
var import_core11 = require("@korajs/core");
|
|
2036
2404
|
var ENCRYPTED_MARKER = "__kora_encrypted";
|
|
2037
2405
|
var ENCRYPTION_VERSION = 1;
|
|
2038
|
-
var OperationEncryptionError = class extends
|
|
2406
|
+
var OperationEncryptionError = class extends import_core11.KoraError {
|
|
2039
2407
|
constructor(message, context) {
|
|
2040
2408
|
super(message, "OPERATION_ENCRYPTION_ERROR", context);
|
|
2041
2409
|
this.name = "OperationEncryptionError";
|
|
@@ -2170,14 +2538,11 @@ var OperationEncryptor = class {
|
|
|
2170
2538
|
if (cause instanceof OperationEncryptionError) {
|
|
2171
2539
|
throw cause;
|
|
2172
2540
|
}
|
|
2173
|
-
throw new OperationEncryptionError(
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
cause: cause instanceof Error ? cause.message : String(cause)
|
|
2179
|
-
}
|
|
2180
|
-
);
|
|
2541
|
+
throw new OperationEncryptionError(`Failed to encrypt operation ${fieldName} field.`, {
|
|
2542
|
+
operationId,
|
|
2543
|
+
fieldName,
|
|
2544
|
+
cause: cause instanceof Error ? cause.message : String(cause)
|
|
2545
|
+
});
|
|
2181
2546
|
}
|
|
2182
2547
|
}
|
|
2183
2548
|
async decryptField(field, operationId, fieldName) {
|
|
@@ -2201,10 +2566,10 @@ var OperationEncryptor = class {
|
|
|
2201
2566
|
const json = new TextDecoder().decode(plaintextBytes);
|
|
2202
2567
|
const parsed = JSON.parse(json);
|
|
2203
2568
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
2204
|
-
throw new OperationEncryptionError(
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
);
|
|
2569
|
+
throw new OperationEncryptionError(`Decrypted ${fieldName} is not a valid record object.`, {
|
|
2570
|
+
operationId,
|
|
2571
|
+
fieldName
|
|
2572
|
+
});
|
|
2208
2573
|
}
|
|
2209
2574
|
return parsed;
|
|
2210
2575
|
} catch (cause) {
|
|
@@ -2229,11 +2594,12 @@ function isEncryptedEnvelope(field) {
|
|
|
2229
2594
|
if (field === null || typeof field !== "object") {
|
|
2230
2595
|
return false;
|
|
2231
2596
|
}
|
|
2232
|
-
return field[ENCRYPTED_MARKER] === true && typeof field
|
|
2597
|
+
return field[ENCRYPTED_MARKER] === true && typeof field.ciphertext === "string" && typeof field.iv === "string" && field.algorithm === "AES-256-GCM";
|
|
2233
2598
|
}
|
|
2234
2599
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2235
2600
|
0 && (module.exports = {
|
|
2236
2601
|
AuthClient,
|
|
2602
|
+
AuthDeviceIdentityError,
|
|
2237
2603
|
AuthError,
|
|
2238
2604
|
AutoLockManager,
|
|
2239
2605
|
CryptoUnavailableError,
|
|
@@ -2254,8 +2620,14 @@ function isEncryptedEnvelope(field) {
|
|
|
2254
2620
|
TokenStore,
|
|
2255
2621
|
authenticateWithPasskey,
|
|
2256
2622
|
computePublicKeyThumbprint,
|
|
2623
|
+
createAuthTokenStorage,
|
|
2257
2624
|
createDeviceKeyStore,
|
|
2625
|
+
createKoraAuth,
|
|
2626
|
+
createKoraAuthSync,
|
|
2627
|
+
createMemoryAuthTokenStorage,
|
|
2258
2628
|
createPasskeyCredential,
|
|
2629
|
+
createPersistentDeviceIdentity,
|
|
2630
|
+
createWebStorageAuthTokenStorage,
|
|
2259
2631
|
decryptData,
|
|
2260
2632
|
deriveEncryptionKey,
|
|
2261
2633
|
encryptData,
|