@korajs/auth 0.4.0 → 0.6.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-7OXBRSJL.js +203 -0
- package/dist/chunk-7OXBRSJL.js.map +1 -0
- package/dist/create-org-session-RsDj9cl4.d.cts +722 -0
- package/dist/create-org-session-RsDj9cl4.d.ts +722 -0
- package/dist/index.cjs +1146 -586
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +67 -118
- package/dist/index.d.ts +67 -118
- package/dist/index.js +654 -258
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +297 -183
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +25 -209
- package/dist/react.d.ts +25 -209
- package/dist/react.js +134 -209
- package/dist/react.js.map +1 -1
- package/dist/server.cjs +1611 -730
- 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 +1565 -704
- package/dist/server.js.map +1 -1
- package/dist/svelte.cjs +512 -0
- package/dist/svelte.cjs.map +1 -0
- package/dist/svelte.d.cts +100 -0
- package/dist/svelte.d.ts +100 -0
- package/dist/svelte.js +278 -0
- package/dist/svelte.js.map +1 -0
- package/dist/vue.cjs +565 -0
- package/dist/vue.cjs.map +1 -0
- package/dist/vue.d.cts +136 -0
- package/dist/vue.d.ts +136 -0
- package/dist/vue.js +338 -0
- package/dist/vue.js.map +1 -0
- package/package.json +40 -6
- package/src/svelte/AuthProvider.svelte +37 -0
- package/src/svelte/OrgProvider.svelte +22 -0
- package/dist/org-client-BVTLKcIk.d.cts +0 -355
- package/dist/org-client-BVTLKcIk.d.ts +0 -355
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,
|
|
@@ -40,9 +41,18 @@ __export(index_exports, {
|
|
|
40
41
|
PasskeyUnsupportedError: () => PasskeyUnsupportedError,
|
|
41
42
|
TokenStore: () => TokenStore,
|
|
42
43
|
authenticateWithPasskey: () => authenticateWithPasskey,
|
|
44
|
+
checkOrgPermission: () => checkOrgPermission,
|
|
43
45
|
computePublicKeyThumbprint: () => computePublicKeyThumbprint,
|
|
46
|
+
createAuthSession: () => createAuthSession,
|
|
47
|
+
createAuthTokenStorage: () => createAuthTokenStorage,
|
|
44
48
|
createDeviceKeyStore: () => createDeviceKeyStore,
|
|
49
|
+
createKoraAuth: () => createKoraAuth,
|
|
50
|
+
createKoraAuthSync: () => createKoraAuthSync,
|
|
51
|
+
createMemoryAuthTokenStorage: () => createMemoryAuthTokenStorage,
|
|
52
|
+
createOrgSession: () => createOrgSession,
|
|
45
53
|
createPasskeyCredential: () => createPasskeyCredential,
|
|
54
|
+
createPersistentDeviceIdentity: () => createPersistentDeviceIdentity,
|
|
55
|
+
createWebStorageAuthTokenStorage: () => createWebStorageAuthTokenStorage,
|
|
46
56
|
decryptData: () => decryptData,
|
|
47
57
|
deriveEncryptionKey: () => deriveEncryptionKey,
|
|
48
58
|
encryptData: () => encryptData,
|
|
@@ -99,6 +109,36 @@ function getUserIdFromToken(token) {
|
|
|
99
109
|
}
|
|
100
110
|
return payload.sub;
|
|
101
111
|
}
|
|
112
|
+
function getDefaultFetch() {
|
|
113
|
+
if (typeof globalThis.fetch !== "function") {
|
|
114
|
+
return async () => {
|
|
115
|
+
throw new AuthError(
|
|
116
|
+
"No fetch implementation is available in this runtime. Pass `fetch` to AuthClientConfig.",
|
|
117
|
+
"AUTH_FETCH_UNAVAILABLE"
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return globalThis.fetch.bind(globalThis);
|
|
122
|
+
}
|
|
123
|
+
function normalizeAuthUser(user) {
|
|
124
|
+
return {
|
|
125
|
+
id: user.id,
|
|
126
|
+
email: user.email,
|
|
127
|
+
name: user.name ?? null
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function canRedirectCurrentWindow() {
|
|
131
|
+
return typeof globalThis.window !== "undefined" && typeof globalThis.window.location?.assign === "function";
|
|
132
|
+
}
|
|
133
|
+
function redirectCurrentWindow(url) {
|
|
134
|
+
if (!canRedirectCurrentWindow()) {
|
|
135
|
+
throw new AuthError(
|
|
136
|
+
"OAuth redirect is not available in this runtime. Pass redirect: false and open the returned URL with your platform browser API.",
|
|
137
|
+
"AUTH_OAUTH_REDIRECT_UNAVAILABLE"
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
globalThis.window.location.assign(url);
|
|
141
|
+
}
|
|
102
142
|
function createTokenStorage(prefix) {
|
|
103
143
|
let useLocalStorage = false;
|
|
104
144
|
try {
|
|
@@ -152,6 +192,8 @@ function createTokenStorage(prefix) {
|
|
|
152
192
|
var AuthClient = class {
|
|
153
193
|
serverUrl;
|
|
154
194
|
storage;
|
|
195
|
+
fetchFn;
|
|
196
|
+
deviceIdentity;
|
|
155
197
|
listeners = /* @__PURE__ */ new Set();
|
|
156
198
|
_state = "loading";
|
|
157
199
|
_user = null;
|
|
@@ -165,7 +207,9 @@ var AuthClient = class {
|
|
|
165
207
|
constructor(config) {
|
|
166
208
|
this.serverUrl = config.serverUrl.replace(/\/+$/, "");
|
|
167
209
|
const prefix = config.storageKey ?? "kora_auth";
|
|
168
|
-
this.storage = createTokenStorage(prefix);
|
|
210
|
+
this.storage = config.storage ?? createTokenStorage(prefix);
|
|
211
|
+
this.fetchFn = config.fetch ?? getDefaultFetch();
|
|
212
|
+
this.deviceIdentity = config.deviceIdentity;
|
|
169
213
|
}
|
|
170
214
|
// -----------------------------------------------------------------------
|
|
171
215
|
// Public getters
|
|
@@ -197,8 +241,8 @@ var AuthClient = class {
|
|
|
197
241
|
return;
|
|
198
242
|
}
|
|
199
243
|
this._initialized = true;
|
|
200
|
-
const accessToken = this.storage.getAccessToken();
|
|
201
|
-
const refreshToken = this.storage.getRefreshToken();
|
|
244
|
+
const accessToken = await this.storage.getAccessToken();
|
|
245
|
+
const refreshToken = await this.storage.getRefreshToken();
|
|
202
246
|
if (!accessToken || !refreshToken) {
|
|
203
247
|
this.setState("unauthenticated", null);
|
|
204
248
|
return;
|
|
@@ -215,7 +259,7 @@ var AuthClient = class {
|
|
|
215
259
|
}
|
|
216
260
|
} catch {
|
|
217
261
|
}
|
|
218
|
-
this.storage.clear();
|
|
262
|
+
await this.storage.clear();
|
|
219
263
|
this.setState("unauthenticated", null);
|
|
220
264
|
}
|
|
221
265
|
// -----------------------------------------------------------------------
|
|
@@ -229,12 +273,13 @@ var AuthClient = class {
|
|
|
229
273
|
* @throws {AuthError} If the request fails or the server returns an error
|
|
230
274
|
*/
|
|
231
275
|
async signUp(params) {
|
|
276
|
+
const body = await this.withDeviceIdentity(params);
|
|
232
277
|
const response = await this.request("/auth/signup", {
|
|
233
278
|
method: "POST",
|
|
234
|
-
body
|
|
279
|
+
body
|
|
235
280
|
});
|
|
236
281
|
const tokens = "tokens" in response ? response.tokens : response;
|
|
237
|
-
this.storage.setTokens(tokens.accessToken, tokens.refreshToken);
|
|
282
|
+
await this.storage.setTokens(tokens.accessToken, tokens.refreshToken);
|
|
238
283
|
const user = await this.fetchUserProfile(tokens.accessToken);
|
|
239
284
|
this.setState("authenticated", user);
|
|
240
285
|
return user;
|
|
@@ -247,16 +292,88 @@ var AuthClient = class {
|
|
|
247
292
|
* @throws {AuthError} If the credentials are invalid or the request fails
|
|
248
293
|
*/
|
|
249
294
|
async signIn(params) {
|
|
295
|
+
const body = await this.withDeviceIdentity(params);
|
|
250
296
|
const response = await this.request("/auth/signin", {
|
|
251
297
|
method: "POST",
|
|
252
|
-
body
|
|
298
|
+
body
|
|
253
299
|
});
|
|
254
300
|
const tokens = "tokens" in response ? response.tokens : response;
|
|
255
|
-
this.storage.setTokens(tokens.accessToken, tokens.refreshToken);
|
|
301
|
+
await this.storage.setTokens(tokens.accessToken, tokens.refreshToken);
|
|
256
302
|
const user = await this.fetchUserProfile(tokens.accessToken);
|
|
257
303
|
this.setState("authenticated", user);
|
|
258
304
|
return user;
|
|
259
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* Create an OAuth authorization URL and optionally redirect the current window.
|
|
308
|
+
*
|
|
309
|
+
* For web apps, call this from a button click and keep the default redirect behavior.
|
|
310
|
+
* For desktop/mobile, pass `redirect: false`, open the returned URL with the runtime's
|
|
311
|
+
* browser API, then call `completeOAuthSignIn()` after receiving the callback.
|
|
312
|
+
*/
|
|
313
|
+
async signInWithOAuth(provider, options = {}) {
|
|
314
|
+
const result = await this.createOAuthAuthorization(provider, options);
|
|
315
|
+
if (options.redirect ?? canRedirectCurrentWindow()) {
|
|
316
|
+
redirectCurrentWindow(result.url);
|
|
317
|
+
}
|
|
318
|
+
return result;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Complete an OAuth sign-in callback and store the issued Kora tokens.
|
|
322
|
+
*/
|
|
323
|
+
async completeOAuthSignIn(provider, params) {
|
|
324
|
+
const body = await this.withDeviceIdentity(params);
|
|
325
|
+
const response = await this.request(
|
|
326
|
+
`/auth/oauth/${encodeURIComponent(provider)}/callback`,
|
|
327
|
+
{
|
|
328
|
+
method: "POST",
|
|
329
|
+
body: { ...body }
|
|
330
|
+
}
|
|
331
|
+
);
|
|
332
|
+
await this.storage.setTokens(response.tokens.accessToken, response.tokens.refreshToken);
|
|
333
|
+
const user = normalizeAuthUser(response.user);
|
|
334
|
+
this.setState("authenticated", user);
|
|
335
|
+
return user;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Create an OAuth authorization URL for linking another provider to the current user.
|
|
339
|
+
*/
|
|
340
|
+
async getOAuthAuthorizationUrl(provider, options = {}) {
|
|
341
|
+
return this.createOAuthAuthorization(provider, { ...options, redirect: false });
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Link an OAuth provider to the current authenticated user.
|
|
345
|
+
*/
|
|
346
|
+
async linkOAuth(provider, params) {
|
|
347
|
+
const token = await this.requireAccessToken();
|
|
348
|
+
return this.request(`/auth/oauth/${encodeURIComponent(provider)}/link`, {
|
|
349
|
+
method: "POST",
|
|
350
|
+
body: {
|
|
351
|
+
code: params.code,
|
|
352
|
+
state: params.state
|
|
353
|
+
},
|
|
354
|
+
token
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* List OAuth accounts linked to the current authenticated user.
|
|
359
|
+
*/
|
|
360
|
+
async listLinkedAccounts() {
|
|
361
|
+
const token = await this.requireAccessToken();
|
|
362
|
+
return this.request("/auth/oauth/links", {
|
|
363
|
+
method: "GET",
|
|
364
|
+
token
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Unlink an OAuth provider from the current authenticated user.
|
|
369
|
+
*/
|
|
370
|
+
async unlinkOAuth(provider) {
|
|
371
|
+
const token = await this.requireAccessToken();
|
|
372
|
+
await this.request(`/auth/oauth/${encodeURIComponent(provider)}/link`, {
|
|
373
|
+
method: "DELETE",
|
|
374
|
+
token
|
|
375
|
+
});
|
|
376
|
+
}
|
|
260
377
|
/**
|
|
261
378
|
* Sign out the current user.
|
|
262
379
|
*
|
|
@@ -265,9 +382,9 @@ var AuthClient = class {
|
|
|
265
382
|
* stolen refresh tokens cannot be used after the user explicitly signs out.
|
|
266
383
|
*/
|
|
267
384
|
async signOut() {
|
|
268
|
-
const accessToken = this.storage.getAccessToken();
|
|
269
|
-
const refreshToken = this.storage.getRefreshToken();
|
|
270
|
-
this.storage.clear();
|
|
385
|
+
const accessToken = await this.storage.getAccessToken();
|
|
386
|
+
const refreshToken = await this.storage.getRefreshToken();
|
|
387
|
+
await this.storage.clear();
|
|
271
388
|
this._refreshPromise = null;
|
|
272
389
|
this.setState("unauthenticated", null);
|
|
273
390
|
if (accessToken) {
|
|
@@ -291,11 +408,11 @@ var AuthClient = class {
|
|
|
291
408
|
* authenticated and refresh is not possible
|
|
292
409
|
*/
|
|
293
410
|
async getAccessToken() {
|
|
294
|
-
const accessToken = this.storage.getAccessToken();
|
|
411
|
+
const accessToken = await this.storage.getAccessToken();
|
|
295
412
|
if (accessToken && !isTokenExpired(accessToken)) {
|
|
296
413
|
return accessToken;
|
|
297
414
|
}
|
|
298
|
-
const refreshToken = this.storage.getRefreshToken();
|
|
415
|
+
const refreshToken = await this.storage.getRefreshToken();
|
|
299
416
|
if (!refreshToken) {
|
|
300
417
|
return null;
|
|
301
418
|
}
|
|
@@ -378,7 +495,7 @@ var AuthClient = class {
|
|
|
378
495
|
name: null
|
|
379
496
|
});
|
|
380
497
|
} else {
|
|
381
|
-
this.storage.clear();
|
|
498
|
+
await this.storage.clear();
|
|
382
499
|
this.setState("unauthenticated", null);
|
|
383
500
|
}
|
|
384
501
|
}
|
|
@@ -391,10 +508,47 @@ var AuthClient = class {
|
|
|
391
508
|
method: "GET",
|
|
392
509
|
token: accessToken
|
|
393
510
|
});
|
|
511
|
+
return normalizeAuthUser(profile);
|
|
512
|
+
}
|
|
513
|
+
async createOAuthAuthorization(provider, options) {
|
|
514
|
+
const params = new URLSearchParams();
|
|
515
|
+
const withIdentity = await this.withDeviceIdentity({
|
|
516
|
+
deviceId: options.deviceId,
|
|
517
|
+
devicePublicKey: options.devicePublicKey
|
|
518
|
+
});
|
|
519
|
+
if (options.returnTo) {
|
|
520
|
+
params.set("returnTo", options.returnTo);
|
|
521
|
+
}
|
|
522
|
+
if (withIdentity.deviceId) {
|
|
523
|
+
params.set("deviceId", withIdentity.deviceId);
|
|
524
|
+
}
|
|
525
|
+
if (withIdentity.devicePublicKey) {
|
|
526
|
+
params.set("devicePublicKey", withIdentity.devicePublicKey);
|
|
527
|
+
}
|
|
528
|
+
if (options.metadata) {
|
|
529
|
+
for (const [key, value] of Object.entries(options.metadata)) {
|
|
530
|
+
if (value !== void 0 && value !== null) {
|
|
531
|
+
params.set(key, String(value));
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
const query = params.toString();
|
|
536
|
+
return this.request(
|
|
537
|
+
`/auth/oauth/${encodeURIComponent(provider)}${query ? `?${query}` : ""}`,
|
|
538
|
+
{
|
|
539
|
+
method: "GET"
|
|
540
|
+
}
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
async withDeviceIdentity(params) {
|
|
544
|
+
if (!this.deviceIdentity || params.deviceId && params.devicePublicKey) {
|
|
545
|
+
return params;
|
|
546
|
+
}
|
|
547
|
+
const identity = await this.deviceIdentity.getDeviceIdentity();
|
|
394
548
|
return {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
549
|
+
...params,
|
|
550
|
+
deviceId: params.deviceId ?? identity.deviceId,
|
|
551
|
+
devicePublicKey: params.devicePublicKey ?? identity.devicePublicKey
|
|
398
552
|
};
|
|
399
553
|
}
|
|
400
554
|
/**
|
|
@@ -413,6 +567,13 @@ var AuthClient = class {
|
|
|
413
567
|
this._refreshPromise = null;
|
|
414
568
|
}
|
|
415
569
|
}
|
|
570
|
+
async requireAccessToken() {
|
|
571
|
+
const token = await this.getAccessToken();
|
|
572
|
+
if (!token) {
|
|
573
|
+
throw new AuthError("You must be signed in to perform this action.", "AUTH_REQUIRED");
|
|
574
|
+
}
|
|
575
|
+
return token;
|
|
576
|
+
}
|
|
416
577
|
/**
|
|
417
578
|
* Execute the token refresh network request.
|
|
418
579
|
*/
|
|
@@ -422,10 +583,10 @@ var AuthClient = class {
|
|
|
422
583
|
method: "POST",
|
|
423
584
|
body: { refreshToken }
|
|
424
585
|
});
|
|
425
|
-
this.storage.setTokens(response.accessToken, response.refreshToken);
|
|
586
|
+
await this.storage.setTokens(response.accessToken, response.refreshToken);
|
|
426
587
|
return response.accessToken;
|
|
427
588
|
} catch {
|
|
428
|
-
this.storage.clear();
|
|
589
|
+
await this.storage.clear();
|
|
429
590
|
this.setState("unauthenticated", null);
|
|
430
591
|
return null;
|
|
431
592
|
}
|
|
@@ -449,7 +610,7 @@ var AuthClient = class {
|
|
|
449
610
|
}
|
|
450
611
|
let response;
|
|
451
612
|
try {
|
|
452
|
-
response = await
|
|
613
|
+
response = await this.fetchFn(url, {
|
|
453
614
|
method: options.method,
|
|
454
615
|
headers,
|
|
455
616
|
body: options.body ? JSON.stringify(options.body) : void 0
|
|
@@ -487,637 +648,1026 @@ var AuthClient = class {
|
|
|
487
648
|
}
|
|
488
649
|
};
|
|
489
650
|
|
|
490
|
-
// src/client/
|
|
651
|
+
// src/client/device-session.ts
|
|
652
|
+
var import_core4 = require("@korajs/core");
|
|
653
|
+
|
|
654
|
+
// src/device/device-identity.ts
|
|
491
655
|
var import_core2 = require("@korajs/core");
|
|
492
|
-
var
|
|
493
|
-
constructor(
|
|
494
|
-
super(
|
|
495
|
-
|
|
656
|
+
var CryptoUnavailableError = class extends import_core2.KoraError {
|
|
657
|
+
constructor() {
|
|
658
|
+
super(
|
|
659
|
+
"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.",
|
|
660
|
+
"CRYPTO_UNAVAILABLE"
|
|
661
|
+
);
|
|
662
|
+
this.name = "CryptoUnavailableError";
|
|
496
663
|
}
|
|
497
664
|
};
|
|
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;
|
|
665
|
+
var DeviceIdentityError = class extends import_core2.KoraError {
|
|
666
|
+
constructor(message, context) {
|
|
667
|
+
super(message, "DEVICE_IDENTITY_ERROR", context);
|
|
668
|
+
this.name = "DeviceIdentityError";
|
|
508
669
|
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
670
|
+
};
|
|
671
|
+
function toBase64Url(buffer) {
|
|
672
|
+
const bytes = new Uint8Array(buffer);
|
|
673
|
+
let binary = "";
|
|
674
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
675
|
+
binary += String.fromCharCode(bytes[i]);
|
|
513
676
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
677
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
678
|
+
}
|
|
679
|
+
function fromBase64Url(str) {
|
|
680
|
+
let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
681
|
+
const paddingNeeded = (4 - base64.length % 4) % 4;
|
|
682
|
+
base64 += "=".repeat(paddingNeeded);
|
|
683
|
+
const binary = atob(base64);
|
|
684
|
+
const bytes = new Uint8Array(binary.length);
|
|
685
|
+
for (let i = 0; i < binary.length; i++) {
|
|
686
|
+
bytes[i] = binary.charCodeAt(i);
|
|
517
687
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
688
|
+
return bytes;
|
|
689
|
+
}
|
|
690
|
+
function assertCryptoAvailable() {
|
|
691
|
+
if (typeof globalThis.crypto === "undefined" || typeof globalThis.crypto.subtle === "undefined") {
|
|
692
|
+
throw new CryptoUnavailableError();
|
|
521
693
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
694
|
+
}
|
|
695
|
+
var ECDSA_ALGORITHM = {
|
|
696
|
+
name: "ECDSA",
|
|
697
|
+
namedCurve: "P-256"
|
|
698
|
+
};
|
|
699
|
+
var ECDSA_SIGN_ALGORITHM = {
|
|
700
|
+
name: "ECDSA",
|
|
701
|
+
hash: { name: "SHA-256" }
|
|
702
|
+
};
|
|
703
|
+
async function generateDeviceKeyPair() {
|
|
704
|
+
assertCryptoAvailable();
|
|
705
|
+
try {
|
|
706
|
+
const keyPair = await globalThis.crypto.subtle.generateKey(
|
|
707
|
+
ECDSA_ALGORITHM,
|
|
708
|
+
// extractable: false makes the private key non-extractable.
|
|
709
|
+
// The public key is always extractable regardless of this flag.
|
|
710
|
+
false,
|
|
711
|
+
["sign", "verify"]
|
|
712
|
+
);
|
|
713
|
+
return keyPair;
|
|
714
|
+
} catch (cause) {
|
|
715
|
+
throw new DeviceIdentityError(
|
|
716
|
+
"Failed to generate ECDSA P-256 device key pair. Ensure the runtime supports the ECDSA algorithm with the P-256 curve.",
|
|
717
|
+
{ cause: cause instanceof Error ? cause.message : String(cause) }
|
|
718
|
+
);
|
|
531
719
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
720
|
+
}
|
|
721
|
+
async function exportPublicKeyJwk(keyPair) {
|
|
722
|
+
assertCryptoAvailable();
|
|
723
|
+
try {
|
|
724
|
+
const jwk = await globalThis.crypto.subtle.exportKey("jwk", keyPair.publicKey);
|
|
725
|
+
return jwk;
|
|
726
|
+
} catch (cause) {
|
|
727
|
+
throw new DeviceIdentityError(
|
|
728
|
+
"Failed to export public key as JWK. The key pair may be invalid or the public key may not support JWK export.",
|
|
729
|
+
{ cause: cause instanceof Error ? cause.message : String(cause) }
|
|
730
|
+
);
|
|
537
731
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
732
|
+
}
|
|
733
|
+
async function signChallenge(privateKey, challenge) {
|
|
734
|
+
assertCryptoAvailable();
|
|
735
|
+
try {
|
|
736
|
+
const encoded = new TextEncoder().encode(challenge);
|
|
737
|
+
const signatureBuffer = await globalThis.crypto.subtle.sign(
|
|
738
|
+
ECDSA_SIGN_ALGORITHM,
|
|
739
|
+
privateKey,
|
|
740
|
+
encoded
|
|
741
|
+
);
|
|
742
|
+
return toBase64Url(signatureBuffer);
|
|
743
|
+
} catch (cause) {
|
|
744
|
+
throw new DeviceIdentityError(
|
|
745
|
+
'Failed to sign challenge. Ensure the key is a valid ECDSA P-256 private key with "sign" usage.',
|
|
746
|
+
{ cause: cause instanceof Error ? cause.message : String(cause) }
|
|
747
|
+
);
|
|
543
748
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
749
|
+
}
|
|
750
|
+
async function verifyChallenge(publicKeyJwk, challenge, signature) {
|
|
751
|
+
assertCryptoAvailable();
|
|
752
|
+
try {
|
|
753
|
+
const publicKey = await globalThis.crypto.subtle.importKey(
|
|
754
|
+
"jwk",
|
|
755
|
+
publicKeyJwk,
|
|
756
|
+
ECDSA_ALGORITHM,
|
|
757
|
+
true,
|
|
758
|
+
["verify"]
|
|
759
|
+
);
|
|
760
|
+
const encoded = new TextEncoder().encode(challenge);
|
|
761
|
+
const signatureBytes = fromBase64Url(signature);
|
|
762
|
+
const isValid = await globalThis.crypto.subtle.verify(
|
|
763
|
+
ECDSA_SIGN_ALGORITHM,
|
|
764
|
+
publicKey,
|
|
765
|
+
signatureBytes,
|
|
766
|
+
encoded
|
|
767
|
+
);
|
|
768
|
+
return isValid;
|
|
769
|
+
} catch (cause) {
|
|
770
|
+
throw new DeviceIdentityError(
|
|
771
|
+
"Failed to verify challenge signature. The public key JWK or signature format may be invalid.",
|
|
772
|
+
{
|
|
773
|
+
cause: cause instanceof Error ? cause.message : String(cause),
|
|
774
|
+
publicKeyKty: publicKeyJwk.kty,
|
|
775
|
+
publicKeyCrv: publicKeyJwk.crv
|
|
776
|
+
}
|
|
777
|
+
);
|
|
556
778
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
this._activeRole = null;
|
|
566
|
-
this.notifyListeners();
|
|
567
|
-
}
|
|
779
|
+
}
|
|
780
|
+
async function computePublicKeyThumbprint(publicKeyJwk) {
|
|
781
|
+
assertCryptoAvailable();
|
|
782
|
+
if (publicKeyJwk.kty !== "EC") {
|
|
783
|
+
throw new DeviceIdentityError(
|
|
784
|
+
`Expected JWK key type "EC" but received "${publicKeyJwk.kty ?? "undefined"}". Only ECDSA public keys are supported for device identity.`,
|
|
785
|
+
{ kty: publicKeyJwk.kty }
|
|
786
|
+
);
|
|
568
787
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
});
|
|
579
|
-
this._activeOrgId = orgId;
|
|
580
|
-
this._activeOrg = org;
|
|
581
|
-
this._activeRole = membership.role;
|
|
582
|
-
this.notifyListeners();
|
|
583
|
-
}
|
|
584
|
-
/**
|
|
585
|
-
* Clear the active organization (no org selected).
|
|
586
|
-
*/
|
|
587
|
-
clearActiveOrg() {
|
|
588
|
-
this._activeOrgId = null;
|
|
589
|
-
this._activeOrg = null;
|
|
590
|
-
this._activeRole = null;
|
|
591
|
-
this.notifyListeners();
|
|
788
|
+
if (!publicKeyJwk.crv || !publicKeyJwk.x || !publicKeyJwk.y) {
|
|
789
|
+
throw new DeviceIdentityError(
|
|
790
|
+
'JWK is missing required EC fields. An EC public key JWK must include "crv", "x", and "y" members.',
|
|
791
|
+
{
|
|
792
|
+
hasCrv: Boolean(publicKeyJwk.crv),
|
|
793
|
+
hasX: Boolean(publicKeyJwk.x),
|
|
794
|
+
hasY: Boolean(publicKeyJwk.y)
|
|
795
|
+
}
|
|
796
|
+
);
|
|
592
797
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
798
|
+
const canonicalJson = JSON.stringify({
|
|
799
|
+
crv: publicKeyJwk.crv,
|
|
800
|
+
kty: publicKeyJwk.kty,
|
|
801
|
+
x: publicKeyJwk.x,
|
|
802
|
+
y: publicKeyJwk.y
|
|
803
|
+
});
|
|
804
|
+
try {
|
|
805
|
+
const encoded = new TextEncoder().encode(canonicalJson);
|
|
806
|
+
const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", encoded);
|
|
807
|
+
return toBase64Url(hashBuffer);
|
|
808
|
+
} catch (cause) {
|
|
809
|
+
throw new DeviceIdentityError("Failed to compute SHA-256 thumbprint of the public key JWK.", {
|
|
810
|
+
cause: cause instanceof Error ? cause.message : String(cause)
|
|
811
|
+
});
|
|
599
812
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// src/device/device-store.ts
|
|
816
|
+
var import_core3 = require("@korajs/core");
|
|
817
|
+
var DeviceKeyStoreError = class extends import_core3.KoraError {
|
|
818
|
+
constructor(message, context) {
|
|
819
|
+
super(message, "DEVICE_KEY_STORE_ERROR", context);
|
|
820
|
+
this.name = "DeviceKeyStoreError";
|
|
605
821
|
}
|
|
822
|
+
};
|
|
823
|
+
var IDB_DATABASE_NAME = "kora_device_keys";
|
|
824
|
+
var IDB_STORE_NAME = "keypairs";
|
|
825
|
+
var IDB_VERSION = 1;
|
|
826
|
+
var IndexedDBDeviceKeyStore = class {
|
|
827
|
+
dbPromise = null;
|
|
606
828
|
/**
|
|
607
|
-
*
|
|
829
|
+
* Opens (or creates) the IndexedDB database.
|
|
830
|
+
*
|
|
831
|
+
* The database connection is lazily initialized on first use and
|
|
832
|
+
* reused for subsequent operations. If the database does not exist,
|
|
833
|
+
* it is created with the `keypairs` object store.
|
|
608
834
|
*/
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
835
|
+
openDatabase() {
|
|
836
|
+
if (this.dbPromise !== null) {
|
|
837
|
+
return this.dbPromise;
|
|
838
|
+
}
|
|
839
|
+
this.dbPromise = new Promise((resolve, reject) => {
|
|
840
|
+
let request;
|
|
841
|
+
try {
|
|
842
|
+
request = globalThis.indexedDB.open(IDB_DATABASE_NAME, IDB_VERSION);
|
|
843
|
+
} catch (cause) {
|
|
844
|
+
this.dbPromise = null;
|
|
845
|
+
reject(
|
|
846
|
+
new DeviceKeyStoreError(
|
|
847
|
+
"Failed to open IndexedDB database for device key storage. IndexedDB may be unavailable or access may be denied.",
|
|
848
|
+
{ cause: cause instanceof Error ? cause.message : String(cause) }
|
|
849
|
+
)
|
|
850
|
+
);
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
request.onupgradeneeded = () => {
|
|
854
|
+
const db = request.result;
|
|
855
|
+
if (!db.objectStoreNames.contains(IDB_STORE_NAME)) {
|
|
856
|
+
db.createObjectStore(IDB_STORE_NAME);
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
request.onsuccess = () => {
|
|
860
|
+
resolve(request.result);
|
|
861
|
+
};
|
|
862
|
+
request.onerror = () => {
|
|
863
|
+
this.dbPromise = null;
|
|
864
|
+
reject(
|
|
865
|
+
new DeviceKeyStoreError("Failed to open IndexedDB database for device key storage.", {
|
|
866
|
+
error: request.error?.message
|
|
867
|
+
})
|
|
868
|
+
);
|
|
869
|
+
};
|
|
870
|
+
request.onblocked = () => {
|
|
871
|
+
this.dbPromise = null;
|
|
872
|
+
reject(
|
|
873
|
+
new DeviceKeyStoreError(
|
|
874
|
+
"IndexedDB database open was blocked. Another tab may have an older version of the database open. Close other tabs and try again."
|
|
875
|
+
)
|
|
876
|
+
);
|
|
877
|
+
};
|
|
613
878
|
});
|
|
879
|
+
return this.dbPromise;
|
|
614
880
|
}
|
|
615
|
-
/**
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
881
|
+
/** @inheritdoc */
|
|
882
|
+
async saveKeyPair(deviceId, keyPair) {
|
|
883
|
+
const db = await this.openDatabase();
|
|
884
|
+
return new Promise((resolve, reject) => {
|
|
885
|
+
try {
|
|
886
|
+
const tx = db.transaction(IDB_STORE_NAME, "readwrite");
|
|
887
|
+
const store = tx.objectStore(IDB_STORE_NAME);
|
|
888
|
+
store.put(keyPair, deviceId);
|
|
889
|
+
tx.oncomplete = () => {
|
|
890
|
+
resolve();
|
|
891
|
+
};
|
|
892
|
+
tx.onerror = () => {
|
|
893
|
+
reject(
|
|
894
|
+
new DeviceKeyStoreError(`Failed to save key pair for device "${deviceId}".`, {
|
|
895
|
+
deviceId,
|
|
896
|
+
error: tx.error?.message
|
|
897
|
+
})
|
|
898
|
+
);
|
|
899
|
+
};
|
|
900
|
+
} catch (cause) {
|
|
901
|
+
reject(
|
|
902
|
+
new DeviceKeyStoreError(`Failed to save key pair for device "${deviceId}".`, {
|
|
903
|
+
deviceId,
|
|
904
|
+
cause: cause instanceof Error ? cause.message : String(cause)
|
|
905
|
+
})
|
|
906
|
+
);
|
|
907
|
+
}
|
|
622
908
|
});
|
|
623
909
|
}
|
|
624
|
-
/**
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
910
|
+
/** @inheritdoc */
|
|
911
|
+
async loadKeyPair(deviceId) {
|
|
912
|
+
const db = await this.openDatabase();
|
|
913
|
+
return new Promise((resolve, reject) => {
|
|
914
|
+
try {
|
|
915
|
+
const tx = db.transaction(IDB_STORE_NAME, "readonly");
|
|
916
|
+
const store = tx.objectStore(IDB_STORE_NAME);
|
|
917
|
+
const request = store.get(deviceId);
|
|
918
|
+
request.onsuccess = () => {
|
|
919
|
+
const result = request.result;
|
|
920
|
+
resolve(result ?? null);
|
|
921
|
+
};
|
|
922
|
+
request.onerror = () => {
|
|
923
|
+
reject(
|
|
924
|
+
new DeviceKeyStoreError(`Failed to load key pair for device "${deviceId}".`, {
|
|
925
|
+
deviceId,
|
|
926
|
+
error: request.error?.message
|
|
927
|
+
})
|
|
928
|
+
);
|
|
929
|
+
};
|
|
930
|
+
} catch (cause) {
|
|
931
|
+
reject(
|
|
932
|
+
new DeviceKeyStoreError(`Failed to load key pair for device "${deviceId}".`, {
|
|
933
|
+
deviceId,
|
|
934
|
+
cause: cause instanceof Error ? cause.message : String(cause)
|
|
935
|
+
})
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
});
|
|
635
939
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
940
|
+
/** @inheritdoc */
|
|
941
|
+
async deleteKeyPair(deviceId) {
|
|
942
|
+
const db = await this.openDatabase();
|
|
943
|
+
return new Promise((resolve, reject) => {
|
|
944
|
+
try {
|
|
945
|
+
const tx = db.transaction(IDB_STORE_NAME, "readwrite");
|
|
946
|
+
const store = tx.objectStore(IDB_STORE_NAME);
|
|
947
|
+
store.delete(deviceId);
|
|
948
|
+
tx.oncomplete = () => {
|
|
949
|
+
resolve();
|
|
950
|
+
};
|
|
951
|
+
tx.onerror = () => {
|
|
952
|
+
reject(
|
|
953
|
+
new DeviceKeyStoreError(`Failed to delete key pair for device "${deviceId}".`, {
|
|
954
|
+
deviceId,
|
|
955
|
+
error: tx.error?.message
|
|
956
|
+
})
|
|
957
|
+
);
|
|
958
|
+
};
|
|
959
|
+
} catch (cause) {
|
|
960
|
+
reject(
|
|
961
|
+
new DeviceKeyStoreError(`Failed to delete key pair for device "${deviceId}".`, {
|
|
962
|
+
deviceId,
|
|
963
|
+
cause: cause instanceof Error ? cause.message : String(cause)
|
|
964
|
+
})
|
|
965
|
+
);
|
|
966
|
+
}
|
|
644
967
|
});
|
|
645
968
|
}
|
|
646
|
-
/**
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
969
|
+
/** @inheritdoc */
|
|
970
|
+
async hasKeyPair(deviceId) {
|
|
971
|
+
const db = await this.openDatabase();
|
|
972
|
+
return new Promise((resolve, reject) => {
|
|
973
|
+
try {
|
|
974
|
+
const tx = db.transaction(IDB_STORE_NAME, "readonly");
|
|
975
|
+
const store = tx.objectStore(IDB_STORE_NAME);
|
|
976
|
+
const request = store.count(deviceId);
|
|
977
|
+
request.onsuccess = () => {
|
|
978
|
+
resolve(request.result > 0);
|
|
979
|
+
};
|
|
980
|
+
request.onerror = () => {
|
|
981
|
+
reject(
|
|
982
|
+
new DeviceKeyStoreError(
|
|
983
|
+
`Failed to check if key pair exists for device "${deviceId}".`,
|
|
984
|
+
{ deviceId, error: request.error?.message }
|
|
985
|
+
)
|
|
986
|
+
);
|
|
987
|
+
};
|
|
988
|
+
} catch (cause) {
|
|
989
|
+
reject(
|
|
990
|
+
new DeviceKeyStoreError(`Failed to check if key pair exists for device "${deviceId}".`, {
|
|
991
|
+
deviceId,
|
|
992
|
+
cause: cause instanceof Error ? cause.message : String(cause)
|
|
993
|
+
})
|
|
994
|
+
);
|
|
995
|
+
}
|
|
653
996
|
});
|
|
654
997
|
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
998
|
+
};
|
|
999
|
+
var InMemoryDeviceKeyStore = class {
|
|
1000
|
+
store = /* @__PURE__ */ new Map();
|
|
1001
|
+
/** @inheritdoc */
|
|
1002
|
+
async saveKeyPair(deviceId, keyPair) {
|
|
1003
|
+
this.store.set(deviceId, keyPair);
|
|
660
1004
|
}
|
|
661
|
-
/**
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
1005
|
+
/** @inheritdoc */
|
|
1006
|
+
async loadKeyPair(deviceId) {
|
|
1007
|
+
return this.store.get(deviceId) ?? null;
|
|
1008
|
+
}
|
|
1009
|
+
/** @inheritdoc */
|
|
1010
|
+
async deleteKeyPair(deviceId) {
|
|
1011
|
+
this.store.delete(deviceId);
|
|
666
1012
|
}
|
|
667
|
-
/**
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
async listMyInvitations(email) {
|
|
671
|
-
return this.request(`/invitations?email=${encodeURIComponent(email)}`, {
|
|
672
|
-
method: "GET"
|
|
673
|
-
});
|
|
1013
|
+
/** @inheritdoc */
|
|
1014
|
+
async hasKeyPair(deviceId) {
|
|
1015
|
+
return this.store.has(deviceId);
|
|
674
1016
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
*/
|
|
680
|
-
onOrgChange(callback) {
|
|
681
|
-
this.listeners.add(callback);
|
|
682
|
-
return () => {
|
|
683
|
-
this.listeners.delete(callback);
|
|
684
|
-
};
|
|
1017
|
+
};
|
|
1018
|
+
function createDeviceKeyStore() {
|
|
1019
|
+
if (typeof globalThis.indexedDB !== "undefined") {
|
|
1020
|
+
return new IndexedDBDeviceKeyStore();
|
|
685
1021
|
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
1022
|
+
return new InMemoryDeviceKeyStore();
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
// src/client/device-session.ts
|
|
1026
|
+
var DEFAULT_DEVICE_ID_KEY = "kora_auth_device_id";
|
|
1027
|
+
var AuthDeviceIdentityError = class extends import_core4.KoraError {
|
|
1028
|
+
constructor(message, context) {
|
|
1029
|
+
super(message, "AUTH_DEVICE_IDENTITY_ERROR", context);
|
|
1030
|
+
this.name = "AuthDeviceIdentityError";
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
1033
|
+
function createPersistentDeviceIdentity(options) {
|
|
1034
|
+
const storage = options.storage;
|
|
1035
|
+
const keyStore = options.keyStore ?? createDefaultPersistentKeyStore();
|
|
1036
|
+
const deviceIdKey = options.deviceIdKey ?? DEFAULT_DEVICE_ID_KEY;
|
|
1037
|
+
const generateDeviceId = options.generateDeviceId ?? defaultDeviceId;
|
|
1038
|
+
return {
|
|
1039
|
+
async getDeviceIdentity() {
|
|
1040
|
+
let deviceId = await storage.getItem(deviceIdKey);
|
|
1041
|
+
if (!deviceId) {
|
|
1042
|
+
deviceId = generateDeviceId();
|
|
1043
|
+
await storage.setItem(deviceIdKey, deviceId);
|
|
692
1044
|
}
|
|
1045
|
+
let keyPair = await keyStore.loadKeyPair(deviceId);
|
|
1046
|
+
if (!keyPair) {
|
|
1047
|
+
keyPair = await generateDeviceKeyPair();
|
|
1048
|
+
await keyStore.saveKeyPair(deviceId, keyPair);
|
|
1049
|
+
}
|
|
1050
|
+
const publicKey = await exportPublicKeyJwk(keyPair);
|
|
1051
|
+
return {
|
|
1052
|
+
deviceId,
|
|
1053
|
+
devicePublicKey: JSON.stringify(publicKey)
|
|
1054
|
+
};
|
|
693
1055
|
}
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
function createDefaultPersistentKeyStore() {
|
|
1059
|
+
if (typeof globalThis.indexedDB !== "undefined") {
|
|
1060
|
+
return createDeviceKeyStore();
|
|
694
1061
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
1062
|
+
throw new AuthDeviceIdentityError(
|
|
1063
|
+
"No persistent device key store is available in this runtime. Pass `keyStore` to createPersistentDeviceIdentity()."
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
function defaultDeviceId() {
|
|
1067
|
+
if (typeof globalThis.crypto?.randomUUID === "function") {
|
|
1068
|
+
return globalThis.crypto.randomUUID();
|
|
1069
|
+
}
|
|
1070
|
+
const bytes = new Uint8Array(16);
|
|
1071
|
+
if (typeof globalThis.crypto?.getRandomValues === "function") {
|
|
1072
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
1073
|
+
} else {
|
|
1074
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1075
|
+
bytes[i] = Math.floor(Math.random() * 256);
|
|
709
1076
|
}
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
1077
|
+
}
|
|
1078
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
1079
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
1080
|
+
const hex = [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1081
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
// src/client/storage.ts
|
|
1085
|
+
function createAuthTokenStorage(options) {
|
|
1086
|
+
const prefix = options.prefix ?? "kora_auth";
|
|
1087
|
+
const accessKey = `${prefix}_access_token`;
|
|
1088
|
+
const refreshKey = `${prefix}_refresh_token`;
|
|
1089
|
+
const store = options.store;
|
|
1090
|
+
return {
|
|
1091
|
+
getAccessToken: () => store.getItem(accessKey),
|
|
1092
|
+
getRefreshToken: () => store.getItem(refreshKey),
|
|
1093
|
+
async setTokens(accessToken, refreshToken) {
|
|
1094
|
+
await store.setItem(accessKey, accessToken);
|
|
1095
|
+
await store.setItem(refreshKey, refreshToken);
|
|
1096
|
+
},
|
|
1097
|
+
async clear() {
|
|
1098
|
+
await store.removeItem(accessKey);
|
|
1099
|
+
await store.removeItem(refreshKey);
|
|
722
1100
|
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
function createMemoryAuthTokenStorage() {
|
|
1104
|
+
let accessToken = null;
|
|
1105
|
+
let refreshToken = null;
|
|
1106
|
+
return {
|
|
1107
|
+
getAccessToken: () => accessToken,
|
|
1108
|
+
getRefreshToken: () => refreshToken,
|
|
1109
|
+
setTokens(access, refresh) {
|
|
1110
|
+
accessToken = access;
|
|
1111
|
+
refreshToken = refresh;
|
|
1112
|
+
},
|
|
1113
|
+
clear() {
|
|
1114
|
+
accessToken = null;
|
|
1115
|
+
refreshToken = null;
|
|
736
1116
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
function createWebStorageAuthTokenStorage(storage, prefix) {
|
|
1120
|
+
return createAuthTokenStorage({
|
|
1121
|
+
prefix,
|
|
1122
|
+
store: {
|
|
1123
|
+
getItem: (key) => storage.getItem(key),
|
|
1124
|
+
setItem: (key, value) => {
|
|
1125
|
+
storage.setItem(key, value);
|
|
1126
|
+
},
|
|
1127
|
+
removeItem: (key) => {
|
|
1128
|
+
storage.removeItem(key);
|
|
743
1129
|
}
|
|
744
|
-
return body;
|
|
745
|
-
} catch {
|
|
746
|
-
return void 0;
|
|
747
1130
|
}
|
|
748
|
-
}
|
|
749
|
-
}
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
750
1133
|
|
|
751
|
-
// src/
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
this.name = "DeviceIdentityError";
|
|
766
|
-
}
|
|
767
|
-
};
|
|
768
|
-
function toBase64Url(buffer) {
|
|
769
|
-
const bytes = new Uint8Array(buffer);
|
|
770
|
-
let binary = "";
|
|
771
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
772
|
-
binary += String.fromCharCode(bytes[i]);
|
|
773
|
-
}
|
|
774
|
-
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1134
|
+
// src/client/quickstart.ts
|
|
1135
|
+
function createKoraAuth(options) {
|
|
1136
|
+
const storage = options.storage ?? (options.credentialStore ? createAuthTokenStorage({
|
|
1137
|
+
store: options.credentialStore,
|
|
1138
|
+
prefix: options.storageKey
|
|
1139
|
+
}) : tryCreateDefaultTokenStorage(options.storageKey));
|
|
1140
|
+
const deviceIdentity = options.deviceIdentity === false ? void 0 : options.deviceIdentity ?? tryCreateDefaultDeviceIdentity(options.credentialStore, options.deviceKeyStore);
|
|
1141
|
+
return new AuthClient({
|
|
1142
|
+
serverUrl: options.serverUrl,
|
|
1143
|
+
storageKey: options.storageKey,
|
|
1144
|
+
storage,
|
|
1145
|
+
fetch: options.fetch,
|
|
1146
|
+
deviceIdentity
|
|
1147
|
+
});
|
|
775
1148
|
}
|
|
776
|
-
function
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
base64 += "=".repeat(paddingNeeded);
|
|
780
|
-
const binary = atob(base64);
|
|
781
|
-
const bytes = new Uint8Array(binary.length);
|
|
782
|
-
for (let i = 0; i < binary.length; i++) {
|
|
783
|
-
bytes[i] = binary.charCodeAt(i);
|
|
784
|
-
}
|
|
785
|
-
return bytes;
|
|
1149
|
+
function tryCreateDefaultTokenStorage(storageKey) {
|
|
1150
|
+
const storage = tryGetBrowserStorage();
|
|
1151
|
+
return storage ? createWebStorageAuthTokenStorage(storage, storageKey) : void 0;
|
|
786
1152
|
}
|
|
787
|
-
function
|
|
788
|
-
|
|
789
|
-
|
|
1153
|
+
function tryCreateDefaultDeviceIdentity(credentialStore, deviceKeyStore) {
|
|
1154
|
+
const storage = credentialStore ?? tryGetBrowserStorage();
|
|
1155
|
+
if (!storage) {
|
|
1156
|
+
return void 0;
|
|
790
1157
|
}
|
|
791
|
-
}
|
|
792
|
-
var ECDSA_ALGORITHM = {
|
|
793
|
-
name: "ECDSA",
|
|
794
|
-
namedCurve: "P-256"
|
|
795
|
-
};
|
|
796
|
-
var ECDSA_SIGN_ALGORITHM = {
|
|
797
|
-
name: "ECDSA",
|
|
798
|
-
hash: { name: "SHA-256" }
|
|
799
|
-
};
|
|
800
|
-
async function generateDeviceKeyPair() {
|
|
801
|
-
assertCryptoAvailable();
|
|
802
1158
|
try {
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
);
|
|
810
|
-
return keyPair;
|
|
811
|
-
} catch (cause) {
|
|
812
|
-
throw new DeviceIdentityError(
|
|
813
|
-
"Failed to generate ECDSA P-256 device key pair. Ensure the runtime supports the ECDSA algorithm with the P-256 curve.",
|
|
814
|
-
{ cause: cause instanceof Error ? cause.message : String(cause) }
|
|
815
|
-
);
|
|
1159
|
+
return createPersistentDeviceIdentity({
|
|
1160
|
+
storage,
|
|
1161
|
+
keyStore: deviceKeyStore
|
|
1162
|
+
});
|
|
1163
|
+
} catch {
|
|
1164
|
+
return void 0;
|
|
816
1165
|
}
|
|
817
1166
|
}
|
|
818
|
-
|
|
819
|
-
assertCryptoAvailable();
|
|
1167
|
+
function tryGetBrowserStorage() {
|
|
820
1168
|
try {
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
1169
|
+
if (typeof globalThis.localStorage === "undefined") {
|
|
1170
|
+
return null;
|
|
1171
|
+
}
|
|
1172
|
+
const key = "__kora_auth_quickstart_test__";
|
|
1173
|
+
globalThis.localStorage.setItem(key, "1");
|
|
1174
|
+
globalThis.localStorage.removeItem(key);
|
|
1175
|
+
return globalThis.localStorage;
|
|
1176
|
+
} catch {
|
|
1177
|
+
return null;
|
|
828
1178
|
}
|
|
829
1179
|
}
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
encoded
|
|
838
|
-
);
|
|
839
|
-
return toBase64Url(signatureBuffer);
|
|
840
|
-
} catch (cause) {
|
|
841
|
-
throw new DeviceIdentityError(
|
|
842
|
-
'Failed to sign challenge. Ensure the key is a valid ECDSA P-256 private key with "sign" usage.',
|
|
843
|
-
{ cause: cause instanceof Error ? cause.message : String(cause) }
|
|
844
|
-
);
|
|
1180
|
+
|
|
1181
|
+
// src/client/auth-sync.ts
|
|
1182
|
+
var import_core5 = require("@korajs/core");
|
|
1183
|
+
function decodeJwtPayload2(token) {
|
|
1184
|
+
const parts = token.split(".");
|
|
1185
|
+
if (parts.length !== 3) {
|
|
1186
|
+
return null;
|
|
845
1187
|
}
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
try {
|
|
850
|
-
const publicKey = await globalThis.crypto.subtle.importKey(
|
|
851
|
-
"jwk",
|
|
852
|
-
publicKeyJwk,
|
|
853
|
-
ECDSA_ALGORITHM,
|
|
854
|
-
true,
|
|
855
|
-
["verify"]
|
|
856
|
-
);
|
|
857
|
-
const encoded = new TextEncoder().encode(challenge);
|
|
858
|
-
const signatureBytes = fromBase64Url(signature);
|
|
859
|
-
const isValid = await globalThis.crypto.subtle.verify(
|
|
860
|
-
ECDSA_SIGN_ALGORITHM,
|
|
861
|
-
publicKey,
|
|
862
|
-
signatureBytes,
|
|
863
|
-
encoded
|
|
864
|
-
);
|
|
865
|
-
return isValid;
|
|
866
|
-
} catch (cause) {
|
|
867
|
-
throw new DeviceIdentityError(
|
|
868
|
-
"Failed to verify challenge signature. The public key JWK or signature format may be invalid.",
|
|
869
|
-
{
|
|
870
|
-
cause: cause instanceof Error ? cause.message : String(cause),
|
|
871
|
-
publicKeyKty: publicKeyJwk.kty,
|
|
872
|
-
publicKeyCrv: publicKeyJwk.crv
|
|
873
|
-
}
|
|
874
|
-
);
|
|
1188
|
+
const payloadSegment = parts[1];
|
|
1189
|
+
if (payloadSegment === void 0) {
|
|
1190
|
+
return null;
|
|
875
1191
|
}
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
1192
|
+
try {
|
|
1193
|
+
const base64 = payloadSegment.replace(/-/g, "+").replace(/_/g, "/");
|
|
1194
|
+
const padded = base64.padEnd(base64.length + (4 - base64.length % 4) % 4, "=");
|
|
1195
|
+
const json = typeof atob === "function" ? atob(padded) : Buffer.from(padded, "base64").toString("utf-8");
|
|
1196
|
+
const parsed = JSON.parse(json);
|
|
1197
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1198
|
+
return null;
|
|
1199
|
+
}
|
|
1200
|
+
return parsed;
|
|
1201
|
+
} catch {
|
|
1202
|
+
return null;
|
|
884
1203
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
1204
|
+
}
|
|
1205
|
+
function readDeviceIdFromClaims(claims) {
|
|
1206
|
+
const dev = claims.dev;
|
|
1207
|
+
return typeof dev === "string" && dev.length > 0 ? dev : void 0;
|
|
1208
|
+
}
|
|
1209
|
+
function createKoraAuthSync(options) {
|
|
1210
|
+
const { authClient, schema, scopeFromClaims } = options;
|
|
1211
|
+
const binding = {
|
|
1212
|
+
auth: async () => {
|
|
1213
|
+
const token = await authClient.getAccessToken();
|
|
1214
|
+
return { token: token ?? "" };
|
|
1215
|
+
}
|
|
1216
|
+
};
|
|
1217
|
+
if (schema) {
|
|
1218
|
+
binding.resolveScopeMap = async () => {
|
|
1219
|
+
const token = await authClient.getAccessToken();
|
|
1220
|
+
if (!token) {
|
|
1221
|
+
return void 0;
|
|
892
1222
|
}
|
|
893
|
-
|
|
1223
|
+
const claims = decodeJwtPayload2(token);
|
|
1224
|
+
if (!claims) {
|
|
1225
|
+
return void 0;
|
|
1226
|
+
}
|
|
1227
|
+
const scopeValues = scopeFromClaims ? scopeFromClaims(claims) : (0, import_core5.extractScopeValuesFromClaims)(schema, claims);
|
|
1228
|
+
return (0, import_core5.buildScopeMap)(schema, scopeValues);
|
|
1229
|
+
};
|
|
894
1230
|
}
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
return
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
|
|
1231
|
+
binding.resolveNodeId = async () => {
|
|
1232
|
+
const token = await authClient.getAccessToken();
|
|
1233
|
+
if (!token) {
|
|
1234
|
+
return void 0;
|
|
1235
|
+
}
|
|
1236
|
+
const claims = decodeJwtPayload2(token);
|
|
1237
|
+
if (!claims) {
|
|
1238
|
+
return void 0;
|
|
1239
|
+
}
|
|
1240
|
+
return readDeviceIdFromClaims(claims);
|
|
1241
|
+
};
|
|
1242
|
+
if (authClient.onAuthChange) {
|
|
1243
|
+
binding.subscribe = (listener) => authClient.onAuthChange?.(() => listener()) ?? (() => {
|
|
908
1244
|
});
|
|
909
1245
|
}
|
|
1246
|
+
return binding;
|
|
910
1247
|
}
|
|
911
1248
|
|
|
912
|
-
// src/
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
1249
|
+
// src/bindings/create-auth-session.ts
|
|
1250
|
+
function createAuthSession(client) {
|
|
1251
|
+
let state = client.state;
|
|
1252
|
+
let isLoading = true;
|
|
1253
|
+
let initError = null;
|
|
1254
|
+
let lastError = null;
|
|
1255
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1256
|
+
let snapshot = buildSnapshot2();
|
|
1257
|
+
const refreshSnapshot = () => {
|
|
1258
|
+
snapshot = buildSnapshot2();
|
|
1259
|
+
};
|
|
1260
|
+
const notify = () => {
|
|
1261
|
+
refreshSnapshot();
|
|
1262
|
+
for (const listener of listeners) {
|
|
1263
|
+
listener();
|
|
1264
|
+
}
|
|
1265
|
+
};
|
|
1266
|
+
function buildSnapshot2() {
|
|
1267
|
+
return {
|
|
1268
|
+
state,
|
|
1269
|
+
user: client.currentUser,
|
|
1270
|
+
isAuthenticated: state === "authenticated",
|
|
1271
|
+
isLoading,
|
|
1272
|
+
initError,
|
|
1273
|
+
error: lastError
|
|
1274
|
+
};
|
|
918
1275
|
}
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
* reused for subsequent operations. If the database does not exist,
|
|
930
|
-
* it is created with the `keypairs` object store.
|
|
931
|
-
*/
|
|
932
|
-
openDatabase() {
|
|
933
|
-
if (this.dbPromise !== null) {
|
|
934
|
-
return this.dbPromise;
|
|
1276
|
+
const captureError = (error) => {
|
|
1277
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
1278
|
+
notify();
|
|
1279
|
+
};
|
|
1280
|
+
const run = async (action) => {
|
|
1281
|
+
lastError = null;
|
|
1282
|
+
try {
|
|
1283
|
+
await action();
|
|
1284
|
+
} catch (error) {
|
|
1285
|
+
captureError(error);
|
|
935
1286
|
}
|
|
936
|
-
|
|
937
|
-
|
|
1287
|
+
};
|
|
1288
|
+
const runWithResult = async (action) => {
|
|
1289
|
+
lastError = null;
|
|
1290
|
+
try {
|
|
1291
|
+
return await action();
|
|
1292
|
+
} catch (error) {
|
|
1293
|
+
captureError(error);
|
|
1294
|
+
return null;
|
|
1295
|
+
}
|
|
1296
|
+
};
|
|
1297
|
+
const unsubscribeAuth = client.onAuthChange((nextState) => {
|
|
1298
|
+
state = nextState;
|
|
1299
|
+
notify();
|
|
1300
|
+
});
|
|
1301
|
+
void client.initialize().then(() => {
|
|
1302
|
+
state = client.state;
|
|
1303
|
+
isLoading = false;
|
|
1304
|
+
notify();
|
|
1305
|
+
}).catch((error) => {
|
|
1306
|
+
initError = error instanceof Error ? error : new Error(String(error));
|
|
1307
|
+
isLoading = false;
|
|
1308
|
+
notify();
|
|
1309
|
+
});
|
|
1310
|
+
return {
|
|
1311
|
+
client,
|
|
1312
|
+
getSnapshot: () => snapshot,
|
|
1313
|
+
subscribe(listener) {
|
|
1314
|
+
listeners.add(listener);
|
|
1315
|
+
return () => {
|
|
1316
|
+
listeners.delete(listener);
|
|
1317
|
+
};
|
|
1318
|
+
},
|
|
1319
|
+
signUp: (params) => run(async () => {
|
|
1320
|
+
await client.signUp(params);
|
|
1321
|
+
}),
|
|
1322
|
+
signIn: (params) => run(async () => {
|
|
1323
|
+
await client.signIn(params);
|
|
1324
|
+
}),
|
|
1325
|
+
signInWithOAuth: async (provider, options) => {
|
|
1326
|
+
lastError = null;
|
|
938
1327
|
try {
|
|
939
|
-
|
|
940
|
-
} catch (
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
new DeviceKeyStoreError(
|
|
944
|
-
"Failed to open IndexedDB database for device key storage. IndexedDB may be unavailable or access may be denied.",
|
|
945
|
-
{ cause: cause instanceof Error ? cause.message : String(cause) }
|
|
946
|
-
)
|
|
947
|
-
);
|
|
948
|
-
return;
|
|
1328
|
+
return await client.signInWithOAuth(provider, options);
|
|
1329
|
+
} catch (error) {
|
|
1330
|
+
captureError(error);
|
|
1331
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
949
1332
|
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1333
|
+
},
|
|
1334
|
+
completeOAuthSignIn: (provider, params) => run(async () => {
|
|
1335
|
+
await client.completeOAuthSignIn(provider, params);
|
|
1336
|
+
}),
|
|
1337
|
+
getOAuthAuthorizationUrl: async (provider, options) => {
|
|
1338
|
+
lastError = null;
|
|
1339
|
+
try {
|
|
1340
|
+
return await client.getOAuthAuthorizationUrl(provider, options);
|
|
1341
|
+
} catch (error) {
|
|
1342
|
+
captureError(error);
|
|
1343
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
1344
|
+
}
|
|
1345
|
+
},
|
|
1346
|
+
linkOAuth: (provider, params) => runWithResult(async () => client.linkOAuth(provider, params)),
|
|
1347
|
+
listLinkedAccounts: () => runWithResult(async () => client.listLinkedAccounts()).then((value) => value ?? []),
|
|
1348
|
+
unlinkOAuth: (provider) => run(async () => {
|
|
1349
|
+
await client.unlinkOAuth(provider);
|
|
1350
|
+
}),
|
|
1351
|
+
signOut: () => run(async () => {
|
|
1352
|
+
await client.signOut();
|
|
1353
|
+
}),
|
|
1354
|
+
destroy() {
|
|
1355
|
+
unsubscribeAuth();
|
|
1356
|
+
listeners.clear();
|
|
1357
|
+
}
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
// src/bindings/create-org-session.ts
|
|
1362
|
+
var ROLE_LEVELS = {
|
|
1363
|
+
viewer: 10,
|
|
1364
|
+
billing: 15,
|
|
1365
|
+
member: 20,
|
|
1366
|
+
admin: 30,
|
|
1367
|
+
owner: 40
|
|
1368
|
+
};
|
|
1369
|
+
function checkOrgPermission(currentRole, requiredRole) {
|
|
1370
|
+
if (!currentRole) return false;
|
|
1371
|
+
const currentLevel = ROLE_LEVELS[currentRole] ?? 0;
|
|
1372
|
+
const requiredLevel = ROLE_LEVELS[requiredRole] ?? 0;
|
|
1373
|
+
return currentLevel >= requiredLevel;
|
|
1374
|
+
}
|
|
1375
|
+
function createOrgSession(client) {
|
|
1376
|
+
let snapshot = buildSnapshot(client);
|
|
1377
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1378
|
+
const notify = () => {
|
|
1379
|
+
snapshot = buildSnapshot(client);
|
|
1380
|
+
for (const listener of listeners) {
|
|
1381
|
+
listener();
|
|
1382
|
+
}
|
|
1383
|
+
};
|
|
1384
|
+
const unsubscribeOrgChange = client.onOrgChange(notify);
|
|
1385
|
+
return {
|
|
1386
|
+
client,
|
|
1387
|
+
getSnapshot: () => snapshot,
|
|
1388
|
+
subscribe(listener) {
|
|
1389
|
+
listeners.add(listener);
|
|
1390
|
+
return () => {
|
|
1391
|
+
listeners.delete(listener);
|
|
974
1392
|
};
|
|
1393
|
+
},
|
|
1394
|
+
checkPermission(requiredRole) {
|
|
1395
|
+
return checkOrgPermission(snapshot.role, requiredRole);
|
|
1396
|
+
},
|
|
1397
|
+
destroy() {
|
|
1398
|
+
unsubscribeOrgChange();
|
|
1399
|
+
listeners.clear();
|
|
1400
|
+
}
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
function buildSnapshot(client) {
|
|
1404
|
+
return {
|
|
1405
|
+
orgId: client.activeOrgId,
|
|
1406
|
+
org: client.activeOrg,
|
|
1407
|
+
role: client.activeRole
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
// src/client/org-client.ts
|
|
1412
|
+
var import_core6 = require("@korajs/core");
|
|
1413
|
+
var OrgClientError = class extends import_core6.KoraError {
|
|
1414
|
+
constructor(message, code, context) {
|
|
1415
|
+
super(message, code, context);
|
|
1416
|
+
this.name = "OrgClientError";
|
|
1417
|
+
}
|
|
1418
|
+
};
|
|
1419
|
+
var OrgClient = class {
|
|
1420
|
+
serverUrl;
|
|
1421
|
+
getAccessToken;
|
|
1422
|
+
listeners = /* @__PURE__ */ new Set();
|
|
1423
|
+
_activeOrgId = null;
|
|
1424
|
+
_activeOrg = null;
|
|
1425
|
+
_activeRole = null;
|
|
1426
|
+
constructor(config) {
|
|
1427
|
+
this.serverUrl = config.serverUrl.replace(/\/+$/, "");
|
|
1428
|
+
this.getAccessToken = config.getAccessToken;
|
|
1429
|
+
}
|
|
1430
|
+
// --- Getters ---
|
|
1431
|
+
/** Currently active organization ID */
|
|
1432
|
+
get activeOrgId() {
|
|
1433
|
+
return this._activeOrgId;
|
|
1434
|
+
}
|
|
1435
|
+
/** Currently active organization */
|
|
1436
|
+
get activeOrg() {
|
|
1437
|
+
return this._activeOrg;
|
|
1438
|
+
}
|
|
1439
|
+
/** Current user's role in the active organization */
|
|
1440
|
+
get activeRole() {
|
|
1441
|
+
return this._activeRole;
|
|
1442
|
+
}
|
|
1443
|
+
// --- Organization Operations ---
|
|
1444
|
+
/**
|
|
1445
|
+
* Create a new organization.
|
|
1446
|
+
*/
|
|
1447
|
+
async createOrg(params) {
|
|
1448
|
+
return this.request("/orgs", {
|
|
1449
|
+
method: "POST",
|
|
1450
|
+
body: params
|
|
975
1451
|
});
|
|
976
|
-
return this.dbPromise;
|
|
977
1452
|
}
|
|
978
|
-
/**
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1453
|
+
/**
|
|
1454
|
+
* List all organizations the current user belongs to.
|
|
1455
|
+
*/
|
|
1456
|
+
async listOrgs() {
|
|
1457
|
+
return this.request("/orgs", { method: "GET" });
|
|
1458
|
+
}
|
|
1459
|
+
/**
|
|
1460
|
+
* Get an organization by ID.
|
|
1461
|
+
*/
|
|
1462
|
+
async getOrg(orgId) {
|
|
1463
|
+
return this.request(`/orgs/${orgId}`, { method: "GET" });
|
|
1464
|
+
}
|
|
1465
|
+
/**
|
|
1466
|
+
* Update an organization.
|
|
1467
|
+
*/
|
|
1468
|
+
async updateOrg(orgId, params) {
|
|
1469
|
+
const result = await this.request(`/orgs/${orgId}`, {
|
|
1470
|
+
method: "PATCH",
|
|
1471
|
+
body: params
|
|
1472
|
+
});
|
|
1473
|
+
if (this._activeOrgId === orgId) {
|
|
1474
|
+
this._activeOrg = result;
|
|
1475
|
+
}
|
|
1476
|
+
return result;
|
|
1477
|
+
}
|
|
1478
|
+
/**
|
|
1479
|
+
* Delete an organization.
|
|
1480
|
+
*/
|
|
1481
|
+
async deleteOrg(orgId) {
|
|
1482
|
+
await this.request(`/orgs/${orgId}`, { method: "DELETE" });
|
|
1483
|
+
if (this._activeOrgId === orgId) {
|
|
1484
|
+
this._activeOrgId = null;
|
|
1485
|
+
this._activeOrg = null;
|
|
1486
|
+
this._activeRole = null;
|
|
1487
|
+
this.notifyListeners();
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
// --- Org Switching ---
|
|
1491
|
+
/**
|
|
1492
|
+
* Switch the active organization context.
|
|
1493
|
+
* Fetches the org details and the user's membership/role.
|
|
1494
|
+
*/
|
|
1495
|
+
async switchOrg(orgId) {
|
|
1496
|
+
const org = await this.request(`/orgs/${orgId}`, { method: "GET" });
|
|
1497
|
+
const membership = await this.request(`/orgs/${orgId}/membership`, {
|
|
1498
|
+
method: "GET"
|
|
1499
|
+
});
|
|
1500
|
+
this._activeOrgId = orgId;
|
|
1501
|
+
this._activeOrg = org;
|
|
1502
|
+
this._activeRole = membership.role;
|
|
1503
|
+
this.notifyListeners();
|
|
1504
|
+
}
|
|
1505
|
+
/**
|
|
1506
|
+
* Clear the active organization (no org selected).
|
|
1507
|
+
*/
|
|
1508
|
+
clearActiveOrg() {
|
|
1509
|
+
this._activeOrgId = null;
|
|
1510
|
+
this._activeOrg = null;
|
|
1511
|
+
this._activeRole = null;
|
|
1512
|
+
this.notifyListeners();
|
|
1513
|
+
}
|
|
1514
|
+
// --- Member Management ---
|
|
1515
|
+
/**
|
|
1516
|
+
* List members of an organization.
|
|
1517
|
+
*/
|
|
1518
|
+
async listMembers(orgId) {
|
|
1519
|
+
return this.request(`/orgs/${orgId}/members`, { method: "GET" });
|
|
1520
|
+
}
|
|
1521
|
+
/**
|
|
1522
|
+
* Remove a member from an organization.
|
|
1523
|
+
*/
|
|
1524
|
+
async removeMember(orgId, userId) {
|
|
1525
|
+
await this.request(`/orgs/${orgId}/members/${userId}`, { method: "DELETE" });
|
|
1526
|
+
}
|
|
1527
|
+
/**
|
|
1528
|
+
* Update a member's role.
|
|
1529
|
+
*/
|
|
1530
|
+
async updateMemberRole(orgId, userId, role) {
|
|
1531
|
+
return this.request(`/orgs/${orgId}/members/${userId}/role`, {
|
|
1532
|
+
method: "PATCH",
|
|
1533
|
+
body: { role }
|
|
1005
1534
|
});
|
|
1006
1535
|
}
|
|
1007
|
-
/**
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
const request = store.get(deviceId);
|
|
1015
|
-
request.onsuccess = () => {
|
|
1016
|
-
const result = request.result;
|
|
1017
|
-
resolve(result ?? null);
|
|
1018
|
-
};
|
|
1019
|
-
request.onerror = () => {
|
|
1020
|
-
reject(
|
|
1021
|
-
new DeviceKeyStoreError(`Failed to load key pair for device "${deviceId}".`, {
|
|
1022
|
-
deviceId,
|
|
1023
|
-
error: request.error?.message
|
|
1024
|
-
})
|
|
1025
|
-
);
|
|
1026
|
-
};
|
|
1027
|
-
} catch (cause) {
|
|
1028
|
-
reject(
|
|
1029
|
-
new DeviceKeyStoreError(`Failed to load key pair for device "${deviceId}".`, {
|
|
1030
|
-
deviceId,
|
|
1031
|
-
cause: cause instanceof Error ? cause.message : String(cause)
|
|
1032
|
-
})
|
|
1033
|
-
);
|
|
1034
|
-
}
|
|
1536
|
+
/**
|
|
1537
|
+
* Transfer ownership to another member.
|
|
1538
|
+
*/
|
|
1539
|
+
async transferOwnership(orgId, newOwnerId) {
|
|
1540
|
+
await this.request(`/orgs/${orgId}/transfer`, {
|
|
1541
|
+
method: "POST",
|
|
1542
|
+
body: { newOwnerId }
|
|
1035
1543
|
});
|
|
1036
1544
|
}
|
|
1037
|
-
/**
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
reject(
|
|
1058
|
-
new DeviceKeyStoreError(`Failed to delete key pair for device "${deviceId}".`, {
|
|
1059
|
-
deviceId,
|
|
1060
|
-
cause: cause instanceof Error ? cause.message : String(cause)
|
|
1061
|
-
})
|
|
1062
|
-
);
|
|
1063
|
-
}
|
|
1545
|
+
/**
|
|
1546
|
+
* Leave an organization (remove yourself).
|
|
1547
|
+
*/
|
|
1548
|
+
async leaveOrg(orgId) {
|
|
1549
|
+
await this.request(`/orgs/${orgId}/leave`, { method: "POST" });
|
|
1550
|
+
if (this._activeOrgId === orgId) {
|
|
1551
|
+
this._activeOrgId = null;
|
|
1552
|
+
this._activeOrg = null;
|
|
1553
|
+
this._activeRole = null;
|
|
1554
|
+
this.notifyListeners();
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
// --- Invitations ---
|
|
1558
|
+
/**
|
|
1559
|
+
* Invite a user to an organization by email.
|
|
1560
|
+
*/
|
|
1561
|
+
async inviteMember(orgId, params) {
|
|
1562
|
+
return this.request(`/orgs/${orgId}/invitations`, {
|
|
1563
|
+
method: "POST",
|
|
1564
|
+
body: params
|
|
1064
1565
|
});
|
|
1065
1566
|
}
|
|
1066
|
-
/**
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
const request = store.count(deviceId);
|
|
1074
|
-
request.onsuccess = () => {
|
|
1075
|
-
resolve(request.result > 0);
|
|
1076
|
-
};
|
|
1077
|
-
request.onerror = () => {
|
|
1078
|
-
reject(
|
|
1079
|
-
new DeviceKeyStoreError(
|
|
1080
|
-
`Failed to check if key pair exists for device "${deviceId}".`,
|
|
1081
|
-
{ deviceId, error: request.error?.message }
|
|
1082
|
-
)
|
|
1083
|
-
);
|
|
1084
|
-
};
|
|
1085
|
-
} catch (cause) {
|
|
1086
|
-
reject(
|
|
1087
|
-
new DeviceKeyStoreError(`Failed to check if key pair exists for device "${deviceId}".`, {
|
|
1088
|
-
deviceId,
|
|
1089
|
-
cause: cause instanceof Error ? cause.message : String(cause)
|
|
1090
|
-
})
|
|
1091
|
-
);
|
|
1092
|
-
}
|
|
1567
|
+
/**
|
|
1568
|
+
* Accept an invitation by token.
|
|
1569
|
+
*/
|
|
1570
|
+
async acceptInvitation(token) {
|
|
1571
|
+
return this.request("/invitations/accept", {
|
|
1572
|
+
method: "POST",
|
|
1573
|
+
body: { token }
|
|
1093
1574
|
});
|
|
1094
1575
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
this.store.set(deviceId, keyPair);
|
|
1576
|
+
/**
|
|
1577
|
+
* List pending invitations for an organization.
|
|
1578
|
+
*/
|
|
1579
|
+
async listInvitations(orgId) {
|
|
1580
|
+
return this.request(`/orgs/${orgId}/invitations`, { method: "GET" });
|
|
1101
1581
|
}
|
|
1102
|
-
/**
|
|
1103
|
-
|
|
1104
|
-
|
|
1582
|
+
/**
|
|
1583
|
+
* Revoke a pending invitation.
|
|
1584
|
+
*/
|
|
1585
|
+
async revokeInvitation(orgId, invitationId) {
|
|
1586
|
+
await this.request(`/orgs/${orgId}/invitations/${invitationId}`, { method: "DELETE" });
|
|
1105
1587
|
}
|
|
1106
|
-
/**
|
|
1107
|
-
|
|
1108
|
-
|
|
1588
|
+
/**
|
|
1589
|
+
* List pending invitations for the current user's email.
|
|
1590
|
+
*/
|
|
1591
|
+
async listMyInvitations(email) {
|
|
1592
|
+
return this.request(`/invitations?email=${encodeURIComponent(email)}`, {
|
|
1593
|
+
method: "GET"
|
|
1594
|
+
});
|
|
1109
1595
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1596
|
+
// --- Subscriptions ---
|
|
1597
|
+
/**
|
|
1598
|
+
* Subscribe to active org changes.
|
|
1599
|
+
* @returns Unsubscribe function
|
|
1600
|
+
*/
|
|
1601
|
+
onOrgChange(callback) {
|
|
1602
|
+
this.listeners.add(callback);
|
|
1603
|
+
return () => {
|
|
1604
|
+
this.listeners.delete(callback);
|
|
1605
|
+
};
|
|
1113
1606
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1607
|
+
// --- Internal ---
|
|
1608
|
+
notifyListeners() {
|
|
1609
|
+
for (const listener of this.listeners) {
|
|
1610
|
+
try {
|
|
1611
|
+
listener(this._activeOrgId);
|
|
1612
|
+
} catch {
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1118
1615
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1616
|
+
async request(path, options) {
|
|
1617
|
+
const token = await this.getAccessToken();
|
|
1618
|
+
if (!token) {
|
|
1619
|
+
throw new OrgClientError(
|
|
1620
|
+
"Not authenticated. Sign in before performing organization operations.",
|
|
1621
|
+
"ORG_NOT_AUTHENTICATED"
|
|
1622
|
+
);
|
|
1623
|
+
}
|
|
1624
|
+
const url = `${this.serverUrl}${path}`;
|
|
1625
|
+
const headers = {
|
|
1626
|
+
Authorization: `Bearer ${token}`
|
|
1627
|
+
};
|
|
1628
|
+
if (options.body) {
|
|
1629
|
+
headers["Content-Type"] = "application/json";
|
|
1630
|
+
}
|
|
1631
|
+
let response;
|
|
1632
|
+
try {
|
|
1633
|
+
response = await fetch(url, {
|
|
1634
|
+
method: options.method,
|
|
1635
|
+
headers,
|
|
1636
|
+
body: options.body ? JSON.stringify(options.body) : void 0
|
|
1637
|
+
});
|
|
1638
|
+
} catch (cause) {
|
|
1639
|
+
throw new OrgClientError(`Network request to ${path} failed.`, "ORG_NETWORK_ERROR", {
|
|
1640
|
+
path,
|
|
1641
|
+
cause: cause instanceof Error ? cause.message : String(cause)
|
|
1642
|
+
});
|
|
1643
|
+
}
|
|
1644
|
+
if (!response.ok) {
|
|
1645
|
+
let errorMessage = `Server returned HTTP ${response.status}`;
|
|
1646
|
+
try {
|
|
1647
|
+
const body = await response.json();
|
|
1648
|
+
if (typeof body.error === "string") {
|
|
1649
|
+
errorMessage = body.error;
|
|
1650
|
+
}
|
|
1651
|
+
} catch {
|
|
1652
|
+
}
|
|
1653
|
+
throw new OrgClientError(errorMessage, "ORG_SERVER_ERROR", {
|
|
1654
|
+
path,
|
|
1655
|
+
status: response.status
|
|
1656
|
+
});
|
|
1657
|
+
}
|
|
1658
|
+
const text = await response.text();
|
|
1659
|
+
if (text.length === 0) return void 0;
|
|
1660
|
+
try {
|
|
1661
|
+
const body = JSON.parse(text);
|
|
1662
|
+
if (body && typeof body === "object" && "data" in body) {
|
|
1663
|
+
return body.data;
|
|
1664
|
+
}
|
|
1665
|
+
return body;
|
|
1666
|
+
} catch {
|
|
1667
|
+
return void 0;
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
};
|
|
1121
1671
|
|
|
1122
1672
|
// src/tokens/token-store.ts
|
|
1123
1673
|
var DEFAULT_STORAGE_KEY = "kora_auth_tokens";
|
|
@@ -1242,17 +1792,17 @@ var TokenStore = class {
|
|
|
1242
1792
|
};
|
|
1243
1793
|
|
|
1244
1794
|
// src/tokens/encrypted-token-store.ts
|
|
1245
|
-
var
|
|
1795
|
+
var import_core8 = require("@korajs/core");
|
|
1246
1796
|
|
|
1247
1797
|
// src/encryption/database-encryption.ts
|
|
1248
|
-
var
|
|
1249
|
-
var EncryptionError = class extends
|
|
1798
|
+
var import_core7 = require("@korajs/core");
|
|
1799
|
+
var EncryptionError = class extends import_core7.KoraError {
|
|
1250
1800
|
constructor(message, context) {
|
|
1251
1801
|
super(message, "ENCRYPTION_ERROR", context);
|
|
1252
1802
|
this.name = "EncryptionError";
|
|
1253
1803
|
}
|
|
1254
1804
|
};
|
|
1255
|
-
var CryptoUnavailableError2 = class extends
|
|
1805
|
+
var CryptoUnavailableError2 = class extends import_core7.KoraError {
|
|
1256
1806
|
constructor() {
|
|
1257
1807
|
super(
|
|
1258
1808
|
"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.",
|
|
@@ -1360,7 +1910,7 @@ async function importKey(rawKey) {
|
|
|
1360
1910
|
}
|
|
1361
1911
|
|
|
1362
1912
|
// src/tokens/encrypted-token-store.ts
|
|
1363
|
-
var EncryptedTokenStoreError = class extends
|
|
1913
|
+
var EncryptedTokenStoreError = class extends import_core8.KoraError {
|
|
1364
1914
|
constructor(message, context) {
|
|
1365
1915
|
super(message, "ENCRYPTED_TOKEN_STORE_ERROR", context);
|
|
1366
1916
|
this.name = "EncryptedTokenStoreError";
|
|
@@ -1555,14 +2105,14 @@ var EncryptedTokenStore = class {
|
|
|
1555
2105
|
};
|
|
1556
2106
|
|
|
1557
2107
|
// src/passkey/passkey-client.ts
|
|
1558
|
-
var
|
|
1559
|
-
var PasskeyError = class extends
|
|
2108
|
+
var import_core9 = require("@korajs/core");
|
|
2109
|
+
var PasskeyError = class extends import_core9.KoraError {
|
|
1560
2110
|
constructor(message, context) {
|
|
1561
2111
|
super(message, "PASSKEY_ERROR", context);
|
|
1562
2112
|
this.name = "PasskeyError";
|
|
1563
2113
|
}
|
|
1564
2114
|
};
|
|
1565
|
-
var PasskeyUnsupportedError = class extends
|
|
2115
|
+
var PasskeyUnsupportedError = class extends import_core9.KoraError {
|
|
1566
2116
|
constructor() {
|
|
1567
2117
|
super(
|
|
1568
2118
|
"WebAuthn is not supported in this browser. Passkey authentication requires a modern browser with WebAuthn support.",
|
|
@@ -1834,8 +2384,8 @@ function decodeCbor(data, offset) {
|
|
|
1834
2384
|
}
|
|
1835
2385
|
|
|
1836
2386
|
// src/encryption/key-derivation.ts
|
|
1837
|
-
var
|
|
1838
|
-
var KeyDerivationError = class extends
|
|
2387
|
+
var import_core10 = require("@korajs/core");
|
|
2388
|
+
var KeyDerivationError = class extends import_core10.KoraError {
|
|
1839
2389
|
constructor(message, context) {
|
|
1840
2390
|
super(message, "KEY_DERIVATION_ERROR", context);
|
|
1841
2391
|
this.name = "KeyDerivationError";
|
|
@@ -2015,10 +2565,10 @@ var AutoLockManager = class {
|
|
|
2015
2565
|
};
|
|
2016
2566
|
|
|
2017
2567
|
// src/encryption/operation-encryptor.ts
|
|
2018
|
-
var
|
|
2568
|
+
var import_core11 = require("@korajs/core");
|
|
2019
2569
|
var ENCRYPTED_MARKER = "__kora_encrypted";
|
|
2020
2570
|
var ENCRYPTION_VERSION = 1;
|
|
2021
|
-
var OperationEncryptionError = class extends
|
|
2571
|
+
var OperationEncryptionError = class extends import_core11.KoraError {
|
|
2022
2572
|
constructor(message, context) {
|
|
2023
2573
|
super(message, "OPERATION_ENCRYPTION_ERROR", context);
|
|
2024
2574
|
this.name = "OperationEncryptionError";
|
|
@@ -2214,6 +2764,7 @@ function isEncryptedEnvelope(field) {
|
|
|
2214
2764
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2215
2765
|
0 && (module.exports = {
|
|
2216
2766
|
AuthClient,
|
|
2767
|
+
AuthDeviceIdentityError,
|
|
2217
2768
|
AuthError,
|
|
2218
2769
|
AutoLockManager,
|
|
2219
2770
|
CryptoUnavailableError,
|
|
@@ -2233,9 +2784,18 @@ function isEncryptedEnvelope(field) {
|
|
|
2233
2784
|
PasskeyUnsupportedError,
|
|
2234
2785
|
TokenStore,
|
|
2235
2786
|
authenticateWithPasskey,
|
|
2787
|
+
checkOrgPermission,
|
|
2236
2788
|
computePublicKeyThumbprint,
|
|
2789
|
+
createAuthSession,
|
|
2790
|
+
createAuthTokenStorage,
|
|
2237
2791
|
createDeviceKeyStore,
|
|
2792
|
+
createKoraAuth,
|
|
2793
|
+
createKoraAuthSync,
|
|
2794
|
+
createMemoryAuthTokenStorage,
|
|
2795
|
+
createOrgSession,
|
|
2238
2796
|
createPasskeyCredential,
|
|
2797
|
+
createPersistentDeviceIdentity,
|
|
2798
|
+
createWebStorageAuthTokenStorage,
|
|
2239
2799
|
decryptData,
|
|
2240
2800
|
deriveEncryptionKey,
|
|
2241
2801
|
encryptData,
|