@cellarnode/auth 0.14.0 → 0.15.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 CHANGED
@@ -13,7 +13,14 @@ npm install @cellarnode/auth
13
13
  ### Core (framework-agnostic)
14
14
 
15
15
  ```ts
16
- import { createAuthStore, createAuthClient, createAuthApi } from "@cellarnode/auth";
16
+ import {
17
+ canReplaySession,
18
+ captureSessionContinuity,
19
+ createAuthApi,
20
+ createAuthClient,
21
+ createAuthStore,
22
+ resolveSessionForReplay,
23
+ } from "@cellarnode/auth";
17
24
 
18
25
  const authStore = createAuthStore({
19
26
  baseUrl: "http://localhost:4000",
@@ -28,6 +35,59 @@ const authClient = createAuthClient({
28
35
  const authApi = createAuthApi({ client: authClient, store: authStore });
29
36
  ```
30
37
 
38
+ All package-owned requests require HTTPS. HTTP works automatically only for
39
+ exact loopback hosts `localhost`, `127.0.0.1`, and `[::1]` during local
40
+ development. Invalid URLs, userinfo, other schemes, non-loopback HTTP, and
41
+ request paths that escape the configured origin or base path fail before
42
+ network transport. Redirects are rejected so credentials cannot follow a
43
+ cross-origin or HTTPS-to-HTTP redirect.
44
+
45
+ Resolve token and authoritative identity together before enabling protected
46
+ work. `unavailable` preserves credentials while authority getters fail closed.
47
+ Caller abort returns `superseded` without cancelling shared adoption.
48
+
49
+ ```ts
50
+ const session = await authStore.resolveSession({ refresh: true, signal });
51
+ if (session.status === "ready") {
52
+ // session.token and validated session.user are from one guarded generation
53
+ }
54
+ ```
55
+
56
+ `ensureAccessToken()` remains a legacy credential-only wrapper. It may return a
57
+ retained token while authority is transiently `unavailable`; authority getters
58
+ still fail closed. Never use its token as proof that user or organisation
59
+ authority is ready. Use `resolveSession()` and require `status === "ready"` for
60
+ authorization decisions.
61
+
62
+ `onSessionStateChange` immediately reports current state. A synchronous
63
+ `resolving` notification precedes credential or authority changes, allowing
64
+ consumers to suspend writes and clear captured tenant queries first.
65
+ `getSessionState()` returns the same guarded state as a defensive snapshot;
66
+ continuity capture requires that snapshot to remain `ready`. Internal refresh
67
+ and identity requests time out after 10 seconds by default; configure
68
+ `resolutionTimeoutMs` on `createAuthStore` when needed.
69
+
70
+ Authenticated transports that retry after 401 must capture continuity before
71
+ their first request and replay only for same validated user and organisation:
72
+
73
+ ```ts
74
+ const before = captureSessionContinuity(authStore);
75
+ if (!before) throw new Error("Session authority unavailable");
76
+ const response = await fetch(url, init);
77
+ if (response.status === 401) {
78
+ const after = await resolveSessionForReplay(authStore, before, {
79
+ signal: init.signal,
80
+ });
81
+ if (canReplaySession(before, after, authStore)) {
82
+ // retry once with after.token
83
+ }
84
+ }
85
+ ```
86
+
87
+ `resolveSessionForReplay` centralizes stale-401 handling. It never refreshes a
88
+ replacement session. A ready rotated token can be reused once only when
89
+ validated user and organisation still match captured request authority.
90
+
31
91
  ### React Components
32
92
 
33
93
  ```tsx
@@ -84,7 +144,7 @@ Add this to your CSS file so Tailwind picks up utility classes from the package:
84
144
 
85
145
  ## Exports
86
146
 
87
- - `@cellarnode/auth` — Core: `createAuthStore`, `createAuthClient`, `createAuthApi`, `validateUserType`, `hasEntitlement`, `extractAccessToken`, types (incl. `DevLoginResult`)
147
+ - `@cellarnode/auth` — Core: `createAuthStore`, `createAuthClient`, `createAuthApi`, `captureSessionContinuity`, `resolveSessionForReplay`, `canReplaySession`, `validateUserType`, `hasEntitlement`, `extractAccessToken`, `AuthError`, session-resolution types, and `DevLoginResult`
88
148
  - `@cellarnode/auth/react` — React: `LoginForm`, `RegisterForm`, `UnauthorizedPage`, `SquircleShift`, `InputOTP` (+ `Group` / `Slot` / `Separator`)
89
149
 
90
150
  `DevSignInBypass`, `DEV_LOGIN_EMAIL_STORAGE_KEY`, `readDevLoginEmail` and
@@ -93,6 +153,16 @@ Add this to your CSS file so Tailwind picks up utility classes from the package:
93
153
  and an exported symbol carries no gate — a consumer importing it could render the
94
154
  bypass UI, or write a sign-in address to `localStorage`, from a production build.
95
155
 
156
+ ## Publishing
157
+
158
+ Published to npm (public access) via **npm Trusted Publishing (OIDC)** —
159
+ `.github/workflows/publish.yml` publishes automatically on a merge to `main` that changes
160
+ `package.json`'s version. There is no long-lived npm token in the publish job, and nobody runs
161
+ `npm publish` by hand. Provenance is generated automatically (public repo + public package).
162
+
163
+ Bump `version` in `package.json` and land it through a normal PR — merging to `main` is what
164
+ triggers the publish job, gated on the version having actually changed.
165
+
96
166
  ## License
97
167
 
98
168
  MIT
@@ -1 +1 @@
1
- {"version":3,"file":"auth-api.d.ts","sourceRoot":"","sources":["../src/auth-api.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,OAAO,EACP,UAAU,EACV,SAAS,EAKV,MAAM,YAAY,CAAC;AAGpB,wBAAgB,aAAa,CAAC,MAAM,EAAE;IACpC,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,EAAE,SAAS,CAAC;CAClB,GAAG,OAAO,CAiEV"}
1
+ {"version":3,"file":"auth-api.d.ts","sourceRoot":"","sources":["../src/auth-api.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,OAAO,EACP,UAAU,EACV,SAAS,EAIV,MAAM,YAAY,CAAC;AAIpB,wBAAgB,aAAa,CAAC,MAAM,EAAE;IACpC,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,EAAE,SAAS,CAAC;CAClB,GAAG,OAAO,CAoHV"}
package/dist/auth-api.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { AuthError } from "./types.js";
2
2
  import { extractAccessToken } from "./extract-token.js";
3
+ import { parseAuthUser, parseVerifyOtpUser } from "./auth-user.js";
3
4
  export function createAuthApi(config) {
4
5
  const { client, store } = config;
5
6
  return {
@@ -27,26 +28,64 @@ export function createAuthApi(config) {
27
28
  if (!token) {
28
29
  throw new AuthError(500, "TOKEN_EXTRACTION_FAILED", "No access token found in verify-otp response");
29
30
  }
31
+ const user = parseVerifyOtpUser(raw.user);
32
+ if (!user) {
33
+ throw new AuthError(500, "OTP_USER_INVALID", "Invalid user in verify-otp response");
34
+ }
30
35
  const expiresIn = typeof raw.expiresIn === "number" ? raw.expiresIn : 900;
31
36
  store.setAccessToken(token, expiresIn);
32
37
  return {
33
38
  accessToken: token,
34
39
  expiresIn,
35
- user: raw.user,
40
+ user,
36
41
  };
37
42
  },
38
43
  async logout() {
39
- await client.fetch("/auth/logout", { method: "POST" });
44
+ const token = store.getAccessToken();
45
+ await client.fetch("/auth/logout", {
46
+ method: "POST",
47
+ skipAuth: true,
48
+ ...(token
49
+ ? { headers: { Authorization: `Bearer ${token}` } }
50
+ : {}),
51
+ });
40
52
  },
41
53
  async getMe(token) {
42
- if (token) {
43
- return client.fetch("/auth/me", {
54
+ const currentToken = store.getAccessToken();
55
+ const explicitCurrentToken = token !== undefined && token === currentToken;
56
+ if (store.resolveSession && token === undefined) {
57
+ const resolution = await store.resolveSession();
58
+ if (resolution.status === "ready")
59
+ return resolution.user;
60
+ if (resolution.status === "unauthorized") {
61
+ throw new AuthError(401, "UNAUTHORIZED", "Session is unauthorized");
62
+ }
63
+ if (resolution.status === "superseded") {
64
+ throw new AuthError(409, "SESSION_SUPERSEDED", "Session was superseded");
65
+ }
66
+ throw new AuthError(503, "AUTHORITY_UNAVAILABLE", "Session identity is unavailable");
67
+ }
68
+ const raw = token !== undefined
69
+ ? await client.fetch("/auth/me", {
44
70
  method: "GET",
45
71
  skipAuth: true,
46
72
  headers: { Authorization: `Bearer ${token}` },
73
+ })
74
+ : await client.fetch("/auth/me", {
75
+ method: "GET",
76
+ skipAuth: true,
77
+ ...(currentToken
78
+ ? { headers: { Authorization: `Bearer ${currentToken}` } }
79
+ : {}),
47
80
  });
81
+ if (explicitCurrentToken && store.getAccessToken() !== token) {
82
+ throw new AuthError(409, "SESSION_SUPERSEDED", "Session was superseded");
83
+ }
84
+ const user = parseAuthUser(raw);
85
+ if (!user) {
86
+ throw new AuthError(503, "AUTHORITY_UNAVAILABLE", "Session identity is unavailable");
48
87
  }
49
- return client.fetch("/auth/me", { method: "GET" });
88
+ return user;
50
89
  },
51
90
  };
52
91
  }
@@ -1 +1 @@
1
- {"version":3,"file":"auth-api.js","sourceRoot":"","sources":["../src/auth-api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAUvC,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,UAAU,aAAa,CAAC,MAG7B;IACC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC;IAEjC,OAAO;QACL,KAAK,CAAC,QAAQ,CAAC,KAAoB;YACjC,OAAO,MAAM,CAAC,KAAK,CACjB,gBAAgB,EAChB;gBACE,MAAM,EAAE,MAAM;gBACd,QAAQ,EAAE,IAAI;gBACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;aAC5B,CACF,CAAC;QACJ,CAAC;QAED,KAAK,CAAC,UAAU,CAAC,KAAa;YAC5B,OAAO,MAAM,CAAC,KAAK,CAAqB,mBAAmB,EAAE;gBAC3D,MAAM,EAAE,MAAM;gBACd,QAAQ,EAAE,IAAI;gBACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC;aAChC,CAAC,CAAC;QACL,CAAC;QAED,KAAK,CAAC,SAAS,CAAC,KAAa,EAAE,IAAY;YACzC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,KAAK,CAC5B,kBAAkB,EAClB;gBACE,MAAM,EAAE,MAAM;gBACd,QAAQ,EAAE,IAAI;gBACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;aACtC,CACF,CAAC;YAEF,MAAM,KAAK,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;YACtC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,yBAAyB,EAAE,8CAA8C,CAAC,CAAC;YACtG,CAAC;YAED,MAAM,SAAS,GACb,OAAO,GAAG,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC;YAE1D,KAAK,CAAC,cAAc,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;YAEvC,OAAO;gBACL,WAAW,EAAE,KAAK;gBAClB,SAAS;gBACT,IAAI,EAAE,GAAG,CAAC,IAAgB;aAC3B,CAAC;QACJ,CAAC;QAED,KAAK,CAAC,MAAM;YACV,MAAM,MAAM,CAAC,KAAK,CAAO,cAAc,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAC/D,CAAC;QAED,KAAK,CAAC,KAAK,CAAC,KAAc;YACxB,IAAI,KAAK,EAAE,CAAC;gBACV,OAAO,MAAM,CAAC,KAAK,CAAW,UAAU,EAAE;oBACxC,MAAM,EAAE,KAAK;oBACb,QAAQ,EAAE,IAAI;oBACd,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE;iBAC9C,CAAC,CAAC;YACL,CAAC;YACD,OAAO,MAAM,CAAC,KAAK,CAAW,UAAU,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QAC/D,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"auth-api.js","sourceRoot":"","sources":["../src/auth-api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AASvC,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEnE,MAAM,UAAU,aAAa,CAAC,MAG7B;IACC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC;IAEjC,OAAO;QACL,KAAK,CAAC,QAAQ,CAAC,KAAoB;YACjC,OAAO,MAAM,CAAC,KAAK,CACjB,gBAAgB,EAChB;gBACE,MAAM,EAAE,MAAM;gBACd,QAAQ,EAAE,IAAI;gBACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;aAC5B,CACF,CAAC;QACJ,CAAC;QAED,KAAK,CAAC,UAAU,CAAC,KAAa;YAC5B,OAAO,MAAM,CAAC,KAAK,CAAqB,mBAAmB,EAAE;gBAC3D,MAAM,EAAE,MAAM;gBACd,QAAQ,EAAE,IAAI;gBACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC;aAChC,CAAC,CAAC;QACL,CAAC;QAED,KAAK,CAAC,SAAS,CAAC,KAAa,EAAE,IAAY;YACzC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,KAAK,CAC5B,kBAAkB,EAClB;gBACE,MAAM,EAAE,MAAM;gBACd,QAAQ,EAAE,IAAI;gBACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;aACtC,CACF,CAAC;YAEF,MAAM,KAAK,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;YACtC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,yBAAyB,EAAE,8CAA8C,CAAC,CAAC;YACtG,CAAC;YAED,MAAM,IAAI,GAAG,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC1C,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,MAAM,IAAI,SAAS,CACjB,GAAG,EACH,kBAAkB,EAClB,qCAAqC,CACtC,CAAC;YACJ,CAAC;YAED,MAAM,SAAS,GACb,OAAO,GAAG,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC;YAE1D,KAAK,CAAC,cAAc,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;YAEvC,OAAO;gBACL,WAAW,EAAE,KAAK;gBAClB,SAAS;gBACT,IAAI;aACL,CAAC;QACJ,CAAC;QAED,KAAK,CAAC,MAAM;YACV,MAAM,KAAK,GAAG,KAAK,CAAC,cAAc,EAAE,CAAC;YACrC,MAAM,MAAM,CAAC,KAAK,CAAO,cAAc,EAAE;gBACvC,MAAM,EAAE,MAAM;gBACd,QAAQ,EAAE,IAAI;gBACd,GAAG,CAAC,KAAK;oBACP,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE,EAAE;oBACnD,CAAC,CAAC,EAAE,CAAC;aACR,CAAC,CAAC;QACL,CAAC;QAED,KAAK,CAAC,KAAK,CAAC,KAAc;YACxB,MAAM,YAAY,GAAG,KAAK,CAAC,cAAc,EAAE,CAAC;YAC5C,MAAM,oBAAoB,GAAG,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,YAAY,CAAC;YAC3E,IAAI,KAAK,CAAC,cAAc,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBAChD,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC,cAAc,EAAE,CAAC;gBAChD,IAAI,UAAU,CAAC,MAAM,KAAK,OAAO;oBAAE,OAAO,UAAU,CAAC,IAAI,CAAC;gBAC1D,IAAI,UAAU,CAAC,MAAM,KAAK,cAAc,EAAE,CAAC;oBACzC,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,cAAc,EAAE,yBAAyB,CAAC,CAAC;gBACtE,CAAC;gBACD,IAAI,UAAU,CAAC,MAAM,KAAK,YAAY,EAAE,CAAC;oBACvC,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,oBAAoB,EAAE,wBAAwB,CAAC,CAAC;gBAC3E,CAAC;gBACD,MAAM,IAAI,SAAS,CACjB,GAAG,EACH,uBAAuB,EACvB,iCAAiC,CAClC,CAAC;YACJ,CAAC;YAED,MAAM,GAAG,GAAG,KAAK,KAAK,SAAS;gBAC7B,CAAC,CAAC,MAAM,MAAM,CAAC,KAAK,CAAU,UAAU,EAAE;oBACxC,MAAM,EAAE,KAAK;oBACb,QAAQ,EAAE,IAAI;oBACd,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE;iBAC9C,CAAC;gBACF,CAAC,CAAC,MAAM,MAAM,CAAC,KAAK,CAAU,UAAU,EAAE;oBACtC,MAAM,EAAE,KAAK;oBACb,QAAQ,EAAE,IAAI;oBACd,GAAG,CAAC,YAAY;wBACd,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,YAAY,EAAE,EAAE,EAAE;wBAC1D,CAAC,CAAC,EAAE,CAAC;iBACR,CAAC,CAAC;YACP,IAAI,oBAAoB,IAAI,KAAK,CAAC,cAAc,EAAE,KAAK,KAAK,EAAE,CAAC;gBAC7D,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,oBAAoB,EAAE,wBAAwB,CAAC,CAAC;YAC3E,CAAC;YACD,MAAM,IAAI,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;YAChC,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,MAAM,IAAI,SAAS,CACjB,GAAG,EACH,uBAAuB,EACvB,iCAAiC,CAClC,CAAC;YACJ,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"auth-client.d.ts","sourceRoot":"","sources":["../src/auth-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAqB,MAAM,YAAY,CAAC;AAElF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,UAAU,CAmFrE"}
1
+ {"version":3,"file":"auth-client.d.ts","sourceRoot":"","sources":["../src/auth-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAqB,MAAM,YAAY,CAAC;AAQlF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,UAAU,CA8JrE"}
@@ -1,4 +1,6 @@
1
1
  import { AuthError } from "./types.js";
2
+ import { canReplaySession, captureSessionContinuity, resolveSessionForReplay, } from "./session-continuity.js";
3
+ import { fetchAuthRequest } from "./auth-transport.js";
2
4
  export function createAuthClient(config) {
3
5
  const { baseUrl, store, onAuthFailure } = config;
4
6
  async function parseErrorResponse(res) {
@@ -20,17 +22,32 @@ export function createAuthClient(config) {
20
22
  async fetch(path, options) {
21
23
  const { skipAuth, ...init } = options ?? {};
22
24
  const headers = new Headers(init.headers);
25
+ const continuity = skipAuth ? null : captureSessionContinuity(store);
23
26
  if (!skipAuth) {
24
27
  const token = store.getAccessToken();
25
- if (token) {
26
- headers.set("Authorization", `Bearer ${token}`);
28
+ if (!continuity) {
29
+ throw token
30
+ ? new AuthError(503, "SESSION_UNAVAILABLE", "Session identity is unavailable")
31
+ : new AuthError(401, "UNAUTHORIZED", "Session is unauthorized");
27
32
  }
33
+ headers.set("Authorization", `Bearer ${continuity.token}`);
28
34
  }
29
35
  if (!headers.has("Content-Type") && init.body) {
30
36
  headers.set("Content-Type", "application/json");
31
37
  }
32
- const url = `${baseUrl}${path}`;
33
- const res = await fetch(url, {
38
+ const retryWithToken = async (token) => {
39
+ headers.set("Authorization", `Bearer ${token}`);
40
+ const retryRes = await fetchAuthRequest(baseUrl, path, {
41
+ ...init,
42
+ headers,
43
+ credentials: "include",
44
+ });
45
+ if (retryRes.ok)
46
+ return (await retryRes.json());
47
+ const retryError = await parseErrorResponse(retryRes);
48
+ throw new AuthError(retryRes.status, retryError.code, retryError.error, retryError.remainingAttempts);
49
+ };
50
+ const res = await fetchAuthRequest(baseUrl, path, {
34
51
  ...init,
35
52
  headers,
36
53
  credentials: "include",
@@ -38,23 +55,50 @@ export function createAuthClient(config) {
38
55
  if (res.ok) {
39
56
  return (await res.json());
40
57
  }
41
- // 401: attempt one refresh + retry (only for authenticated requests)
58
+ // 401: refresh once. Replay only when validated user + tenant continuity
59
+ // matches the authority captured before original transport.
42
60
  if (res.status === 401 && !skipAuth) {
43
- const newToken = await store.ensureAccessToken(true);
44
- if (newToken) {
45
- headers.set("Authorization", `Bearer ${newToken}`);
46
- const retryRes = await fetch(url, {
47
- ...init,
48
- headers,
49
- credentials: "include",
61
+ if (!store.resolveSession) {
62
+ const current = captureSessionContinuity(store);
63
+ if (!continuity ||
64
+ !current ||
65
+ current.token !== continuity.token ||
66
+ current.userId !== continuity.userId ||
67
+ current.orgId !== continuity.orgId) {
68
+ throw new AuthError(409, "SESSION_SUPERSEDED", "Request session was superseded");
69
+ }
70
+ store.clearAccessToken();
71
+ if (store.getAccessToken() === null)
72
+ onAuthFailure?.();
73
+ }
74
+ else {
75
+ const resolution = await resolveSessionForReplay(store, continuity, {
76
+ signal: init.signal ?? undefined,
50
77
  });
51
- if (retryRes.ok) {
52
- return (await retryRes.json());
78
+ if (resolution.status === "ready" &&
79
+ canReplaySession(continuity, resolution, store)) {
80
+ return retryWithToken(resolution.token);
81
+ }
82
+ if (resolution.status === "unavailable") {
83
+ throw new AuthError(503, "SESSION_UNAVAILABLE", "Session identity is unavailable");
84
+ }
85
+ if (resolution.status === "superseded") {
86
+ if (init.signal?.aborted) {
87
+ throw new DOMException("Request aborted", "AbortError");
88
+ }
89
+ throw new AuthError(409, "SESSION_SUPERSEDED", "Request session was superseded");
90
+ }
91
+ if (resolution.status === "ready") {
92
+ const currentState = store.getSessionState?.();
93
+ if ((currentState?.status === "resolving" ||
94
+ currentState?.status === "unavailable") &&
95
+ currentState.token === resolution.token) {
96
+ throw new AuthError(503, "SESSION_UNAVAILABLE", "Session identity is unavailable");
97
+ }
98
+ throw new AuthError(409, "SESSION_CONTINUITY_CHANGED", "Request session authority changed");
53
99
  }
100
+ onAuthFailure?.();
54
101
  }
55
- // Refresh failed or retry failed
56
- store.clearAccessToken();
57
- onAuthFailure?.();
58
102
  }
59
103
  const errBody = await parseErrorResponse(res);
60
104
  throw new AuthError(res.status, errBody.code, errBody.error, errBody.remainingAttempts);
@@ -1 +1 @@
1
- {"version":3,"file":"auth-client.js","sourceRoot":"","sources":["../src/auth-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth-client.js","sourceRoot":"","sources":["../src/auth-client.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- import type { AuthStore, AuthStoreConfig } from "./types.js";
2
- export declare function createAuthStore(config: AuthStoreConfig): AuthStore;
1
+ import type { AuthStoreConfig, ConcreteAuthStore } from "./types.js";
2
+ export declare function createAuthStore(config: AuthStoreConfig): ConcreteAuthStore;
3
3
  //# sourceMappingURL=auth-store.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth-store.d.ts","sourceRoot":"","sources":["../src/auth-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,SAAS,EACT,eAAe,EAOhB,MAAM,YAAY,CAAC;AAyEpB,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CAmYlE"}
1
+ {"version":3,"file":"auth-store.d.ts","sourceRoot":"","sources":["../src/auth-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAEV,eAAe,EAEf,iBAAiB,EASlB,MAAM,YAAY,CAAC;AAsDpB,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,iBAAiB,CAsvB1E"}