@c1m-public/platform-auth 0.21.0 → 0.23.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
@@ -28,11 +28,13 @@ sessions never needs them.
28
28
  | `readCookie`, `serializeCookie`, `setCookie`, `deleteCookie` | Cookie helpers over Web `Request`/`Response`. `__Host-` names get `Secure` automatically. |
29
29
  | `createDeviceIdentity` | The `X-Client-Device-ID` cookie the backend requires to create a session. |
30
30
  | `applyForwardedClientHeaders`, `forwardedRequestHeaders` | Copy client identity (IP, geo, UA) onto a backend call. Never used for auth. |
31
- | `safeInternalPath` | Strict same-origin return-path sanitiser (rejects `//`, `://`, encoded slashes, control chars, anything that does not round-trip). |
31
+ | `safeInternalPath(raw, { stripParams? })` | Strict same-origin return-path sanitiser (rejects `//`, `://`, encoded slashes, control chars, anything that does not round-trip). `stripParams` drops named query params (e.g. a one-shot `auth`/`from` flag) from the accepted value so it doesn't survive into the redirect target. |
32
32
  | `createSessionRefresher` | Coalesced refresh-token exchange: one backend call per token per window. |
33
- | `evaluateSessionGate`, `hasSessionShape` | Pure proxy decision — `pass` / `redirect_home` / `anonymous_public` / `unauthorized_api` / `redirect_login`. The proxy owns response construction. |
33
+ | `evaluateSessionGate`, `hasSessionShape` | Pure proxy decision — `pass` / `redirect_home` / `anonymous_public` / `unauthorized_api` / `redirect_login`. The proxy owns response construction. `eagerReauthBufferSeconds` demotes a token expiring within that window to not-live, so a failed refresh rejects the session instead of passing through on the still-valid token. |
34
34
  | `createHandoffTarget`, `createHandoffSource`, `brokerPaths`, `sourceStartPath` | Browser halves of the first-party session handoff (`/api/v1/platform/auth/handoffs/{app}`): PKCE S256 + state, one-time code, host-local cookies, no tokens in URLs. |
35
35
  | `HANDOFF_ERROR_MESSAGES`, `SSO_ERROR_MESSAGES`, `AUTH_ERROR_MESSAGES`, `authErrorMessage(code, overrides?)`, `isAuthErrorCode`, `handoffErrorMessage`, `isHandoffErrorCode` | One sign-in error vocabulary — handoff, admission and SSO codes. Surfaces pass their own copy as `overrides` instead of forking the map. |
36
+ | `ADMIN_HOST_ROOT`, `isAdminRootHost`, `adminSlugFromHost`, `recoveryAdminOrigin`, `brandedAdminHost` | The `<slug>.saasdash.app` / `admin.<domain>` admin-host rules, mirroring the backend's `internal/platform/domainmanagement/admin_dash_hostnames.go`. |
37
+ | `SIGN_IN_HINT_COOKIE`, `signInHintDomain`, `signInHintCookie`, `hasSignInHint` | The non-secret "signed in" display hint an admin host sets on its own domain so the Dash's public site can show "Open your workspace" instead of "Sign in". `signInHintDomain`/`signInHintCookie` are the pure writer half; `hasSignInHint` the reader. The hint carries no identity and must never gate access. |
36
38
 
37
39
  ```ts
38
40
  // proxy.ts
@@ -1 +1 @@
1
- export { a as AccessEntry, b as AccessErrorCode, c as AccessScopeType, A as AccessSnapshot, d as AccessSnapshotError, D as DASH_PERMISSIONS, e as DASH_ROLES, f as DEFAULT_ACCESS_ENDPOINT_PATH, g as DashPermissionKey, h as DashRole, F as FetchAccessSnapshotOptions, L as LEADBROKER_PERMISSIONS, i as LeadBrokerPermissionKey, j as PIPELINE_STAGES, k as PLATFORM_PERMISSIONS, P as PermissionCheckOptions, l as PermissionKey, m as PipelineStage, n as PlatformPermissionKey, W as WORKSPACE_PERMISSIONS, o as WORKSPACE_ROLES, p as WorkspacePermissionKey, q as WorkspaceRole, r as appScopes, s as dashRole, t as emptySnapshot, u as entryCoversApp, v as fetchAccessSnapshot, w as hasAllPermissions, x as hasAnyPermission, y as hasPermission, z as hasWorkspacePermission, B as isAccessSnapshotError, C as isPipelineStage, E as isPlatformOwner, G as managedDashes, H as parseAccessSnapshot, I as permissionsFor } from '../index-XWID1Fkg.js';
1
+ export { a as AccessEntry, b as AccessErrorCode, c as AccessScopeType, A as AccessSnapshot, d as AccessSnapshotError, D as DASH_PERMISSIONS, e as DASH_ROLES, f as DEFAULT_ACCESS_ENDPOINT_PATH, g as DashPermissionKey, h as DashRole, F as FetchAccessSnapshotOptions, L as LEADBROKER_PERMISSIONS, i as LeadBrokerPermissionKey, j as PIPELINE_STAGES, k as PLATFORM_PERMISSIONS, P as PermissionCheckOptions, l as PermissionKey, m as PipelineStage, n as PlatformPermissionKey, W as WORKSPACE_PERMISSIONS, o as WORKSPACE_ROLES, p as WorkspacePermissionKey, q as WorkspaceRole, r as appScopes, s as dashRole, t as emptySnapshot, u as entryCoversApp, v as fetchAccessSnapshot, w as hasAllPermissions, x as hasAnyPermission, y as hasPermission, z as hasWorkspacePermission, B as isAccessSnapshotError, C as isPipelineStage, E as isPlatformOwner, G as managedDashes, H as parseAccessSnapshot, I as permissionsFor } from '../index-FoojTvYk.js';
@@ -306,7 +306,7 @@ function resolveValue(value) {
306
306
  return typeof value === "function" ? value() : value;
307
307
  }
308
308
  async function timedFetch(url, options = {}) {
309
- const doFetch = options.fetch ?? options.fetchImpl ?? fetch;
309
+ const doFetch = options.fetch ?? fetch;
310
310
  const controller = options.timeoutMs ? new AbortController() : void 0;
311
311
  const timer = controller ? setTimeout(() => controller.abort(), options.timeoutMs) : void 0;
312
312
  try {
@@ -331,8 +331,8 @@ async function fetchAccessSnapshot(opts) {
331
331
  try {
332
332
  response = await timedFetch(url, {
333
333
  init: { method: "GET", headers },
334
- timeoutMs: opts.fetchTimeoutMs,
335
- fetchImpl: opts.fetchImpl
334
+ timeoutMs: opts.timeoutMs ?? opts.fetchTimeoutMs,
335
+ fetch: opts.fetch ?? opts.fetchImpl
336
336
  });
337
337
  } catch (error) {
338
338
  throw new AccessSnapshotError("unavailable", `access snapshot request failed: ${error.message}`);
@@ -1,5 +1,5 @@
1
- import { A as AccessSnapshot, P as PermissionCheckOptions, R as Resolvable } from '../index-XWID1Fkg.js';
2
- export { a as AccessEntry, b as AccessErrorCode, c as AccessScopeType, d as AccessSnapshotError, D as DASH_PERMISSIONS, e as DASH_ROLES, f as DEFAULT_ACCESS_ENDPOINT_PATH, g as DashPermissionKey, h as DashRole, F as FetchAccessSnapshotOptions, L as LEADBROKER_PERMISSIONS, i as LeadBrokerPermissionKey, j as PIPELINE_STAGES, k as PLATFORM_PERMISSIONS, l as PermissionKey, m as PipelineStage, n as PlatformPermissionKey, W as WORKSPACE_PERMISSIONS, o as WORKSPACE_ROLES, p as WorkspacePermissionKey, q as WorkspaceRole, r as appScopes, s as dashRole, t as emptySnapshot, u as entryCoversApp, v as fetchAccessSnapshot, w as hasAllPermissions, x as hasAnyPermission, y as hasPermission, z as hasWorkspacePermission, B as isAccessSnapshotError, C as isPipelineStage, E as isPlatformOwner, G as managedDashes, H as parseAccessSnapshot, I as permissionsFor } from '../index-XWID1Fkg.js';
1
+ import { A as AccessSnapshot, P as PermissionCheckOptions, R as Resolvable } from '../index-FoojTvYk.js';
2
+ export { a as AccessEntry, b as AccessErrorCode, c as AccessScopeType, d as AccessSnapshotError, D as DASH_PERMISSIONS, e as DASH_ROLES, f as DEFAULT_ACCESS_ENDPOINT_PATH, g as DashPermissionKey, h as DashRole, F as FetchAccessSnapshotOptions, L as LEADBROKER_PERMISSIONS, i as LeadBrokerPermissionKey, j as PIPELINE_STAGES, k as PLATFORM_PERMISSIONS, l as PermissionKey, m as PipelineStage, n as PlatformPermissionKey, W as WORKSPACE_PERMISSIONS, o as WORKSPACE_ROLES, p as WorkspacePermissionKey, q as WorkspaceRole, r as appScopes, s as dashRole, t as emptySnapshot, u as entryCoversApp, v as fetchAccessSnapshot, w as hasAllPermissions, x as hasAnyPermission, y as hasPermission, z as hasWorkspacePermission, B as isAccessSnapshotError, C as isPipelineStage, E as isPlatformOwner, G as managedDashes, H as parseAccessSnapshot, I as permissionsFor } from '../index-FoojTvYk.js';
3
3
 
4
4
  /** Default cache tag for one subject's snapshot: `access:<subjectId>`. */
5
5
  declare function accessCacheTag(subjectId: string): string;
@@ -14,8 +14,8 @@ interface CreateAccessLoaderOptions {
14
14
  * per-subject rather than sharing one tag across users.
15
15
  */
16
16
  getSubjectId?: () => Promise<string | null>;
17
- fetchTimeoutMs?: number;
18
- fetchImpl?: typeof fetch;
17
+ timeoutMs?: number;
18
+ fetch?: typeof fetch;
19
19
  headers?: () => Promise<HeadersInit | undefined> | HeadersInit | undefined;
20
20
  /**
21
21
  * Optional cross-request cache seam. When provided, the loader calls this
@@ -10,7 +10,7 @@ function resolveValue(value) {
10
10
  return typeof value === "function" ? value() : value;
11
11
  }
12
12
  async function timedFetch(url, options = {}) {
13
- const doFetch = options.fetch ?? options.fetchImpl ?? fetch;
13
+ const doFetch = options.fetch ?? fetch;
14
14
  const controller = options.timeoutMs ? new AbortController() : void 0;
15
15
  const timer = controller ? setTimeout(() => controller.abort(), options.timeoutMs) : void 0;
16
16
  try {
@@ -180,8 +180,8 @@ async function fetchAccessSnapshot(opts) {
180
180
  try {
181
181
  response = await timedFetch(url, {
182
182
  init: { method: "GET", headers },
183
- timeoutMs: opts.fetchTimeoutMs,
184
- fetchImpl: opts.fetchImpl
183
+ timeoutMs: opts.timeoutMs ?? opts.fetchTimeoutMs,
184
+ fetch: opts.fetch ?? opts.fetchImpl
185
185
  });
186
186
  } catch (error) {
187
187
  throw new AccessSnapshotError("unavailable", `access snapshot request failed: ${error.message}`);
@@ -404,8 +404,8 @@ function createAccessLoader(options) {
404
404
  apiUrl: resolveValue(options.apiUrl),
405
405
  accessToken,
406
406
  endpointPath: options.endpointPath,
407
- fetchTimeoutMs: options.fetchTimeoutMs,
408
- fetchImpl: options.fetchImpl,
407
+ timeoutMs: options.timeoutMs,
408
+ fetch: options.fetch,
409
409
  headers
410
410
  });
411
411
  });
@@ -50,12 +50,22 @@ interface AuthResponse {
50
50
  }
51
51
  declare function parseAuthResponse(value: unknown): AuthResponse | null;
52
52
 
53
+ interface SafeInternalPathOptions {
54
+ /**
55
+ * Query params to drop from the accepted value before returning it — e.g. a
56
+ * one-shot `auth`/`from` flag that must not survive into the redirect
57
+ * target and reopen a login modal. Omitted or empty: the value comes back
58
+ * verbatim, as before this option existed.
59
+ */
60
+ stripParams?: readonly string[];
61
+ }
53
62
  /**
54
63
  * Strict same-origin return path: must start with `/`, not `//` or contain `://`,
55
64
  * no encoded slashes or control chars, and round-trip URL parsing unchanged.
56
- * Returns the value verbatim (query/hash kept) or null.
65
+ * Returns the value verbatim (query/hash kept) or null, unless `stripParams`
66
+ * asks for named query params to be dropped from the accepted value.
57
67
  */
58
- declare function safeInternalPath(raw: string | null | undefined): string | null;
68
+ declare function safeInternalPath(raw: string | null | undefined, options?: SafeInternalPathOptions): string | null;
59
69
 
60
70
  declare function applyForwardedClientHeaders(incoming: Headers, headers?: Headers): Headers;
61
71
  declare function forwardedRequestHeaders(request: Request, init?: HeadersInit): Headers;
@@ -130,8 +140,6 @@ interface HandoffTargetConfig {
130
140
  establishSession: (session: AuthResponse, response: Response, request: Request) => Promise<Response | void> | Response | void;
131
141
  secureCookies?: boolean;
132
142
  timeoutMs?: number;
133
- /** @deprecated use `timeoutMs`. */
134
- fetchTimeoutMs?: number;
135
143
  fetch?: typeof fetch;
136
144
  }
137
145
  declare function createHandoffTarget(config: HandoffTargetConfig): {
@@ -159,8 +167,6 @@ interface HandoffSourceConfig {
159
167
  getAccessToken: (request: Request) => Promise<string | null> | string | null;
160
168
  loginPath: string;
161
169
  timeoutMs?: number;
162
- /** @deprecated use `timeoutMs`. */
163
- fetchTimeoutMs?: number;
164
170
  fetch?: typeof fetch;
165
171
  }
166
172
  declare function createHandoffSource(config: HandoffSourceConfig): {
@@ -211,8 +217,6 @@ interface SessionRefresherConfig {
211
217
  singleflightTtlMs?: number;
212
218
  maxSingleflights?: number;
213
219
  timeoutMs?: number;
214
- /** @deprecated use `timeoutMs`. */
215
- fetchTimeoutMs?: number;
216
220
  fetch?: typeof fetch;
217
221
  }
218
222
  /**
@@ -269,7 +273,97 @@ interface SessionGateInput {
269
273
  isLoginPath: boolean;
270
274
  isApiPath: boolean;
271
275
  expiringSoonBufferSeconds?: number;
276
+ /**
277
+ * Demotes a token expiring within this many seconds to not-live, so refresh
278
+ * becomes mandatory rather than best-effort: a failed exchange rejects the
279
+ * session (`redirect_login` / `anonymous_public` / `unauthorized_api`)
280
+ * instead of passing through on the still-valid access token. Unset, a
281
+ * live token always passes even when its pre-emptive refresh fails — the
282
+ * default, and what `expiringSoonBufferSeconds` alone gives you. Set this
283
+ * where a surface wants to force reauthentication near expiry rather than
284
+ * ride out the old token (saasdash-app's proxy did this by hand, passing
285
+ * `accessToken: undefined` once inside its own buffer).
286
+ */
287
+ eagerReauthBufferSeconds?: number;
272
288
  }
273
289
  declare function evaluateSessionGate(input: SessionGateInput): Promise<SessionGateDecision>;
274
290
 
275
- export { type AsyncResolvable as A, decodeDefaultSession as B, type CookieOptions as C, type DeviceIdentity as D, decodeTokenPayload as E, deleteCookie as F, evaluateSessionGate as G, HANDOFF_ERROR_FALLBACK as H, forwardedRequestHeaders as I, handoffErrorMessage as J, hasSessionShape as K, hasTokenAudience as L, isAuthErrorCode as M, isHandoffErrorCode as N, isTokenExpired as O, isTokenExpiringSoon as P, parseAuthResponse as Q, type Resolvable as R, type SessionData as S, type TokenPayload as T, parseRefreshEnvelope as U, readCookie as V, safeInternalPath as W, serializeCookie as X, setCookie as Y, sourceStartPath as Z, tokenMaxAgeSeconds as _, AUTH_ERROR_MESSAGES as a, type AuthResponse as b, type DeviceIdentityConfig as c, type DeviceIdentityStore as d, HANDOFF_ERROR_MESSAGES as e, type HandoffSource as f, type HandoffSourceConfig as g, type HandoffSourceTarget as h, type HandoffTarget as i, type HandoffTargetConfig as j, type RefreshOutcome as k, type RefreshedSession as l, SSO_ERROR_MESSAGES as m, type SessionGateDecision as n, type SessionGateInput as o, type SessionRefresher as p, type SessionRefresherConfig as q, type SessionUser as r, applyForwardedClientHeaders as s, authErrorMessage as t, brokerPaths as u, createDeviceIdentity as v, createHandoffSource as w, createHandoffTarget as x, createSessionRefresher as y, decodeBase64Url as z };
291
+ /**
292
+ * Root of the shared `<slug>.saasdash.app` admin hosts every Dash gets
293
+ * (`c1m-server` `internal/platform/domainmanagement/admin_dash_hostnames.go`
294
+ * `AdminHostRoot`), alongside its own `admin.<domain>` once it has a managed
295
+ * domain.
296
+ */
297
+ declare const ADMIN_HOST_ROOT = "saasdash.app";
298
+ /**
299
+ * True for any host under the shared admin root, excluding the bare root
300
+ * itself (`saasdash.app` is the shared app, not an admin host).
301
+ */
302
+ declare function isAdminRootHost(hostname: string): boolean;
303
+ /**
304
+ * The Dash slug a `<slug>.saasdash.app` admin host is locked to, or null for
305
+ * every other host — including the bare root, a nested label
306
+ * (`a.b.saasdash.app`), and a `www` label, which is a real subdomain here and
307
+ * never stripped: `www.saasdash.app` and `www.<slug>.saasdash.app` are both
308
+ * rejected rather than aliased to `<slug>`.
309
+ */
310
+ declare function adminSlugFromHost(hostname: string): string | null;
311
+ /**
312
+ * The Dash's platform-owned recovery admin origin
313
+ * (`https://<slug>.saasdash.app`). Unlike `admin.<customer domain>` it cannot
314
+ * be broken by the customer's DNS, so links the platform sends itself
315
+ * (billing and security mail, hosted-checkout returns) use it. Mirrors
316
+ * `DashRecoveryBaseURL`. Returns `""` for an empty slug, exactly as the Go
317
+ * source does.
318
+ */
319
+ declare function recoveryAdminOrigin(slug: string): string;
320
+ /**
321
+ * The branded admin host for a Dash's own managed domain (`admin.<domain>`).
322
+ * Mirrors the second entry `DashAdminHostnames` returns once a Dash has a
323
+ * managed domain.
324
+ */
325
+ declare function brandedAdminHost(domain: string): string;
326
+
327
+ /**
328
+ * Set by a Dash's admin host on the Dash's own domain while someone is signed
329
+ * in there. A display hint only ("Open your workspace" instead of "Sign
330
+ * in"): it carries no identity and must never gate access.
331
+ */
332
+ declare const SIGN_IN_HINT_COOKIE = "c1m_signed_in";
333
+ interface SignInHintDomainOptions {
334
+ /**
335
+ * Extra hosts (and their subdomains) that must never receive the hint's
336
+ * cookie Domain because their siblings are other Dashes — e.g. the shared
337
+ * app's own host. `ADMIN_HOST_ROOT` is always excluded, in addition to
338
+ * these.
339
+ */
340
+ excludedRoots?: readonly string[];
341
+ }
342
+ /**
343
+ * The cookie Domain for an admin host's sign-in hint: the host minus its
344
+ * first label (`admin.simplevan.co.uk` → `simplevan.co.uk`). null for the
345
+ * shared admin root (and any `excludedRoots`) or one of their subdomains —
346
+ * their siblings are other Dashes — and for a host with fewer than three
347
+ * labels, which has nothing to strip.
348
+ */
349
+ declare function signInHintDomain(hostname: string, options?: SignInHintDomainOptions): string | null;
350
+ interface SignInHintCookieAttributes {
351
+ name: string;
352
+ value: string;
353
+ domain: string;
354
+ path: "/";
355
+ sameSite: "lax";
356
+ secure: true;
357
+ httpOnly: false;
358
+ maxAge: number;
359
+ }
360
+ /**
361
+ * The cookie attributes to set (`signedIn: true`) or clear (`signedIn:
362
+ * false`) the hint with. Pure data — the caller applies it to its own
363
+ * response.
364
+ */
365
+ declare function signInHintCookie(domain: string, signedIn: boolean): SignInHintCookieAttributes;
366
+ /** Reads the hint from a `Cookie` header or `document.cookie` string. */
367
+ declare function hasSignInHint(cookieHeader: string | null | undefined): boolean;
368
+
369
+ export { isTokenExpiringSoon as $, type AsyncResolvable as A, brandedAdminHost as B, type CookieOptions as C, type DeviceIdentity as D, brokerPaths as E, createDeviceIdentity as F, createHandoffSource as G, HANDOFF_ERROR_FALLBACK as H, createHandoffTarget as I, createSessionRefresher as J, decodeBase64Url as K, decodeDefaultSession as L, decodeTokenPayload as M, deleteCookie as N, evaluateSessionGate as O, forwardedRequestHeaders as P, handoffErrorMessage as Q, type Resolvable as R, type SessionData as S, type TokenPayload as T, hasSessionShape as U, hasSignInHint as V, hasTokenAudience as W, isAdminRootHost as X, isAuthErrorCode as Y, isHandoffErrorCode as Z, isTokenExpired as _, ADMIN_HOST_ROOT as a, parseAuthResponse as a0, parseRefreshEnvelope as a1, readCookie as a2, recoveryAdminOrigin as a3, safeInternalPath as a4, serializeCookie as a5, setCookie as a6, signInHintCookie as a7, signInHintDomain as a8, sourceStartPath as a9, tokenMaxAgeSeconds as aa, AUTH_ERROR_MESSAGES as b, type AuthResponse as c, type DeviceIdentityConfig as d, type DeviceIdentityStore as e, HANDOFF_ERROR_MESSAGES as f, type HandoffSource as g, type HandoffSourceConfig as h, type HandoffSourceTarget as i, type HandoffTarget as j, type HandoffTargetConfig as k, type RefreshOutcome as l, type RefreshedSession as m, SIGN_IN_HINT_COOKIE as n, SSO_ERROR_MESSAGES as o, type SafeInternalPathOptions as p, type SessionGateDecision as q, type SessionGateInput as r, type SessionRefresher as s, type SessionRefresherConfig as t, type SessionUser as u, type SignInHintCookieAttributes as v, type SignInHintDomainOptions as w, adminSlugFromHost as x, applyForwardedClientHeaders as y, authErrorMessage as z };
@@ -138,7 +138,11 @@ interface FetchAccessSnapshotOptions {
138
138
  accessToken: string;
139
139
  /** Defaults to /api/v1/platform/access. */
140
140
  endpointPath?: string;
141
+ timeoutMs?: number;
142
+ fetch?: typeof fetch;
143
+ /** @deprecated use `timeoutMs`; kept until platform_fe's access.server.ts renames it. */
141
144
  fetchTimeoutMs?: number;
145
+ /** @deprecated use `fetch`; kept until platform_fe's access.server.ts renames it. */
142
146
  fetchImpl?: typeof fetch;
143
147
  /** Extra headers. Authorization and Accept are always set here. */
144
148
  headers?: HeadersInit;
package/dist/index.d.ts CHANGED
@@ -1 +1 @@
1
- export { a as AUTH_ERROR_MESSAGES, b as AuthResponse, C as CookieOptions, D as DeviceIdentity, c as DeviceIdentityConfig, d as DeviceIdentityStore, H as HANDOFF_ERROR_FALLBACK, e as HANDOFF_ERROR_MESSAGES, f as HandoffSource, g as HandoffSourceConfig, h as HandoffSourceTarget, i as HandoffTarget, j as HandoffTargetConfig, k as RefreshOutcome, l as RefreshedSession, m as SSO_ERROR_MESSAGES, S as SessionData, n as SessionGateDecision, o as SessionGateInput, p as SessionRefresher, q as SessionRefresherConfig, r as SessionUser, T as TokenPayload, s as applyForwardedClientHeaders, t as authErrorMessage, u as brokerPaths, v as createDeviceIdentity, w as createHandoffSource, x as createHandoffTarget, y as createSessionRefresher, z as decodeBase64Url, B as decodeDefaultSession, E as decodeTokenPayload, F as deleteCookie, G as evaluateSessionGate, I as forwardedRequestHeaders, J as handoffErrorMessage, K as hasSessionShape, L as hasTokenAudience, M as isAuthErrorCode, N as isHandoffErrorCode, O as isTokenExpired, P as isTokenExpiringSoon, Q as parseAuthResponse, U as parseRefreshEnvelope, V as readCookie, W as safeInternalPath, X as serializeCookie, Y as setCookie, Z as sourceStartPath, _ as tokenMaxAgeSeconds } from './index-BxyVV6Wt.js';
1
+ export { a as ADMIN_HOST_ROOT, b as AUTH_ERROR_MESSAGES, c as AuthResponse, C as CookieOptions, D as DeviceIdentity, d as DeviceIdentityConfig, e as DeviceIdentityStore, H as HANDOFF_ERROR_FALLBACK, f as HANDOFF_ERROR_MESSAGES, g as HandoffSource, h as HandoffSourceConfig, i as HandoffSourceTarget, j as HandoffTarget, k as HandoffTargetConfig, l as RefreshOutcome, m as RefreshedSession, n as SIGN_IN_HINT_COOKIE, o as SSO_ERROR_MESSAGES, p as SafeInternalPathOptions, S as SessionData, q as SessionGateDecision, r as SessionGateInput, s as SessionRefresher, t as SessionRefresherConfig, u as SessionUser, v as SignInHintCookieAttributes, w as SignInHintDomainOptions, T as TokenPayload, x as adminSlugFromHost, y as applyForwardedClientHeaders, z as authErrorMessage, B as brandedAdminHost, E as brokerPaths, F as createDeviceIdentity, G as createHandoffSource, I as createHandoffTarget, J as createSessionRefresher, K as decodeBase64Url, L as decodeDefaultSession, M as decodeTokenPayload, N as deleteCookie, O as evaluateSessionGate, P as forwardedRequestHeaders, Q as handoffErrorMessage, U as hasSessionShape, V as hasSignInHint, W as hasTokenAudience, X as isAdminRootHost, Y as isAuthErrorCode, Z as isHandoffErrorCode, _ as isTokenExpired, $ as isTokenExpiringSoon, a0 as parseAuthResponse, a1 as parseRefreshEnvelope, a2 as readCookie, a3 as recoveryAdminOrigin, a4 as safeInternalPath, a5 as serializeCookie, a6 as setCookie, a7 as signInHintCookie, a8 as signInHintDomain, a9 as sourceStartPath, aa as tokenMaxAgeSeconds } from './index-BKUzRIXJ.js';
package/dist/index.js CHANGED
@@ -81,7 +81,7 @@ function parseAuthResponse(value) {
81
81
  }
82
82
 
83
83
  // src/return-path.ts
84
- function safeInternalPath(raw) {
84
+ function safeInternalPath(raw, options) {
85
85
  if (raw == null) return null;
86
86
  const candidate = raw.trim();
87
87
  if (candidate.length === 0) return null;
@@ -94,7 +94,11 @@ function safeInternalPath(raw) {
94
94
  if (parsed.origin !== "https://internal.invalid") return null;
95
95
  const roundtrip = `${parsed.pathname}${parsed.search}${parsed.hash}`;
96
96
  if (roundtrip !== candidate) return null;
97
- return candidate;
97
+ if (!options?.stripParams?.length) return candidate;
98
+ const params = new URLSearchParams(parsed.search);
99
+ for (const key of options.stripParams) params.delete(key);
100
+ const query = params.toString();
101
+ return `${parsed.pathname}${query ? `?${query}` : ""}${parsed.hash}`;
98
102
  } catch {
99
103
  return null;
100
104
  }
@@ -273,7 +277,7 @@ function resolveAll(...values) {
273
277
  }
274
278
  var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
275
279
  async function timedFetch(url, options = {}) {
276
- const doFetch = options.fetch ?? options.fetchImpl ?? fetch;
280
+ const doFetch = options.fetch ?? fetch;
277
281
  const controller = options.timeoutMs ? new AbortController() : void 0;
278
282
  const timer = controller ? setTimeout(() => controller.abort(), options.timeoutMs) : void 0;
279
283
  try {
@@ -337,7 +341,7 @@ function createHandoffTarget(config) {
337
341
  const prefix = `${secure ? "__Host-" : ""}${config.cookiePrefix}_handoff_`;
338
342
  const cookies = { state: `${prefix}state`, verifier: `${prefix}verifier`, from: `${prefix}from` };
339
343
  const cookieOptions = { httpOnly: true, secure, sameSite: "lax", path: "/", maxAge: HANDOFF_COOKIE_TTL_SECONDS };
340
- const timeout = config.timeoutMs ?? config.fetchTimeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
344
+ const timeout = config.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
341
345
  const clearHandoffCookies = (response) => {
342
346
  let result = response;
343
347
  for (const name of Object.values(cookies)) {
@@ -408,7 +412,7 @@ function createHandoffTarget(config) {
408
412
  var DEFAULT_TARGET_CALLBACK = "/api/auth/platform-sso/callback";
409
413
  var DEFAULT_TARGET_LOGIN = "/login";
410
414
  function createHandoffSource(config) {
411
- const timeout = config.timeoutMs ?? config.fetchTimeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
415
+ const timeout = config.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
412
416
  return {
413
417
  /** GET `/api/auth/handoffs/{app}/start?code_challenge=&state=` */
414
418
  async start(request, app) {
@@ -478,7 +482,7 @@ function parseRefreshEnvelope(value) {
478
482
  function createSessionRefresher(config) {
479
483
  const ttl = config.singleflightTtlMs ?? DEFAULT_TTL_MS;
480
484
  const max = config.maxSingleflights ?? DEFAULT_MAX;
481
- const timeoutMs = config.timeoutMs ?? config.fetchTimeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
485
+ const timeoutMs = config.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
482
486
  const inflight = /* @__PURE__ */ new Map();
483
487
  async function perform(refreshToken) {
484
488
  try {
@@ -564,7 +568,9 @@ function hasSessionShape(token) {
564
568
  }
565
569
  async function evaluateSessionGate(input) {
566
570
  const { accessToken, refreshToken, audience, refresher, isPublic, isLoginPath, isApiPath } = input;
567
- const live = !!accessToken && hasSessionShape(accessToken) && hasTokenAudience(accessToken, audience) && !isTokenExpired(accessToken);
571
+ const rawLive = !!accessToken && hasSessionShape(accessToken) && hasTokenAudience(accessToken, audience) && !isTokenExpired(accessToken);
572
+ const eagerlyExpiring = rawLive && input.eagerReauthBufferSeconds != null && isTokenExpiringSoon(accessToken, input.eagerReauthBufferSeconds);
573
+ const live = rawLive && !eagerlyExpiring;
568
574
  const attempt = async (token) => refresher.attemptRefresh ? await refresher.attemptRefresh(token) : await refresher.refresh(token).then(
569
575
  (session) => session ? { status: "refreshed", session } : { status: "rejected" }
570
576
  );
@@ -588,4 +594,78 @@ async function evaluateSessionGate(input) {
588
594
  return { kind: "redirect_login" };
589
595
  }
590
596
 
591
- export { AUTH_ERROR_MESSAGES, HANDOFF_ERROR_FALLBACK, HANDOFF_ERROR_MESSAGES, SSO_ERROR_MESSAGES, applyForwardedClientHeaders2 as applyForwardedClientHeaders, authErrorMessage, brokerPaths, createDeviceIdentity, createHandoffSource, createHandoffTarget, createSessionRefresher, decodeBase64Url, decodeDefaultSession, decodeTokenPayload, deleteCookie, evaluateSessionGate, forwardedRequestHeaders2 as forwardedRequestHeaders, handoffErrorMessage, hasSessionShape, hasTokenAudience, isAuthErrorCode, isHandoffErrorCode, isTokenExpired, isTokenExpiringSoon, parseAuthResponse, parseRefreshEnvelope, readCookie, safeInternalPath, serializeCookie, setCookie, sourceStartPath, tokenMaxAgeSeconds };
597
+ // src/hosts.ts
598
+ var ADMIN_HOST_ROOT = "saasdash.app";
599
+ function normalizeRequestHost(hostname) {
600
+ let host = hostname.trim().toLowerCase();
601
+ if (host.startsWith("[")) {
602
+ const end = host.indexOf("]");
603
+ host = end >= 0 ? host.slice(1, end) : host;
604
+ } else {
605
+ const colon = host.lastIndexOf(":");
606
+ if (colon >= 0) host = host.slice(0, colon);
607
+ }
608
+ while (host.endsWith(".")) host = host.slice(0, -1);
609
+ return host;
610
+ }
611
+ function normalizeGeneratedHost(raw) {
612
+ let host = raw.trim().toLowerCase();
613
+ host = host.replace(/^https?:\/\//, "");
614
+ host = host.replace(/^www\./, "");
615
+ host = host.replace(/\/$/, "");
616
+ return host;
617
+ }
618
+ function isAdminRootHost(hostname) {
619
+ const host = normalizeRequestHost(hostname);
620
+ return host !== ADMIN_HOST_ROOT && host.endsWith(`.${ADMIN_HOST_ROOT}`);
621
+ }
622
+ function adminSlugFromHost(hostname) {
623
+ if (!isAdminRootHost(hostname)) return null;
624
+ const host = normalizeRequestHost(hostname);
625
+ const slug = host.slice(0, host.length - `.${ADMIN_HOST_ROOT}`.length);
626
+ if (!slug || slug === "www" || slug.includes(".")) return null;
627
+ return slug;
628
+ }
629
+ function recoveryAdminOrigin(slug) {
630
+ const normalized = normalizeGeneratedHost(slug);
631
+ if (!normalized) return "";
632
+ return `https://${normalized}.${ADMIN_HOST_ROOT}`;
633
+ }
634
+ function brandedAdminHost(domain) {
635
+ return `admin.${normalizeGeneratedHost(domain)}`;
636
+ }
637
+
638
+ // src/sign-in-hint.ts
639
+ var SIGN_IN_HINT_COOKIE = "c1m_signed_in";
640
+ function normalizeHost(hostname) {
641
+ return hostname.split(":")[0]?.trim().toLowerCase() ?? "";
642
+ }
643
+ function signInHintDomain(hostname, options = {}) {
644
+ const host = normalizeHost(hostname);
645
+ const roots = [ADMIN_HOST_ROOT, ...options.excludedRoots ?? []];
646
+ for (const raw of roots) {
647
+ const root = normalizeHost(raw);
648
+ if (!host || !root || host === root || host.endsWith(`.${root}`)) return null;
649
+ }
650
+ const labels = host.split(".");
651
+ if (labels.length < 3) return null;
652
+ return labels.slice(1).join(".");
653
+ }
654
+ function signInHintCookie(domain, signedIn) {
655
+ return {
656
+ name: SIGN_IN_HINT_COOKIE,
657
+ value: signedIn ? "1" : "",
658
+ domain,
659
+ path: "/",
660
+ sameSite: "lax",
661
+ secure: true,
662
+ httpOnly: false,
663
+ maxAge: signedIn ? 30 * 24 * 60 * 60 : 0
664
+ };
665
+ }
666
+ function hasSignInHint(cookieHeader) {
667
+ if (!cookieHeader) return false;
668
+ return cookieHeader.split(";").some((pair) => pair.trim() === `${SIGN_IN_HINT_COOKIE}=1`);
669
+ }
670
+
671
+ export { ADMIN_HOST_ROOT, AUTH_ERROR_MESSAGES, HANDOFF_ERROR_FALLBACK, HANDOFF_ERROR_MESSAGES, SIGN_IN_HINT_COOKIE, SSO_ERROR_MESSAGES, adminSlugFromHost, applyForwardedClientHeaders2 as applyForwardedClientHeaders, authErrorMessage, brandedAdminHost, brokerPaths, createDeviceIdentity, createHandoffSource, createHandoffTarget, createSessionRefresher, decodeBase64Url, decodeDefaultSession, decodeTokenPayload, deleteCookie, evaluateSessionGate, forwardedRequestHeaders2 as forwardedRequestHeaders, handoffErrorMessage, hasSessionShape, hasSignInHint, hasTokenAudience, isAdminRootHost, isAuthErrorCode, isHandoffErrorCode, isTokenExpired, isTokenExpiringSoon, parseAuthResponse, parseRefreshEnvelope, readCookie, recoveryAdminOrigin, safeInternalPath, serializeCookie, setCookie, signInHintCookie, signInHintDomain, sourceStartPath, tokenMaxAgeSeconds };
package/dist/next.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as Resolvable, A as AsyncResolvable, S as SessionData, C as CookieOptions } from './index-BxyVV6Wt.js';
2
- export { a as AUTH_ERROR_MESSAGES, b as AuthResponse, D as DeviceIdentity, c as DeviceIdentityConfig, d as DeviceIdentityStore, H as HANDOFF_ERROR_FALLBACK, e as HANDOFF_ERROR_MESSAGES, f as HandoffSource, g as HandoffSourceConfig, h as HandoffSourceTarget, i as HandoffTarget, j as HandoffTargetConfig, k as RefreshOutcome, l as RefreshedSession, m as SSO_ERROR_MESSAGES, n as SessionGateDecision, o as SessionGateInput, p as SessionRefresher, q as SessionRefresherConfig, r as SessionUser, T as TokenPayload, s as applyForwardedClientHeaders, t as authErrorMessage, u as brokerPaths, v as createDeviceIdentity, w as createHandoffSource, x as createHandoffTarget, y as createSessionRefresher, z as decodeBase64Url, B as decodeDefaultSession, E as decodeTokenPayload, F as deleteCookie, G as evaluateSessionGate, I as forwardedRequestHeaders, J as handoffErrorMessage, K as hasSessionShape, L as hasTokenAudience, M as isAuthErrorCode, N as isHandoffErrorCode, O as isTokenExpired, P as isTokenExpiringSoon, Q as parseAuthResponse, U as parseRefreshEnvelope, V as readCookie, W as safeInternalPath, X as serializeCookie, Y as setCookie, Z as sourceStartPath, _ as tokenMaxAgeSeconds } from './index-BxyVV6Wt.js';
1
+ import { R as Resolvable, A as AsyncResolvable, S as SessionData, C as CookieOptions } from './index-BKUzRIXJ.js';
2
+ export { a as ADMIN_HOST_ROOT, b as AUTH_ERROR_MESSAGES, c as AuthResponse, D as DeviceIdentity, d as DeviceIdentityConfig, e as DeviceIdentityStore, H as HANDOFF_ERROR_FALLBACK, f as HANDOFF_ERROR_MESSAGES, g as HandoffSource, h as HandoffSourceConfig, i as HandoffSourceTarget, j as HandoffTarget, k as HandoffTargetConfig, l as RefreshOutcome, m as RefreshedSession, n as SIGN_IN_HINT_COOKIE, o as SSO_ERROR_MESSAGES, p as SafeInternalPathOptions, q as SessionGateDecision, r as SessionGateInput, s as SessionRefresher, t as SessionRefresherConfig, u as SessionUser, v as SignInHintCookieAttributes, w as SignInHintDomainOptions, T as TokenPayload, x as adminSlugFromHost, y as applyForwardedClientHeaders, z as authErrorMessage, B as brandedAdminHost, E as brokerPaths, F as createDeviceIdentity, G as createHandoffSource, I as createHandoffTarget, J as createSessionRefresher, K as decodeBase64Url, L as decodeDefaultSession, M as decodeTokenPayload, N as deleteCookie, O as evaluateSessionGate, P as forwardedRequestHeaders, Q as handoffErrorMessage, U as hasSessionShape, V as hasSignInHint, W as hasTokenAudience, X as isAdminRootHost, Y as isAuthErrorCode, Z as isHandoffErrorCode, _ as isTokenExpired, $ as isTokenExpiringSoon, a0 as parseAuthResponse, a1 as parseRefreshEnvelope, a2 as readCookie, a3 as recoveryAdminOrigin, a4 as safeInternalPath, a5 as serializeCookie, a6 as setCookie, a7 as signInHintCookie, a8 as signInHintDomain, a9 as sourceStartPath, aa as tokenMaxAgeSeconds } from './index-BKUzRIXJ.js';
3
3
 
4
4
  /**
5
5
  * The error vocabulary every first-party platform surface uses at its edge,
package/dist/next.js CHANGED
@@ -86,7 +86,7 @@ function parseAuthResponse(value) {
86
86
  }
87
87
 
88
88
  // src/return-path.ts
89
- function safeInternalPath(raw) {
89
+ function safeInternalPath(raw, options) {
90
90
  if (raw == null) return null;
91
91
  const candidate = raw.trim();
92
92
  if (candidate.length === 0) return null;
@@ -99,7 +99,11 @@ function safeInternalPath(raw) {
99
99
  if (parsed.origin !== "https://internal.invalid") return null;
100
100
  const roundtrip = `${parsed.pathname}${parsed.search}${parsed.hash}`;
101
101
  if (roundtrip !== candidate) return null;
102
- return candidate;
102
+ if (!options?.stripParams?.length) return candidate;
103
+ const params = new URLSearchParams(parsed.search);
104
+ for (const key of options.stripParams) params.delete(key);
105
+ const query = params.toString();
106
+ return `${parsed.pathname}${query ? `?${query}` : ""}${parsed.hash}`;
103
107
  } catch {
104
108
  return null;
105
109
  }
@@ -278,7 +282,7 @@ function resolveAll(...values) {
278
282
  }
279
283
  var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
280
284
  async function timedFetch(url, options = {}) {
281
- const doFetch = options.fetch ?? options.fetchImpl ?? fetch;
285
+ const doFetch = options.fetch ?? fetch;
282
286
  const controller = options.timeoutMs ? new AbortController() : void 0;
283
287
  const timer = controller ? setTimeout(() => controller.abort(), options.timeoutMs) : void 0;
284
288
  try {
@@ -342,7 +346,7 @@ function createHandoffTarget(config) {
342
346
  const prefix = `${secure ? "__Host-" : ""}${config.cookiePrefix}_handoff_`;
343
347
  const cookies = { state: `${prefix}state`, verifier: `${prefix}verifier`, from: `${prefix}from` };
344
348
  const cookieOptions = { httpOnly: true, secure, sameSite: "lax", path: "/", maxAge: HANDOFF_COOKIE_TTL_SECONDS };
345
- const timeout = config.timeoutMs ?? config.fetchTimeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
349
+ const timeout = config.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
346
350
  const clearHandoffCookies = (response) => {
347
351
  let result = response;
348
352
  for (const name of Object.values(cookies)) {
@@ -413,7 +417,7 @@ function createHandoffTarget(config) {
413
417
  var DEFAULT_TARGET_CALLBACK = "/api/auth/platform-sso/callback";
414
418
  var DEFAULT_TARGET_LOGIN = "/login";
415
419
  function createHandoffSource(config) {
416
- const timeout = config.timeoutMs ?? config.fetchTimeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
420
+ const timeout = config.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
417
421
  return {
418
422
  /** GET `/api/auth/handoffs/{app}/start?code_challenge=&state=` */
419
423
  async start(request, app) {
@@ -483,7 +487,7 @@ function parseRefreshEnvelope(value) {
483
487
  function createSessionRefresher(config) {
484
488
  const ttl = config.singleflightTtlMs ?? DEFAULT_TTL_MS;
485
489
  const max = config.maxSingleflights ?? DEFAULT_MAX;
486
- const timeoutMs = config.timeoutMs ?? config.fetchTimeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
490
+ const timeoutMs = config.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
487
491
  const inflight = /* @__PURE__ */ new Map();
488
492
  async function perform(refreshToken) {
489
493
  try {
@@ -569,7 +573,9 @@ function hasSessionShape(token) {
569
573
  }
570
574
  async function evaluateSessionGate(input) {
571
575
  const { accessToken, refreshToken, audience, refresher, isPublic, isLoginPath, isApiPath } = input;
572
- const live = !!accessToken && hasSessionShape(accessToken) && hasTokenAudience(accessToken, audience) && !isTokenExpired(accessToken);
576
+ const rawLive = !!accessToken && hasSessionShape(accessToken) && hasTokenAudience(accessToken, audience) && !isTokenExpired(accessToken);
577
+ const eagerlyExpiring = rawLive && input.eagerReauthBufferSeconds != null && isTokenExpiringSoon(accessToken, input.eagerReauthBufferSeconds);
578
+ const live = rawLive && !eagerlyExpiring;
573
579
  const attempt = async (token) => refresher.attemptRefresh ? await refresher.attemptRefresh(token) : await refresher.refresh(token).then(
574
580
  (session) => session ? { status: "refreshed", session } : { status: "rejected" }
575
581
  );
@@ -593,6 +599,80 @@ async function evaluateSessionGate(input) {
593
599
  return { kind: "redirect_login" };
594
600
  }
595
601
 
602
+ // src/hosts.ts
603
+ var ADMIN_HOST_ROOT = "saasdash.app";
604
+ function normalizeRequestHost(hostname) {
605
+ let host = hostname.trim().toLowerCase();
606
+ if (host.startsWith("[")) {
607
+ const end = host.indexOf("]");
608
+ host = end >= 0 ? host.slice(1, end) : host;
609
+ } else {
610
+ const colon = host.lastIndexOf(":");
611
+ if (colon >= 0) host = host.slice(0, colon);
612
+ }
613
+ while (host.endsWith(".")) host = host.slice(0, -1);
614
+ return host;
615
+ }
616
+ function normalizeGeneratedHost(raw) {
617
+ let host = raw.trim().toLowerCase();
618
+ host = host.replace(/^https?:\/\//, "");
619
+ host = host.replace(/^www\./, "");
620
+ host = host.replace(/\/$/, "");
621
+ return host;
622
+ }
623
+ function isAdminRootHost(hostname) {
624
+ const host = normalizeRequestHost(hostname);
625
+ return host !== ADMIN_HOST_ROOT && host.endsWith(`.${ADMIN_HOST_ROOT}`);
626
+ }
627
+ function adminSlugFromHost(hostname) {
628
+ if (!isAdminRootHost(hostname)) return null;
629
+ const host = normalizeRequestHost(hostname);
630
+ const slug = host.slice(0, host.length - `.${ADMIN_HOST_ROOT}`.length);
631
+ if (!slug || slug === "www" || slug.includes(".")) return null;
632
+ return slug;
633
+ }
634
+ function recoveryAdminOrigin(slug) {
635
+ const normalized = normalizeGeneratedHost(slug);
636
+ if (!normalized) return "";
637
+ return `https://${normalized}.${ADMIN_HOST_ROOT}`;
638
+ }
639
+ function brandedAdminHost(domain) {
640
+ return `admin.${normalizeGeneratedHost(domain)}`;
641
+ }
642
+
643
+ // src/sign-in-hint.ts
644
+ var SIGN_IN_HINT_COOKIE = "c1m_signed_in";
645
+ function normalizeHost(hostname) {
646
+ return hostname.split(":")[0]?.trim().toLowerCase() ?? "";
647
+ }
648
+ function signInHintDomain(hostname, options = {}) {
649
+ const host = normalizeHost(hostname);
650
+ const roots = [ADMIN_HOST_ROOT, ...options.excludedRoots ?? []];
651
+ for (const raw of roots) {
652
+ const root = normalizeHost(raw);
653
+ if (!host || !root || host === root || host.endsWith(`.${root}`)) return null;
654
+ }
655
+ const labels = host.split(".");
656
+ if (labels.length < 3) return null;
657
+ return labels.slice(1).join(".");
658
+ }
659
+ function signInHintCookie(domain, signedIn) {
660
+ return {
661
+ name: SIGN_IN_HINT_COOKIE,
662
+ value: signedIn ? "1" : "",
663
+ domain,
664
+ path: "/",
665
+ sameSite: "lax",
666
+ secure: true,
667
+ httpOnly: false,
668
+ maxAge: signedIn ? 30 * 24 * 60 * 60 : 0
669
+ };
670
+ }
671
+ function hasSignInHint(cookieHeader) {
672
+ if (!cookieHeader) return false;
673
+ return cookieHeader.split(";").some((pair) => pair.trim() === `${SIGN_IN_HINT_COOKIE}=1`);
674
+ }
675
+
596
676
  // src/next/errors.ts
597
677
  var SessionExpiredError = class extends Error {
598
678
  constructor() {
@@ -900,4 +980,4 @@ function createSessionStore(config) {
900
980
  };
901
981
  }
902
982
 
903
- export { AUTH_ERROR_MESSAGES, HANDOFF_ERROR_FALLBACK, HANDOFF_ERROR_MESSAGES, PlatformServerFetchError, RecentAuthenticationRequiredError, SSOReauthenticationRequiredError, SSO_ERROR_MESSAGES, SessionExpiredError, applyForwardedClientHeaders2 as applyForwardedClientHeaders, authErrorMessage, authenticationErrorFor, brokerPaths, buildForwardedRequestHeaders, controlRequest, createDeviceIdentity, createHandoffSource, createHandoffTarget, createPlatformFetch, createSessionRefresher, createSessionStore, decodeBase64Url, decodeDefaultSession, decodeTokenPayload, deleteCookie, evaluateSessionGate, forwardedRequestHeaders2 as forwardedRequestHeaders, handoffErrorMessage, hasSessionShape, hasTokenAudience, isAuthErrorCode, isHandoffErrorCode, isPlatformServerFetchError, isRecentAuthenticationRequiredError, isSSOReauthenticationRequiredError, isSessionExpiredError, isTokenExpired, isTokenExpiringSoon, mapControlError, parseAuthResponse, parseRefreshEnvelope, readCookie, safeInternalPath, serializeCookie, setCookie, sourceStartPath, tokenMaxAgeSeconds };
983
+ export { ADMIN_HOST_ROOT, AUTH_ERROR_MESSAGES, HANDOFF_ERROR_FALLBACK, HANDOFF_ERROR_MESSAGES, PlatformServerFetchError, RecentAuthenticationRequiredError, SIGN_IN_HINT_COOKIE, SSOReauthenticationRequiredError, SSO_ERROR_MESSAGES, SessionExpiredError, adminSlugFromHost, applyForwardedClientHeaders2 as applyForwardedClientHeaders, authErrorMessage, authenticationErrorFor, brandedAdminHost, brokerPaths, buildForwardedRequestHeaders, controlRequest, createDeviceIdentity, createHandoffSource, createHandoffTarget, createPlatformFetch, createSessionRefresher, createSessionStore, decodeBase64Url, decodeDefaultSession, decodeTokenPayload, deleteCookie, evaluateSessionGate, forwardedRequestHeaders2 as forwardedRequestHeaders, handoffErrorMessage, hasSessionShape, hasSignInHint, hasTokenAudience, isAdminRootHost, isAuthErrorCode, isHandoffErrorCode, isPlatformServerFetchError, isRecentAuthenticationRequiredError, isSSOReauthenticationRequiredError, isSessionExpiredError, isTokenExpired, isTokenExpiringSoon, mapControlError, parseAuthResponse, parseRefreshEnvelope, readCookie, recoveryAdminOrigin, safeInternalPath, serializeCookie, setCookie, signInHintCookie, signInHintDomain, sourceStartPath, tokenMaxAgeSeconds };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c1m-public/platform-auth",
3
- "version": "0.21.0",
3
+ "version": "0.23.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Session and authorization helpers for C1M platform surfaces. The root is runtime-neutral (Web Request/Response): token/session reads, the first-party handoff (PKCE target + source), proxy session gate and coalesced refresh; ./next adds the server-only guard; ./access carries the resolved access snapshot, typed permission keys, predicates, a Next loader and React bindings",
@@ -55,8 +55,8 @@
55
55
  "tsup": "^8.5.1",
56
56
  "typescript": "5.9.3",
57
57
  "vitest": "^4.1.4",
58
- "@c1m-public/resilience": "0.1.0",
59
- "@c1m-public/transport": "0.0.0"
58
+ "@c1m-public/transport": "0.0.0",
59
+ "@c1m-public/resilience": "0.1.0"
60
60
  },
61
61
  "peerDependencies": {
62
62
  "next": ">=15",