@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 +4 -2
- package/dist/access/index.d.ts +1 -1
- package/dist/access/index.js +3 -3
- package/dist/access/next.d.ts +4 -4
- package/dist/access/next.js +5 -5
- package/dist/{index-BxyVV6Wt.d.ts → index-BKUzRIXJ.d.ts} +103 -9
- package/dist/{index-XWID1Fkg.d.ts → index-FoojTvYk.d.ts} +4 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +88 -8
- package/dist/next.d.ts +2 -2
- package/dist/next.js +88 -8
- package/package.json +3 -3
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
|
package/dist/access/index.d.ts
CHANGED
|
@@ -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-
|
|
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';
|
package/dist/access/index.js
CHANGED
|
@@ -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 ??
|
|
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
|
-
|
|
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}`);
|
package/dist/access/next.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as AccessSnapshot, P as PermissionCheckOptions, R as Resolvable } from '../index-
|
|
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-
|
|
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
|
-
|
|
18
|
-
|
|
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
|
package/dist/access/next.js
CHANGED
|
@@ -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 ??
|
|
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
|
-
|
|
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
|
-
|
|
408
|
-
|
|
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
|
-
|
|
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
|
|
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 ??
|
|
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 ??
|
|
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 ??
|
|
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 ??
|
|
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
|
|
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
|
-
|
|
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-
|
|
2
|
-
export { a as
|
|
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 ??
|
|
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 ??
|
|
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 ??
|
|
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 ??
|
|
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
|
|
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.
|
|
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/
|
|
59
|
-
"@c1m-public/
|
|
58
|
+
"@c1m-public/transport": "0.0.0",
|
|
59
|
+
"@c1m-public/resilience": "0.1.0"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
62
|
"next": ">=15",
|