@c1m-public/platform-auth 0.6.0 → 0.8.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.
Files changed (47) hide show
  1. package/README.md +27 -0
  2. package/dist/access/index.d.ts +148 -5
  3. package/dist/access/index.js +2 -5
  4. package/dist/access/next.d.ts +11 -10
  5. package/dist/access/next.js +70 -81
  6. package/dist/access/react.d.ts +59 -12
  7. package/dist/access/react.js +84 -37
  8. package/dist/chunk-FBC2NUH7.js +83 -0
  9. package/dist/chunk-OPF4UZLE.js +261 -0
  10. package/dist/chunk-P4LKOHR4.js +47 -0
  11. package/dist/chunk-QV7HSZLE.js +392 -0
  12. package/dist/index.d.ts +224 -9
  13. package/dist/index.js +3 -9
  14. package/dist/internal-D6RafCm2.d.ts +6 -0
  15. package/dist/next.d.ts +171 -2
  16. package/dist/next.js +307 -3
  17. package/package.json +6 -4
  18. package/dist/access/errors.d.ts +0 -13
  19. package/dist/access/errors.js +0 -19
  20. package/dist/access/fetch.d.ts +0 -23
  21. package/dist/access/fetch.js +0 -43
  22. package/dist/access/generated/keys.d.ts +0 -20
  23. package/dist/access/generated/keys.js +0 -87
  24. package/dist/access/predicates.d.ts +0 -48
  25. package/dist/access/predicates.js +0 -95
  26. package/dist/access/snapshot.d.ts +0 -39
  27. package/dist/access/snapshot.js +0 -92
  28. package/dist/cookies.d.ts +0 -11
  29. package/dist/cookies.js +0 -42
  30. package/dist/device-cookie.d.ts +0 -18
  31. package/dist/device-cookie.js +0 -22
  32. package/dist/forwarding.d.ts +0 -2
  33. package/dist/forwarding.js +0 -32
  34. package/dist/handoff-errors.d.ts +0 -13
  35. package/dist/handoff-errors.js +0 -65
  36. package/dist/handoff.d.ts +0 -55
  37. package/dist/handoff.js +0 -195
  38. package/dist/internal.d.ts +0 -16
  39. package/dist/internal.js +0 -27
  40. package/dist/return-path.d.ts +0 -6
  41. package/dist/return-path.js +0 -33
  42. package/dist/session-gate.d.ts +0 -26
  43. package/dist/session-gate.js +0 -33
  44. package/dist/session-refresh.d.ts +0 -24
  45. package/dist/session-refresh.js +0 -62
  46. package/dist/token.d.ts +0 -51
  47. package/dist/token.js +0 -104
@@ -1,87 +0,0 @@
1
- // GENERATED by `node scripts/generate-access-types.mjs` from contracts/authz-keys.json
2
- // (vendored from c1m-server/schemas/api/authz-keys.json). Do not edit by hand.
3
- /** Platform-operator permission keys. */
4
- export const PLATFORM_PERMISSIONS = [
5
- "affiliates.finance.manage",
6
- "affiliates.partners.manage",
7
- "affiliates.programmes.manage",
8
- "affiliates.view",
9
- "linkservice.domains.manage",
10
- "linkservice.links.manage",
11
- "linkservice.qr.manage",
12
- "linkservice.view",
13
- "platform.admin.view",
14
- "platform.audit.view",
15
- "platform.auth.manage",
16
- "platform.billing.manage",
17
- "platform.channels.manage",
18
- "platform.channels.view",
19
- "platform.config.manage",
20
- "platform.docs.manage",
21
- "platform.docs.view",
22
- "platform.observability.manage",
23
- "platform.observability.view",
24
- "platform.offboarding.view",
25
- "platform.ops.manage",
26
- "platform.support.impersonate",
27
- "platform.support.sessions",
28
- "platform.tenants.archive",
29
- "platform.tenants.manage",
30
- "platform.tenants.restore",
31
- "platform.users.deactivate",
32
- "platform.users.delete",
33
- "platform.users.invite",
34
- "platform.users.manage_access",
35
- "platform.users.reactivate",
36
- "platform.users.view",
37
- ];
38
- /** Dash-owner permission keys (granted at dash scope, mirrored from the membership role). */
39
- export const DASH_PERMISSIONS = [
40
- "dash.apps.manage",
41
- "dash.billing.manage",
42
- "dash.channels.manage",
43
- "dash.customers.manage",
44
- "dash.domains.manage",
45
- "dash.members.manage",
46
- "dash.ownership.transfer",
47
- "dash.plans.manage",
48
- "dash.publish",
49
- "dash.settings.manage",
50
- "dash.workspaces.admin",
51
- "dash.workspaces.manage",
52
- ];
53
- /** Membership-mirrored dash role bundles (grant keys at dash scope). */
54
- export const DASH_ROLES = [
55
- "dash.admin",
56
- "dash.owner",
57
- ];
58
- /** Lead-broker workload permission keys. */
59
- export const LEADBROKER_PERMISSIONS = [
60
- "leadbroker.finance.manage",
61
- "leadbroker.leads.dispatch",
62
- "leadbroker.leads.review",
63
- "leadbroker.leads.view",
64
- "leadbroker.policy.manage",
65
- ];
66
- /** Workspace-staff permission keys. */
67
- export const WORKSPACE_PERMISSIONS = [
68
- "workspace.channels.manage",
69
- "workspace.channels.secrets",
70
- "workspace.channels.view",
71
- "workspace.crm.manage",
72
- "workspace.crm.view",
73
- "workspace.customers.manage",
74
- "workspace.customers.view",
75
- "workspace.orders.manage",
76
- "workspace.orders.view",
77
- "workspace.payments.record",
78
- "workspace.site.edit",
79
- "workspace.site.view",
80
- "workspace.staff.manage",
81
- ];
82
- /** Assignable workspace-staff role bundles (grant keys at workspace scope). */
83
- export const WORKSPACE_ROLES = [
84
- "workspace.editor",
85
- "workspace.frontdesk",
86
- "workspace.manager",
87
- ];
@@ -1,48 +0,0 @@
1
- import type { AccessEntry, AccessSnapshot } from "./snapshot.js";
2
- export interface PermissionCheckOptions {
3
- /**
4
- * SaaS app (dash) slug the check is about. Omit for "anywhere": platform
5
- * entries and any saas_app entry satisfy the check.
6
- */
7
- saasApp?: string;
8
- }
9
- /**
10
- * Mirrors c1m-server authz.entryCoversApp: platform scope covers every app;
11
- * saas_app scope covers "*" or a case-insensitive slug match; workspace-scoped
12
- * entries never satisfy an app-level check.
13
- */
14
- export declare function entryCoversApp(entry: AccessEntry, saasApp: string | undefined): boolean;
15
- /** True when the subject is active and holds `key` for the given app (or anywhere). */
16
- export declare function hasPermission(snapshot: AccessSnapshot, key: string, opts?: PermissionCheckOptions): boolean;
17
- export declare function hasAnyPermission(snapshot: AccessSnapshot, keys: readonly string[], opts?: PermissionCheckOptions): boolean;
18
- export declare function hasAllPermissions(snapshot: AccessSnapshot, keys: readonly string[], opts?: PermissionCheckOptions): boolean;
19
- /**
20
- * Workspace-staff check. Platform owners always pass (they own every
21
- * workspace); otherwise the workspace's effective permission list decides.
22
- * Platform/saas_app entries never confer workspace-staff permissions.
23
- */
24
- export declare function hasWorkspacePermission(snapshot: AccessSnapshot, workspaceId: string, key: string): boolean;
25
- /**
26
- * The dashes this subject runs, from the mirrored dash bundle grants — the
27
- * admission list for the Admin Center's dash area. Platform managers are not
28
- * listed: their reach is platform-wide, so admit them with
29
- * `hasPermission(snapshot, "platform.tenants.manage")` instead.
30
- */
31
- export declare function managedDashes(snapshot: AccessSnapshot): Array<{
32
- saasApp: string;
33
- role: string;
34
- }>;
35
- /** The dash bundle held on one dash, or "" when the subject holds none there. */
36
- export declare function dashRole(snapshot: AccessSnapshot, saasApp: string): string;
37
- export declare function isPlatformOwner(snapshot: AccessSnapshot): boolean;
38
- /**
39
- * The saas_app scopes under which `key` is held. Returns `{ all: true }` when a
40
- * platform-scoped or "*" entry grants it everywhere; otherwise the explicit
41
- * app slugs (empty = nowhere). Mirrors authz.AppPermissionScope.
42
- */
43
- export declare function appScopes(snapshot: AccessSnapshot, key: string): {
44
- all: boolean;
45
- apps: string[];
46
- };
47
- /** Distinct permission keys held for `saasApp` (or anywhere). Handy for nav/badges. */
48
- export declare function permissionsFor(snapshot: AccessSnapshot, opts?: PermissionCheckOptions): string[];
@@ -1,95 +0,0 @@
1
- /**
2
- * Mirrors c1m-server authz.entryCoversApp: platform scope covers every app;
3
- * saas_app scope covers "*" or a case-insensitive slug match; workspace-scoped
4
- * entries never satisfy an app-level check.
5
- */
6
- export function entryCoversApp(entry, saasApp) {
7
- switch (entry.scope_type) {
8
- case "platform":
9
- return true;
10
- case "saas_app":
11
- if (saasApp === undefined)
12
- return true;
13
- return entry.scope_id === "*" || entry.scope_id.toLowerCase() === saasApp.toLowerCase();
14
- default:
15
- return false;
16
- }
17
- }
18
- /** True when the subject is active and holds `key` for the given app (or anywhere). */
19
- export function hasPermission(snapshot, key, opts = {}) {
20
- if (!snapshot.active)
21
- return false;
22
- return snapshot.permissions.some((entry) => entry.key === key && entryCoversApp(entry, opts.saasApp));
23
- }
24
- export function hasAnyPermission(snapshot, keys, opts = {}) {
25
- return keys.some((key) => hasPermission(snapshot, key, opts));
26
- }
27
- export function hasAllPermissions(snapshot, keys, opts = {}) {
28
- return keys.every((key) => hasPermission(snapshot, key, opts));
29
- }
30
- /**
31
- * Workspace-staff check. Platform owners always pass (they own every
32
- * workspace); otherwise the workspace's effective permission list decides.
33
- * Platform/saas_app entries never confer workspace-staff permissions.
34
- */
35
- export function hasWorkspacePermission(snapshot, workspaceId, key) {
36
- if (!snapshot.active)
37
- return false;
38
- if (snapshot.is_owner)
39
- return true;
40
- return (snapshot.workspace_permissions[workspaceId] ?? []).includes(key);
41
- }
42
- /**
43
- * The dashes this subject runs, from the mirrored dash bundle grants — the
44
- * admission list for the Admin Center's dash area. Platform managers are not
45
- * listed: their reach is platform-wide, so admit them with
46
- * `hasPermission(snapshot, "platform.tenants.manage")` instead.
47
- */
48
- export function managedDashes(snapshot) {
49
- if (!snapshot.active)
50
- return [];
51
- return Object.entries(snapshot.dash_roles)
52
- .map(([saasApp, role]) => ({ saasApp, role }))
53
- .sort((a, b) => a.saasApp.localeCompare(b.saasApp));
54
- }
55
- /** The dash bundle held on one dash, or "" when the subject holds none there. */
56
- export function dashRole(snapshot, saasApp) {
57
- if (!snapshot.active)
58
- return "";
59
- return snapshot.dash_roles[saasApp] ?? "";
60
- }
61
- export function isPlatformOwner(snapshot) {
62
- return snapshot.active && snapshot.is_owner;
63
- }
64
- /**
65
- * The saas_app scopes under which `key` is held. Returns `{ all: true }` when a
66
- * platform-scoped or "*" entry grants it everywhere; otherwise the explicit
67
- * app slugs (empty = nowhere). Mirrors authz.AppPermissionScope.
68
- */
69
- export function appScopes(snapshot, key) {
70
- if (!snapshot.active)
71
- return { all: false, apps: [] };
72
- const apps = new Set();
73
- for (const entry of snapshot.permissions) {
74
- if (entry.key !== key)
75
- continue;
76
- if (entry.scope_type === "platform")
77
- return { all: true, apps: [] };
78
- if (entry.scope_type === "saas_app") {
79
- if (entry.scope_id === "*")
80
- return { all: true, apps: [] };
81
- apps.add(entry.scope_id.toLowerCase());
82
- }
83
- }
84
- return { all: false, apps: [...apps].sort() };
85
- }
86
- /** Distinct permission keys held for `saasApp` (or anywhere). Handy for nav/badges. */
87
- export function permissionsFor(snapshot, opts = {}) {
88
- if (!snapshot.active)
89
- return [];
90
- const keys = new Set();
91
- for (const entry of snapshot.permissions)
92
- if (entryCoversApp(entry, opts.saasApp))
93
- keys.add(entry.key);
94
- return [...keys].sort();
95
- }
@@ -1,39 +0,0 @@
1
- /** Scope a permission entry applies to. Mirrors c1m-server authz.Scope*. */
2
- export type AccessScopeType = "platform" | "saas_app" | "workspace";
3
- export interface AccessEntry {
4
- /** Permission key, e.g. "platform.users.manage_access". */
5
- key: string;
6
- scope_type: AccessScopeType;
7
- /** "" for platform scope; saas_app slug or "*" for saas_app scope; workspace id for workspace scope. */
8
- scope_id: string;
9
- }
10
- /**
11
- * The resolved authorization snapshot for one subject, as returned by
12
- * `GET /api/v1/platform/access`. This is the *output* of the backend resolver
13
- * (base-role grant ∪ scoped grants), never the grant rows themselves.
14
- */
15
- export interface AccessSnapshot {
16
- subject_id: string;
17
- /** Monotonic; bumps on any grant/role change. Use for optimistic concurrency and cache keys. */
18
- access_version: number;
19
- is_owner: boolean;
20
- active: boolean;
21
- /** Platform operator base role (derived; display only — never authorize on it). */
22
- platform_role: string;
23
- permissions: AccessEntry[];
24
- /**
25
- * Dash bundle (`dash.owner` / `dash.admin`) per saas_app id, mirrored from
26
- * the subject's dash membership. This is the admission list for the dash
27
- * area: the dashes this subject runs. What they may *do* on one is a normal
28
- * `hasPermission(snapshot, key, { saasApp })` check — platform managers hold
29
- * their authority platform-wide and so appear here not at all.
30
- */
31
- dash_roles: Record<string, string>;
32
- /** Effective workspace-staff permission keys per workspace id. */
33
- workspace_permissions: Record<string, string[]>;
34
- /** Workspace-staff role bundle per workspace id (display only). */
35
- workspace_roles: Record<string, string>;
36
- }
37
- export declare function emptySnapshot(subjectId?: string): AccessSnapshot;
38
- /** Runtime validator for the wire payload. Throws AccessSnapshotError("invalid_snapshot"). */
39
- export declare function parseAccessSnapshot(input: unknown): AccessSnapshot;
@@ -1,92 +0,0 @@
1
- import { AccessSnapshotError } from "./errors.js";
2
- const SCOPE_TYPES = new Set(["platform", "saas_app", "workspace"]);
3
- export function emptySnapshot(subjectId = "") {
4
- return {
5
- subject_id: subjectId,
6
- access_version: 0,
7
- is_owner: false,
8
- active: false,
9
- platform_role: "",
10
- permissions: [],
11
- dash_roles: {},
12
- workspace_permissions: {},
13
- workspace_roles: {},
14
- };
15
- }
16
- function isRecord(value) {
17
- return typeof value === "object" && value !== null && !Array.isArray(value);
18
- }
19
- function fail(field, why) {
20
- throw new AccessSnapshotError("invalid_snapshot", `access snapshot: ${field} ${why}`);
21
- }
22
- /** Runtime validator for the wire payload. Throws AccessSnapshotError("invalid_snapshot"). */
23
- export function parseAccessSnapshot(input) {
24
- if (!isRecord(input))
25
- fail("payload", "is not an object");
26
- const { subject_id, access_version, is_owner, active, platform_role, permissions, dash_roles, workspace_permissions, workspace_roles } = input;
27
- if (typeof subject_id !== "string" || subject_id === "")
28
- fail("subject_id", "must be a non-empty string");
29
- if (typeof access_version !== "number" || !Number.isInteger(access_version) || access_version < 0)
30
- fail("access_version", "must be a non-negative integer");
31
- if (typeof is_owner !== "boolean")
32
- fail("is_owner", "must be a boolean");
33
- if (typeof active !== "boolean")
34
- fail("active", "must be a boolean");
35
- if (platform_role !== undefined && platform_role !== null && typeof platform_role !== "string")
36
- fail("platform_role", "must be a string");
37
- if (!Array.isArray(permissions))
38
- fail("permissions", "must be an array");
39
- const entries = permissions.map((raw, i) => {
40
- if (!isRecord(raw))
41
- fail(`permissions[${i}]`, "is not an object");
42
- const { key, scope_type, scope_id } = raw;
43
- if (typeof key !== "string" || key === "")
44
- fail(`permissions[${i}].key`, "must be a non-empty string");
45
- if (typeof scope_type !== "string" || !SCOPE_TYPES.has(scope_type))
46
- fail(`permissions[${i}].scope_type`, "must be platform|saas_app|workspace");
47
- if (scope_id !== undefined && scope_id !== null && typeof scope_id !== "string")
48
- fail(`permissions[${i}].scope_id`, "must be a string");
49
- return { key, scope_type: scope_type, scope_id: typeof scope_id === "string" ? scope_id : "" };
50
- });
51
- const dashRoles = {};
52
- if (dash_roles !== undefined && dash_roles !== null) {
53
- if (!isRecord(dash_roles))
54
- fail("dash_roles", "must be an object");
55
- for (const [dash, role] of Object.entries(dash_roles)) {
56
- if (typeof role !== "string")
57
- fail(`dash_roles[${dash}]`, "must be a string");
58
- dashRoles[dash] = role;
59
- }
60
- }
61
- const wsPerms = {};
62
- if (workspace_permissions !== undefined && workspace_permissions !== null) {
63
- if (!isRecord(workspace_permissions))
64
- fail("workspace_permissions", "must be an object");
65
- for (const [ws, keys] of Object.entries(workspace_permissions)) {
66
- if (!Array.isArray(keys) || keys.some((k) => typeof k !== "string"))
67
- fail(`workspace_permissions[${ws}]`, "must be a string array");
68
- wsPerms[ws] = [...keys];
69
- }
70
- }
71
- const wsRoles = {};
72
- if (workspace_roles !== undefined && workspace_roles !== null) {
73
- if (!isRecord(workspace_roles))
74
- fail("workspace_roles", "must be an object");
75
- for (const [ws, role] of Object.entries(workspace_roles)) {
76
- if (typeof role !== "string")
77
- fail(`workspace_roles[${ws}]`, "must be a string");
78
- wsRoles[ws] = role;
79
- }
80
- }
81
- return {
82
- subject_id,
83
- access_version,
84
- is_owner,
85
- active,
86
- platform_role: typeof platform_role === "string" ? platform_role : "",
87
- permissions: entries,
88
- dash_roles: dashRoles,
89
- workspace_permissions: wsPerms,
90
- workspace_roles: wsRoles,
91
- };
92
- }
package/dist/cookies.d.ts DELETED
@@ -1,11 +0,0 @@
1
- export interface CookieOptions {
2
- httpOnly?: boolean;
3
- secure?: boolean;
4
- sameSite?: "lax" | "strict" | "none";
5
- path?: string;
6
- maxAge?: number;
7
- }
8
- export declare function readCookie(request: Request, name: string): string | null;
9
- export declare function serializeCookie(name: string, value: string, options?: CookieOptions): string;
10
- export declare function setCookie(response: Response, name: string, value: string, options?: CookieOptions): Response;
11
- export declare function deleteCookie(response: Response, name: string, options?: Pick<CookieOptions, "path" | "secure">): Response;
package/dist/cookies.js DELETED
@@ -1,42 +0,0 @@
1
- // Dependency-free cookie helpers over Web Request/Response (what Next route handlers use).
2
- export function readCookie(request, name) {
3
- const header = request.headers.get("cookie");
4
- if (!header)
5
- return null;
6
- for (const part of header.split(";")) {
7
- const separator = part.indexOf("=");
8
- if (separator < 0)
9
- continue;
10
- if (part.slice(0, separator).trim() !== name)
11
- continue;
12
- const raw = part.slice(separator + 1).trim();
13
- try {
14
- return decodeURIComponent(raw);
15
- }
16
- catch {
17
- return raw;
18
- }
19
- }
20
- return null;
21
- }
22
- export function serializeCookie(name, value, options = {}) {
23
- const parts = [`${name}=${encodeURIComponent(value)}`];
24
- parts.push(`Path=${options.path ?? "/"}`);
25
- if (options.maxAge !== undefined)
26
- parts.push(`Max-Age=${Math.max(0, Math.floor(options.maxAge))}`);
27
- if (options.httpOnly ?? true)
28
- parts.push("HttpOnly");
29
- if (options.secure ?? name.startsWith("__Host-"))
30
- parts.push("Secure");
31
- const sameSite = options.sameSite ?? "lax";
32
- parts.push(`SameSite=${sameSite.charAt(0).toUpperCase()}${sameSite.slice(1)}`);
33
- return parts.join("; ");
34
- }
35
- export function setCookie(response, name, value, options) {
36
- response.headers.append("Set-Cookie", serializeCookie(name, value, options));
37
- return response;
38
- }
39
- export function deleteCookie(response, name, options) {
40
- response.headers.append("Set-Cookie", serializeCookie(name, "", { ...options, maxAge: 0 }));
41
- return response;
42
- }
@@ -1,18 +0,0 @@
1
- /**
2
- * Per-browser identity sent as `X-Client-Device-ID`; c1m-server refuses to
3
- * create a session without it. Informational only, no authority.
4
- */
5
- export interface DeviceIdentity {
6
- value: string;
7
- created: boolean;
8
- }
9
- export interface DeviceIdentityConfig {
10
- cookieName: string;
11
- secure?: boolean;
12
- maxAgeSeconds?: number;
13
- }
14
- export declare function createDeviceIdentity(config: DeviceIdentityConfig): {
15
- read(request: Request): DeviceIdentity;
16
- persist(response: Response, identity: DeviceIdentity): Response;
17
- };
18
- export type DeviceIdentityStore = ReturnType<typeof createDeviceIdentity>;
@@ -1,22 +0,0 @@
1
- import { readCookie, setCookie } from "./cookies.js";
2
- const DEFAULT_DEVICE_MAX_AGE = 365 * 24 * 60 * 60;
3
- export function createDeviceIdentity(config) {
4
- const maxAge = config.maxAgeSeconds ?? DEFAULT_DEVICE_MAX_AGE;
5
- return {
6
- read(request) {
7
- const existing = readCookie(request, config.cookieName)?.trim();
8
- return existing ? { value: existing, created: false } : { value: crypto.randomUUID(), created: true };
9
- },
10
- persist(response, identity) {
11
- if (!identity.created)
12
- return response;
13
- return setCookie(response, config.cookieName, identity.value, {
14
- httpOnly: true,
15
- secure: config.secure ?? true,
16
- sameSite: "lax",
17
- path: "/",
18
- maxAge,
19
- });
20
- },
21
- };
22
- }
@@ -1,2 +0,0 @@
1
- export declare function applyForwardedClientHeaders(incoming: Headers, headers?: Headers): Headers;
2
- export declare function forwardedRequestHeaders(request: Request, init?: HeadersInit): Headers;
@@ -1,32 +0,0 @@
1
- // Copies client identity (IP, geo, UA, host, proto) from an incoming BFF request onto a backend
2
- // request as X-Client-* / X-Forwarded-*. Used for device records, rate limits, audit — never auth.
3
- export function applyForwardedClientHeaders(incoming, headers = new Headers()) {
4
- const forwardedFor = incoming.get("x-forwarded-for");
5
- const realIp = incoming.get("x-real-ip") ?? forwardedFor?.split(",")[0]?.trim();
6
- if (realIp && !headers.has("X-Client-IP"))
7
- headers.set("X-Client-IP", realIp);
8
- if (realIp && !headers.has("X-Real-IP"))
9
- headers.set("X-Real-IP", realIp);
10
- if (forwardedFor && !headers.has("X-Forwarded-For"))
11
- headers.set("X-Forwarded-For", forwardedFor);
12
- const country = incoming.get("cf-ipcountry") ??
13
- incoming.get("x-vercel-ip-country") ??
14
- incoming.get("x-geo-country") ??
15
- incoming.get("x-country-code");
16
- if (country && country !== "XX" && !headers.has("X-Geo-Country")) {
17
- headers.set("X-Geo-Country", country);
18
- }
19
- const host = incoming.get("host");
20
- if (host && !headers.has("X-Forwarded-Host"))
21
- headers.set("X-Forwarded-Host", host);
22
- const proto = incoming.get("x-forwarded-proto");
23
- if (proto && !headers.has("X-Forwarded-Proto"))
24
- headers.set("X-Forwarded-Proto", proto);
25
- const userAgent = incoming.get("user-agent");
26
- if (userAgent && !headers.has("X-Client-User-Agent"))
27
- headers.set("X-Client-User-Agent", userAgent);
28
- return headers;
29
- }
30
- export function forwardedRequestHeaders(request, init) {
31
- return applyForwardedClientHeaders(request.headers, new Headers(init));
32
- }
@@ -1,13 +0,0 @@
1
- export declare const HANDOFF_ERROR_MESSAGES: Record<string, string>;
2
- export declare const HANDOFF_ERROR_FALLBACK = "Sign-in failed \u2014 please try again.";
3
- export declare const SSO_ERROR_MESSAGES: Record<string, string>;
4
- /** Every sign-in failure code this package knows: handoff, admission, and SSO. */
5
- export declare const AUTH_ERROR_MESSAGES: Record<string, string>;
6
- export declare function handoffErrorMessage(code: string | null | undefined): string;
7
- export declare function isHandoffErrorCode(code: string | null | undefined): boolean;
8
- /**
9
- * Resolve any sign-in failure code to copy. `overrides` wins, so a surface can
10
- * reword a shared code or add its own without forking the whole map.
11
- */
12
- export declare function authErrorMessage(code: string | null | undefined, overrides?: Record<string, string>): string;
13
- export declare function isAuthErrorCode(code: string | null | undefined, overrides?: Record<string, string>): boolean;
@@ -1,65 +0,0 @@
1
- // One error vocabulary for the handoff: backend codes (c1m-server mapHandoffError) + BFF codes.
2
- export const HANDOFF_ERROR_MESSAGES = {
3
- // backend — handoff broker
4
- handoff_unknown_app: "That application is not registered for sign-in handoff.",
5
- handoff_not_configured: "Sign-in handoff is not configured for this application yet.",
6
- handoff_source_not_allowed: "Your current session cannot open that application. Sign in there directly.",
7
- handoff_invalid: "That sign-in handoff expired or was already used. Start again from where you came from.",
8
- handoff_device_required: "The handoff could not identify this browser. Please try again.",
9
- handoff_request_invalid: "The sign-in handoff request was malformed. Please try again.",
10
- handoff_failed: "Sign-in handoff failed. Sign in with an email code instead.",
11
- // backend — target admission (same codes the target's own login returns)
12
- knowledge_library_access_required: "Your account has not been granted Knowledge Library access. Ask a platform admin to enable it.",
13
- admin_center_access_required: "Your account has not been granted Admin Center access.",
14
- platform_admin_surface_required: "Platform operator accounts sign in through the Platform Admin Center.",
15
- account_disabled: "This account is disabled.",
16
- // BFF — target callback
17
- handoff_state_invalid: "Sign-in attempt expired — please try again.",
18
- handoff_exchange_failed: "Sign-in could not be completed. Please try again.",
19
- // BFF — source start
20
- handoff_start_failed: "Could not start the sign-in handoff. Please try again.",
21
- handoff_redirect_invalid: "The sign-in handoff returned an unexpected destination and was stopped.",
22
- // BFF — surface bounces
23
- admin_surface: "Platform operators sign in here; SaaSDash sent you to the Admin Center.",
24
- };
25
- export const HANDOFF_ERROR_FALLBACK = "Sign-in failed — please try again.";
26
- // External sign-in (OIDC/social) codes every platform surface renders identically.
27
- // Surface-specific copy stays in the app and is passed as `overrides`.
28
- export const SSO_ERROR_MESSAGES = {
29
- sso_cancelled: "Sign-in was cancelled.",
30
- sso_denied: "Sign-in was refused by the provider.",
31
- sso_state_invalid: "Sign-in attempt expired — please try again.",
32
- sso_start_failed: "Could not start sign-in with that provider. Please try again.",
33
- sso_complete_failed: "Sign-in could not be completed. Please try again.",
34
- sso_unknown_operator: "No operator account matches that identity. Ask an admin for an invite.",
35
- sso_provider_not_found: "That sign-in provider is not available.",
36
- sso_not_configured: "That sign-in provider is not fully configured.",
37
- };
38
- /** Every sign-in failure code this package knows: handoff, admission, and SSO. */
39
- export const AUTH_ERROR_MESSAGES = {
40
- ...HANDOFF_ERROR_MESSAGES,
41
- ...SSO_ERROR_MESSAGES,
42
- };
43
- export function handoffErrorMessage(code) {
44
- if (!code)
45
- return HANDOFF_ERROR_FALLBACK;
46
- return HANDOFF_ERROR_MESSAGES[code] ?? HANDOFF_ERROR_FALLBACK;
47
- }
48
- export function isHandoffErrorCode(code) {
49
- return !!code && Object.prototype.hasOwnProperty.call(HANDOFF_ERROR_MESSAGES, code);
50
- }
51
- /**
52
- * Resolve any sign-in failure code to copy. `overrides` wins, so a surface can
53
- * reword a shared code or add its own without forking the whole map.
54
- */
55
- export function authErrorMessage(code, overrides) {
56
- if (!code)
57
- return HANDOFF_ERROR_FALLBACK;
58
- return overrides?.[code] ?? AUTH_ERROR_MESSAGES[code] ?? HANDOFF_ERROR_FALLBACK;
59
- }
60
- export function isAuthErrorCode(code, overrides) {
61
- if (!code)
62
- return false;
63
- return (Object.prototype.hasOwnProperty.call(AUTH_ERROR_MESSAGES, code) ||
64
- (!!overrides && Object.prototype.hasOwnProperty.call(overrides, code)));
65
- }
package/dist/handoff.d.ts DELETED
@@ -1,55 +0,0 @@
1
- import type { DeviceIdentityStore } from "./device-cookie.js";
2
- import { type Resolvable } from "./internal.js";
3
- import { type AuthResponse } from "./token.js";
4
- export declare function sourceStartPath(app: string): string;
5
- export declare function brokerPaths(app: string): {
6
- readonly start: `/api/v1/platform/auth/handoffs/${string}/start`;
7
- readonly exchange: `/api/v1/platform/auth/handoffs/${string}/exchange`;
8
- };
9
- export interface HandoffTargetConfig {
10
- app: string;
11
- apiUrl: Resolvable;
12
- audience: string;
13
- clientType: string;
14
- sources: Record<string, Resolvable>;
15
- defaultSource: string;
16
- cookiePrefix: string;
17
- loginPath: string;
18
- defaultReturnPath: string;
19
- device: DeviceIdentityStore;
20
- /** Write session cookies onto `response` (or return void if written via a framework store). */
21
- establishSession: (session: AuthResponse, response: Response, request: Request) => Promise<Response | void> | Response | void;
22
- secureCookies?: boolean;
23
- fetchTimeoutMs?: number;
24
- }
25
- export declare function createHandoffTarget(config: HandoffTargetConfig): {
26
- cookies: {
27
- readonly state: `${string}state`;
28
- readonly verifier: `${string}verifier`;
29
- readonly from: `${string}from`;
30
- };
31
- /** GET `?source=&from=` */
32
- start(request: Request): Promise<Response>;
33
- /** GET `?code=&state=` */
34
- callback(request: Request): Promise<Response>;
35
- };
36
- export type HandoffTarget = ReturnType<typeof createHandoffTarget>;
37
- export interface HandoffSourceTarget {
38
- origin: Resolvable;
39
- callbackPath?: string;
40
- loginPath?: string;
41
- }
42
- export interface HandoffSourceConfig {
43
- apiUrl: Resolvable;
44
- clientType: string;
45
- targets: Record<string, HandoffSourceTarget>;
46
- /** Live bearer or null when signed out; refresh an expiring token before returning it. */
47
- getAccessToken: (request: Request) => Promise<string | null> | string | null;
48
- loginPath: string;
49
- fetchTimeoutMs?: number;
50
- }
51
- export declare function createHandoffSource(config: HandoffSourceConfig): {
52
- /** GET `/api/auth/handoffs/{app}/start?code_challenge=&state=` */
53
- start(request: Request, app: string): Promise<Response>;
54
- };
55
- export type HandoffSource = ReturnType<typeof createHandoffSource>;