@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/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: params
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: params
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
- id: profile.id,
396
- email: profile.email,
397
- name: profile.name ?? null
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 fetch(url, {
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/org-client.ts
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 OrgClientError = class extends import_core2.KoraError {
493
- constructor(message, code, context) {
494
- super(message, code, context);
495
- this.name = "OrgClientError";
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 OrgClient = class {
499
- serverUrl;
500
- getAccessToken;
501
- listeners = /* @__PURE__ */ new Set();
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
- // --- Getters ---
510
- /** Currently active organization ID */
511
- get activeOrgId() {
512
- return this._activeOrgId;
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
- /** Currently active organization */
515
- get activeOrg() {
516
- return this._activeOrg;
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
- /** Current user's role in the active organization */
519
- get activeRole() {
520
- return this._activeRole;
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
- // --- Organization Operations ---
523
- /**
524
- * Create a new organization.
525
- */
526
- async createOrg(params) {
527
- return this.request("/orgs", {
528
- method: "POST",
529
- body: params
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
- * List all organizations the current user belongs to.
534
- */
535
- async listOrgs() {
536
- return this.request("/orgs", { method: "GET" });
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
- * Get an organization by ID.
540
- */
541
- async getOrg(orgId) {
542
- return this.request(`/orgs/${orgId}`, { method: "GET" });
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
- * Update an organization.
546
- */
547
- async updateOrg(orgId, params) {
548
- const result = await this.request(`/orgs/${orgId}`, {
549
- method: "PATCH",
550
- body: params
551
- });
552
- if (this._activeOrgId === orgId) {
553
- this._activeOrg = result;
554
- }
555
- return result;
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
- * Delete an organization.
559
- */
560
- async deleteOrg(orgId) {
561
- await this.request(`/orgs/${orgId}`, { method: "DELETE" });
562
- if (this._activeOrgId === orgId) {
563
- this._activeOrgId = null;
564
- this._activeOrg = null;
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
- // --- Org Switching ---
570
- /**
571
- * Switch the active organization context.
572
- * Fetches the org details and the user's membership/role.
573
- */
574
- async switchOrg(orgId) {
575
- const org = await this.request(`/orgs/${orgId}`, { method: "GET" });
576
- const membership = await this.request(`/orgs/${orgId}/membership`, {
577
- method: "GET"
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
- // --- Member Management ---
594
- /**
595
- * List members of an organization.
596
- */
597
- async listMembers(orgId) {
598
- return this.request(`/orgs/${orgId}/members`, { method: "GET" });
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
- * Remove a member from an organization.
602
- */
603
- async removeMember(orgId, userId) {
604
- await this.request(`/orgs/${orgId}/members/${userId}`, { method: "DELETE" });
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
- * Update a member's role.
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
- async updateMemberRole(orgId, userId, role) {
610
- return this.request(`/orgs/${orgId}/members/${userId}/role`, {
611
- method: "PATCH",
612
- body: { role }
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
- * Transfer ownership to another member.
617
- */
618
- async transferOwnership(orgId, newOwnerId) {
619
- await this.request(`/orgs/${orgId}/transfer`, {
620
- method: "POST",
621
- body: { newOwnerId }
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
- * 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
- }
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
- // --- 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
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
- * Accept an invitation by token.
648
- */
649
- async acceptInvitation(token) {
650
- return this.request("/invitations/accept", {
651
- method: "POST",
652
- body: { token }
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
- * List pending invitations for an organization.
657
- */
658
- async listInvitations(orgId) {
659
- return this.request(`/orgs/${orgId}/invitations`, { method: "GET" });
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
- * Revoke a pending invitation.
663
- */
664
- async revokeInvitation(orgId, invitationId) {
665
- await this.request(`/orgs/${orgId}/invitations/${invitationId}`, { method: "DELETE" });
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
- * List pending invitations for the current user's email.
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
- // --- Subscriptions ---
676
- /**
677
- * Subscribe to active org changes.
678
- * @returns Unsubscribe function
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
- // --- Internal ---
687
- notifyListeners() {
688
- for (const listener of this.listeners) {
689
- try {
690
- listener(this._activeOrgId);
691
- } catch {
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
- async request(path, options) {
696
- const token = await this.getAccessToken();
697
- if (!token) {
698
- throw new OrgClientError(
699
- "Not authenticated. Sign in before performing organization operations.",
700
- "ORG_NOT_AUTHENTICATED"
701
- );
702
- }
703
- const url = `${this.serverUrl}${path}`;
704
- const headers = {
705
- Authorization: `Bearer ${token}`
706
- };
707
- if (options.body) {
708
- headers["Content-Type"] = "application/json";
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
- let response;
711
- try {
712
- response = await fetch(url, {
713
- method: options.method,
714
- headers,
715
- body: options.body ? JSON.stringify(options.body) : void 0
716
- });
717
- } catch (cause) {
718
- throw new OrgClientError(`Network request to ${path} failed.`, "ORG_NETWORK_ERROR", {
719
- path,
720
- cause: cause instanceof Error ? cause.message : String(cause)
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
- if (!response.ok) {
724
- let errorMessage = `Server returned HTTP ${response.status}`;
725
- try {
726
- const body = await response.json();
727
- if (typeof body.error === "string") {
728
- errorMessage = body.error;
729
- }
730
- } catch {
731
- }
732
- throw new OrgClientError(errorMessage, "ORG_SERVER_ERROR", {
733
- path,
734
- status: response.status
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
- const text = await response.text();
738
- if (text.length === 0) return void 0;
739
- try {
740
- const body = JSON.parse(text);
741
- if (body && typeof body === "object" && "data" in body) {
742
- return body.data;
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/device/device-identity.ts
752
- var import_core3 = require("@korajs/core");
753
- var CryptoUnavailableError = class extends import_core3.KoraError {
754
- constructor() {
755
- super(
756
- "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.",
757
- "CRYPTO_UNAVAILABLE"
758
- );
759
- this.name = "CryptoUnavailableError";
760
- }
761
- };
762
- var DeviceIdentityError = class extends import_core3.KoraError {
763
- constructor(message, context) {
764
- super(message, "DEVICE_IDENTITY_ERROR", context);
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 fromBase64Url(str) {
777
- let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
778
- const paddingNeeded = (4 - base64.length % 4) % 4;
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 assertCryptoAvailable() {
788
- if (typeof globalThis.crypto === "undefined" || typeof globalThis.crypto.subtle === "undefined") {
789
- throw new CryptoUnavailableError();
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
- const keyPair = await globalThis.crypto.subtle.generateKey(
804
- ECDSA_ALGORITHM,
805
- // extractable: false makes the private key non-extractable.
806
- // The public key is always extractable regardless of this flag.
807
- false,
808
- ["sign", "verify"]
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
- async function exportPublicKeyJwk(keyPair) {
819
- assertCryptoAvailable();
1167
+ function tryGetBrowserStorage() {
820
1168
  try {
821
- const jwk = await globalThis.crypto.subtle.exportKey("jwk", keyPair.publicKey);
822
- return jwk;
823
- } catch (cause) {
824
- throw new DeviceIdentityError(
825
- "Failed to export public key as JWK. The key pair may be invalid or the public key may not support JWK export.",
826
- { cause: cause instanceof Error ? cause.message : String(cause) }
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
- async function signChallenge(privateKey, challenge) {
831
- assertCryptoAvailable();
832
- try {
833
- const encoded = new TextEncoder().encode(challenge);
834
- const signatureBuffer = await globalThis.crypto.subtle.sign(
835
- ECDSA_SIGN_ALGORITHM,
836
- privateKey,
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
- async function verifyChallenge(publicKeyJwk, challenge, signature) {
848
- assertCryptoAvailable();
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
- async function computePublicKeyThumbprint(publicKeyJwk) {
878
- assertCryptoAvailable();
879
- if (publicKeyJwk.kty !== "EC") {
880
- throw new DeviceIdentityError(
881
- `Expected JWK key type "EC" but received "${publicKeyJwk.kty ?? "undefined"}". Only ECDSA public keys are supported for device identity.`,
882
- { kty: publicKeyJwk.kty }
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
- if (!publicKeyJwk.crv || !publicKeyJwk.x || !publicKeyJwk.y) {
886
- throw new DeviceIdentityError(
887
- 'JWK is missing required EC fields. An EC public key JWK must include "crv", "x", and "y" members.',
888
- {
889
- hasCrv: Boolean(publicKeyJwk.crv),
890
- hasX: Boolean(publicKeyJwk.x),
891
- hasY: Boolean(publicKeyJwk.y)
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
- const canonicalJson = JSON.stringify({
896
- crv: publicKeyJwk.crv,
897
- kty: publicKeyJwk.kty,
898
- x: publicKeyJwk.x,
899
- y: publicKeyJwk.y
900
- });
901
- try {
902
- const encoded = new TextEncoder().encode(canonicalJson);
903
- const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", encoded);
904
- return toBase64Url(hashBuffer);
905
- } catch (cause) {
906
- throw new DeviceIdentityError("Failed to compute SHA-256 thumbprint of the public key JWK.", {
907
- cause: cause instanceof Error ? cause.message : String(cause)
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/device/device-store.ts
913
- var import_core4 = require("@korajs/core");
914
- var DeviceKeyStoreError = class extends import_core4.KoraError {
915
- constructor(message, context) {
916
- super(message, "DEVICE_KEY_STORE_ERROR", context);
917
- this.name = "DeviceKeyStoreError";
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
- var IDB_DATABASE_NAME = "kora_device_keys";
921
- var IDB_STORE_NAME = "keypairs";
922
- var IDB_VERSION = 1;
923
- var IndexedDBDeviceKeyStore = class {
924
- dbPromise = null;
925
- /**
926
- * Opens (or creates) the IndexedDB database.
927
- *
928
- * The database connection is lazily initialized on first use and
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
- this.dbPromise = new Promise((resolve, reject) => {
937
- let request;
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
- request = globalThis.indexedDB.open(IDB_DATABASE_NAME, IDB_VERSION);
940
- } catch (cause) {
941
- this.dbPromise = null;
942
- reject(
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
- request.onupgradeneeded = () => {
951
- const db = request.result;
952
- if (!db.objectStoreNames.contains(IDB_STORE_NAME)) {
953
- db.createObjectStore(IDB_STORE_NAME);
954
- }
955
- };
956
- request.onsuccess = () => {
957
- resolve(request.result);
958
- };
959
- request.onerror = () => {
960
- this.dbPromise = null;
961
- reject(
962
- new DeviceKeyStoreError("Failed to open IndexedDB database for device key storage.", {
963
- error: request.error?.message
964
- })
965
- );
966
- };
967
- request.onblocked = () => {
968
- this.dbPromise = null;
969
- reject(
970
- new DeviceKeyStoreError(
971
- "IndexedDB database open was blocked. Another tab may have an older version of the database open. Close other tabs and try again."
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
- /** @inheritdoc */
979
- async saveKeyPair(deviceId, keyPair) {
980
- const db = await this.openDatabase();
981
- return new Promise((resolve, reject) => {
982
- try {
983
- const tx = db.transaction(IDB_STORE_NAME, "readwrite");
984
- const store = tx.objectStore(IDB_STORE_NAME);
985
- store.put(keyPair, deviceId);
986
- tx.oncomplete = () => {
987
- resolve();
988
- };
989
- tx.onerror = () => {
990
- reject(
991
- new DeviceKeyStoreError(`Failed to save key pair for device "${deviceId}".`, {
992
- deviceId,
993
- error: tx.error?.message
994
- })
995
- );
996
- };
997
- } catch (cause) {
998
- reject(
999
- new DeviceKeyStoreError(`Failed to save key pair for device "${deviceId}".`, {
1000
- deviceId,
1001
- cause: cause instanceof Error ? cause.message : String(cause)
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
- /** @inheritdoc */
1008
- async loadKeyPair(deviceId) {
1009
- const db = await this.openDatabase();
1010
- return new Promise((resolve, reject) => {
1011
- try {
1012
- const tx = db.transaction(IDB_STORE_NAME, "readonly");
1013
- const store = tx.objectStore(IDB_STORE_NAME);
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
- /** @inheritdoc */
1038
- async deleteKeyPair(deviceId) {
1039
- const db = await this.openDatabase();
1040
- return new Promise((resolve, reject) => {
1041
- try {
1042
- const tx = db.transaction(IDB_STORE_NAME, "readwrite");
1043
- const store = tx.objectStore(IDB_STORE_NAME);
1044
- store.delete(deviceId);
1045
- tx.oncomplete = () => {
1046
- resolve();
1047
- };
1048
- tx.onerror = () => {
1049
- reject(
1050
- new DeviceKeyStoreError(`Failed to delete key pair for device "${deviceId}".`, {
1051
- deviceId,
1052
- error: tx.error?.message
1053
- })
1054
- );
1055
- };
1056
- } catch (cause) {
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
- /** @inheritdoc */
1067
- async hasKeyPair(deviceId) {
1068
- const db = await this.openDatabase();
1069
- return new Promise((resolve, reject) => {
1070
- try {
1071
- const tx = db.transaction(IDB_STORE_NAME, "readonly");
1072
- const store = tx.objectStore(IDB_STORE_NAME);
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
- var InMemoryDeviceKeyStore = class {
1097
- store = /* @__PURE__ */ new Map();
1098
- /** @inheritdoc */
1099
- async saveKeyPair(deviceId, keyPair) {
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
- /** @inheritdoc */
1103
- async loadKeyPair(deviceId) {
1104
- return this.store.get(deviceId) ?? null;
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
- /** @inheritdoc */
1107
- async deleteKeyPair(deviceId) {
1108
- this.store.delete(deviceId);
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
- /** @inheritdoc */
1111
- async hasKeyPair(deviceId) {
1112
- return this.store.has(deviceId);
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
- function createDeviceKeyStore() {
1116
- if (typeof globalThis.indexedDB !== "undefined") {
1117
- return new IndexedDBDeviceKeyStore();
1607
+ // --- Internal ---
1608
+ notifyListeners() {
1609
+ for (const listener of this.listeners) {
1610
+ try {
1611
+ listener(this._activeOrgId);
1612
+ } catch {
1613
+ }
1614
+ }
1118
1615
  }
1119
- return new InMemoryDeviceKeyStore();
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 import_core6 = require("@korajs/core");
1795
+ var import_core8 = require("@korajs/core");
1246
1796
 
1247
1797
  // src/encryption/database-encryption.ts
1248
- var import_core5 = require("@korajs/core");
1249
- var EncryptionError = class extends import_core5.KoraError {
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 import_core5.KoraError {
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 import_core6.KoraError {
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 import_core7 = require("@korajs/core");
1559
- var PasskeyError = class extends import_core7.KoraError {
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 import_core7.KoraError {
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 import_core8 = require("@korajs/core");
1838
- var KeyDerivationError = class extends import_core8.KoraError {
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 import_core9 = require("@korajs/core");
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 import_core9.KoraError {
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,