@c1m-public/platform-auth 0.5.0 → 0.7.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-RR5DP3QT.js +261 -0
  10. package/dist/chunk-RVKIDFXM.js +44 -0
  11. package/dist/chunk-SMBKTB5V.js +389 -0
  12. package/dist/index.d.ts +220 -9
  13. package/dist/index.js +3 -9
  14. package/dist/internal-rnEuAE21.d.ts +4 -0
  15. package/dist/next.d.ts +168 -2
  16. package/dist/next.js +306 -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 -78
  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
@@ -0,0 +1,83 @@
1
+ // src/token.ts
2
+ function readStringClaim(value, fallback = "") {
3
+ return typeof value === "string" ? value : fallback;
4
+ }
5
+ function readBooleanClaim(value, fallback) {
6
+ return typeof value === "boolean" ? value : fallback;
7
+ }
8
+ function decodeBase64Url(input) {
9
+ const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
10
+ const padded = normalized.padEnd(normalized.length + (4 - normalized.length % 4) % 4, "=");
11
+ try {
12
+ const binary = globalThis.atob(padded);
13
+ const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
14
+ return new TextDecoder().decode(bytes);
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
19
+ function decodeTokenPayload(token) {
20
+ const payloadSegment = token.split(".")[1];
21
+ if (!payloadSegment) return null;
22
+ const decodedJson = decodeBase64Url(payloadSegment);
23
+ if (!decodedJson) return null;
24
+ try {
25
+ const parsed = JSON.parse(decodedJson);
26
+ return parsed && typeof parsed === "object" ? parsed : null;
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
31
+ function hasTokenAudience(token, requiredAudience) {
32
+ const audience = decodeTokenPayload(token)?.aud;
33
+ return typeof audience === "string" ? audience === requiredAudience : Array.isArray(audience) && audience.includes(requiredAudience);
34
+ }
35
+ function nowInSeconds() {
36
+ return Math.floor(Date.now() / 1e3);
37
+ }
38
+ function tokenMaxAgeSeconds(token, configuredMaxAge) {
39
+ const decoded = decodeTokenPayload(token);
40
+ if (!decoded?.exp) return configuredMaxAge;
41
+ return Math.max(0, Math.min(configuredMaxAge, decoded.exp - nowInSeconds()));
42
+ }
43
+ function isTokenExpired(token) {
44
+ const decoded = decodeTokenPayload(token);
45
+ if (!decoded?.exp) return true;
46
+ return decoded.exp <= nowInSeconds();
47
+ }
48
+ function isTokenExpiringSoon(token, bufferSeconds = 120) {
49
+ const decoded = decodeTokenPayload(token);
50
+ if (!decoded?.exp) return true;
51
+ return decoded.exp < nowInSeconds() + bufferSeconds;
52
+ }
53
+ function decodeDefaultSession(token) {
54
+ const decoded = decodeTokenPayload(token);
55
+ if (!decoded) return null;
56
+ const actorId = decoded.effective_user_id || decoded.actor_id || decoded.customer_id || decoded.user_id || (typeof decoded.sub === "string" ? decoded.sub : void 0);
57
+ if (!actorId) return null;
58
+ return {
59
+ user: {
60
+ id: String(actorId),
61
+ email: readStringClaim(decoded.email),
62
+ name: readStringClaim(decoded.name),
63
+ role: readStringClaim(decoded.role, "user"),
64
+ is_active: readBooleanClaim(decoded.is_active, true),
65
+ email_verified: readBooleanClaim(decoded.email_verified, false),
66
+ created_at: readStringClaim(decoded.created_at),
67
+ has_passkey: readBooleanClaim(decoded.has_passkeys, false)
68
+ },
69
+ token,
70
+ exp: decoded.exp || 0,
71
+ iat: decoded.iat || 0
72
+ };
73
+ }
74
+ function parseAuthResponse(value) {
75
+ if (!value || typeof value !== "object") return null;
76
+ const candidate = value;
77
+ if (typeof candidate.access_token !== "string" || typeof candidate.refresh_token !== "string" || typeof candidate.token_type !== "string" || typeof candidate.expires_at !== "string" || !candidate.user || typeof candidate.user !== "object") {
78
+ return null;
79
+ }
80
+ return candidate;
81
+ }
82
+
83
+ export { decodeBase64Url, decodeDefaultSession, decodeTokenPayload, hasTokenAudience, isTokenExpired, isTokenExpiringSoon, parseAuthResponse, tokenMaxAgeSeconds };
@@ -0,0 +1,261 @@
1
+ import { resolveValue, forwardedRequestHeaders, timedFetch } from './chunk-RVKIDFXM.js';
2
+
3
+ // src/access/generated/keys.ts
4
+ var 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
+ var DASH_PERMISSIONS = [
39
+ "dash.apps.manage",
40
+ "dash.billing.manage",
41
+ "dash.channels.manage",
42
+ "dash.customers.manage",
43
+ "dash.domains.manage",
44
+ "dash.members.manage",
45
+ "dash.ownership.transfer",
46
+ "dash.plans.manage",
47
+ "dash.publish",
48
+ "dash.settings.manage",
49
+ "dash.workspaces.admin",
50
+ "dash.workspaces.manage"
51
+ ];
52
+ var DASH_ROLES = [
53
+ "dash.admin",
54
+ "dash.owner"
55
+ ];
56
+ var LEADBROKER_PERMISSIONS = [
57
+ "leadbroker.finance.manage",
58
+ "leadbroker.leads.dispatch",
59
+ "leadbroker.leads.review",
60
+ "leadbroker.leads.view",
61
+ "leadbroker.policy.manage"
62
+ ];
63
+ var WORKSPACE_PERMISSIONS = [
64
+ "workspace.channels.manage",
65
+ "workspace.channels.secrets",
66
+ "workspace.channels.view",
67
+ "workspace.crm.manage",
68
+ "workspace.crm.view",
69
+ "workspace.customers.manage",
70
+ "workspace.customers.view",
71
+ "workspace.orders.manage",
72
+ "workspace.orders.view",
73
+ "workspace.payments.record",
74
+ "workspace.site.edit",
75
+ "workspace.site.view",
76
+ "workspace.staff.manage"
77
+ ];
78
+ var WORKSPACE_ROLES = [
79
+ "workspace.editor",
80
+ "workspace.frontdesk",
81
+ "workspace.manager"
82
+ ];
83
+
84
+ // src/access/errors.ts
85
+ var AccessSnapshotError = class extends Error {
86
+ code;
87
+ status;
88
+ constructor(code, message, status) {
89
+ super(message);
90
+ this.name = "AccessSnapshotError";
91
+ this.code = code;
92
+ this.status = status;
93
+ }
94
+ };
95
+ function isAccessSnapshotError(error, code) {
96
+ return error instanceof AccessSnapshotError && (code === void 0 || error.code === code);
97
+ }
98
+
99
+ // src/access/snapshot.ts
100
+ var SCOPE_TYPES = /* @__PURE__ */ new Set(["platform", "saas_app", "workspace"]);
101
+ function emptySnapshot(subjectId = "") {
102
+ return {
103
+ subject_id: subjectId,
104
+ access_version: 0,
105
+ is_owner: false,
106
+ active: false,
107
+ platform_role: "",
108
+ permissions: [],
109
+ dash_roles: {},
110
+ workspace_permissions: {},
111
+ workspace_roles: {}
112
+ };
113
+ }
114
+ function isRecord(value) {
115
+ return typeof value === "object" && value !== null && !Array.isArray(value);
116
+ }
117
+ function fail(field, why) {
118
+ throw new AccessSnapshotError("invalid_snapshot", `access snapshot: ${field} ${why}`);
119
+ }
120
+ function parseAccessSnapshot(input) {
121
+ if (!isRecord(input)) fail("payload", "is not an object");
122
+ const { subject_id, access_version, is_owner, active, platform_role, permissions, dash_roles, workspace_permissions, workspace_roles } = input;
123
+ if (typeof subject_id !== "string" || subject_id === "") fail("subject_id", "must be a non-empty string");
124
+ if (typeof access_version !== "number" || !Number.isInteger(access_version) || access_version < 0) fail("access_version", "must be a non-negative integer");
125
+ if (typeof is_owner !== "boolean") fail("is_owner", "must be a boolean");
126
+ if (typeof active !== "boolean") fail("active", "must be a boolean");
127
+ if (platform_role !== void 0 && platform_role !== null && typeof platform_role !== "string") fail("platform_role", "must be a string");
128
+ if (!Array.isArray(permissions)) fail("permissions", "must be an array");
129
+ const entries = permissions.map((raw, i) => {
130
+ if (!isRecord(raw)) fail(`permissions[${i}]`, "is not an object");
131
+ const { key, scope_type, scope_id } = raw;
132
+ if (typeof key !== "string" || key === "") fail(`permissions[${i}].key`, "must be a non-empty string");
133
+ if (typeof scope_type !== "string" || !SCOPE_TYPES.has(scope_type)) fail(`permissions[${i}].scope_type`, "must be platform|saas_app|workspace");
134
+ if (scope_id !== void 0 && scope_id !== null && typeof scope_id !== "string") fail(`permissions[${i}].scope_id`, "must be a string");
135
+ return { key, scope_type, scope_id: typeof scope_id === "string" ? scope_id : "" };
136
+ });
137
+ const dashRoles = {};
138
+ if (dash_roles !== void 0 && dash_roles !== null) {
139
+ if (!isRecord(dash_roles)) fail("dash_roles", "must be an object");
140
+ for (const [dash, role] of Object.entries(dash_roles)) {
141
+ if (typeof role !== "string") fail(`dash_roles[${dash}]`, "must be a string");
142
+ dashRoles[dash] = role;
143
+ }
144
+ }
145
+ const wsPerms = {};
146
+ if (workspace_permissions !== void 0 && workspace_permissions !== null) {
147
+ if (!isRecord(workspace_permissions)) fail("workspace_permissions", "must be an object");
148
+ for (const [ws, keys] of Object.entries(workspace_permissions)) {
149
+ if (!Array.isArray(keys) || keys.some((k) => typeof k !== "string")) fail(`workspace_permissions[${ws}]`, "must be a string array");
150
+ wsPerms[ws] = [...keys];
151
+ }
152
+ }
153
+ const wsRoles = {};
154
+ if (workspace_roles !== void 0 && workspace_roles !== null) {
155
+ if (!isRecord(workspace_roles)) fail("workspace_roles", "must be an object");
156
+ for (const [ws, role] of Object.entries(workspace_roles)) {
157
+ if (typeof role !== "string") fail(`workspace_roles[${ws}]`, "must be a string");
158
+ wsRoles[ws] = role;
159
+ }
160
+ }
161
+ return {
162
+ subject_id,
163
+ access_version,
164
+ is_owner,
165
+ active,
166
+ platform_role: typeof platform_role === "string" ? platform_role : "",
167
+ permissions: entries,
168
+ dash_roles: dashRoles,
169
+ workspace_permissions: wsPerms,
170
+ workspace_roles: wsRoles
171
+ };
172
+ }
173
+
174
+ // src/access/predicates.ts
175
+ function entryCoversApp(entry, saasApp) {
176
+ switch (entry.scope_type) {
177
+ case "platform":
178
+ return true;
179
+ case "saas_app":
180
+ if (saasApp === void 0) return true;
181
+ return entry.scope_id === "*" || entry.scope_id.toLowerCase() === saasApp.toLowerCase();
182
+ default:
183
+ return false;
184
+ }
185
+ }
186
+ function hasPermission(snapshot, key, opts = {}) {
187
+ if (!snapshot.active) return false;
188
+ return snapshot.permissions.some((entry) => entry.key === key && entryCoversApp(entry, opts.saasApp));
189
+ }
190
+ function hasAnyPermission(snapshot, keys, opts = {}) {
191
+ return keys.some((key) => hasPermission(snapshot, key, opts));
192
+ }
193
+ function hasAllPermissions(snapshot, keys, opts = {}) {
194
+ return keys.every((key) => hasPermission(snapshot, key, opts));
195
+ }
196
+ function hasWorkspacePermission(snapshot, workspaceId, key) {
197
+ if (!snapshot.active) return false;
198
+ if (snapshot.is_owner) return true;
199
+ return (snapshot.workspace_permissions[workspaceId] ?? []).includes(key);
200
+ }
201
+ function managedDashes(snapshot) {
202
+ if (!snapshot.active) return [];
203
+ return Object.entries(snapshot.dash_roles).map(([saasApp, role]) => ({ saasApp, role })).sort((a, b) => a.saasApp.localeCompare(b.saasApp));
204
+ }
205
+ function dashRole(snapshot, saasApp) {
206
+ if (!snapshot.active) return "";
207
+ return snapshot.dash_roles[saasApp] ?? "";
208
+ }
209
+ function isPlatformOwner(snapshot) {
210
+ return snapshot.active && snapshot.is_owner;
211
+ }
212
+ function appScopes(snapshot, key) {
213
+ if (!snapshot.active) return { all: false, apps: [] };
214
+ const apps = /* @__PURE__ */ new Set();
215
+ for (const entry of snapshot.permissions) {
216
+ if (entry.key !== key) continue;
217
+ if (entry.scope_type === "platform") return { all: true, apps: [] };
218
+ if (entry.scope_type === "saas_app") {
219
+ if (entry.scope_id === "*") return { all: true, apps: [] };
220
+ apps.add(entry.scope_id.toLowerCase());
221
+ }
222
+ }
223
+ return { all: false, apps: [...apps].sort() };
224
+ }
225
+ function permissionsFor(snapshot, opts = {}) {
226
+ if (!snapshot.active) return [];
227
+ const keys = /* @__PURE__ */ new Set();
228
+ for (const entry of snapshot.permissions) if (entryCoversApp(entry, opts.saasApp)) keys.add(entry.key);
229
+ return [...keys].sort();
230
+ }
231
+
232
+ // src/access/fetch.ts
233
+ var DEFAULT_ACCESS_ENDPOINT_PATH = "/api/v1/platform/access";
234
+ async function fetchAccessSnapshot(opts) {
235
+ const url = `${resolveValue(opts.apiUrl).replace(/\/$/, "")}${opts.endpointPath ?? DEFAULT_ACCESS_ENDPOINT_PATH}`;
236
+ const headers = opts.request ? forwardedRequestHeaders(opts.request, opts.headers) : new Headers(opts.headers);
237
+ headers.set("Authorization", `Bearer ${opts.accessToken}`);
238
+ headers.set("Accept", "application/json");
239
+ let response;
240
+ try {
241
+ response = await timedFetch(url, {
242
+ init: { method: "GET", headers },
243
+ timeoutMs: opts.fetchTimeoutMs,
244
+ fetchImpl: opts.fetchImpl
245
+ });
246
+ } catch (error) {
247
+ throw new AccessSnapshotError("unavailable", `access snapshot request failed: ${error.message}`);
248
+ }
249
+ if (response.status === 401) throw new AccessSnapshotError("unauthenticated", "session expired", 401);
250
+ if (response.status === 403) throw new AccessSnapshotError("forbidden", "access snapshot refused", 403);
251
+ if (!response.ok) throw new AccessSnapshotError("unavailable", `access snapshot returned ${response.status}`, response.status);
252
+ let body;
253
+ try {
254
+ body = await response.json();
255
+ } catch {
256
+ throw new AccessSnapshotError("invalid_snapshot", "access snapshot body is not JSON", response.status);
257
+ }
258
+ return parseAccessSnapshot(body);
259
+ }
260
+
261
+ export { AccessSnapshotError, DASH_PERMISSIONS, DASH_ROLES, DEFAULT_ACCESS_ENDPOINT_PATH, LEADBROKER_PERMISSIONS, PLATFORM_PERMISSIONS, WORKSPACE_PERMISSIONS, WORKSPACE_ROLES, appScopes, dashRole, emptySnapshot, entryCoversApp, fetchAccessSnapshot, hasAllPermissions, hasAnyPermission, hasPermission, hasWorkspacePermission, isAccessSnapshotError, isPlatformOwner, managedDashes, parseAccessSnapshot, permissionsFor };
@@ -0,0 +1,44 @@
1
+ // ../transport/src/forwarding.ts
2
+ function applyForwardedClientHeaders(incoming, headers = new Headers()) {
3
+ const forwardedFor = incoming.get("x-forwarded-for");
4
+ const realIp = incoming.get("x-real-ip") ?? forwardedFor?.split(",")[0]?.trim();
5
+ if (realIp && !headers.has("X-Client-IP")) headers.set("X-Client-IP", realIp);
6
+ if (realIp && !headers.has("X-Real-IP")) headers.set("X-Real-IP", realIp);
7
+ if (forwardedFor && !headers.has("X-Forwarded-For")) headers.set("X-Forwarded-For", forwardedFor);
8
+ const country = incoming.get("cf-ipcountry") ?? incoming.get("x-vercel-ip-country") ?? incoming.get("x-geo-country") ?? incoming.get("x-country-code");
9
+ if (country && country !== "XX" && !headers.has("X-Geo-Country")) {
10
+ headers.set("X-Geo-Country", country);
11
+ }
12
+ const host = incoming.get("host");
13
+ if (host && !headers.has("X-Forwarded-Host")) headers.set("X-Forwarded-Host", host);
14
+ const proto = incoming.get("x-forwarded-proto");
15
+ if (proto && !headers.has("X-Forwarded-Proto")) headers.set("X-Forwarded-Proto", proto);
16
+ const userAgent = incoming.get("user-agent");
17
+ if (userAgent && !headers.has("X-Client-User-Agent")) headers.set("X-Client-User-Agent", userAgent);
18
+ return headers;
19
+ }
20
+ function forwardedRequestHeaders(request, init) {
21
+ return applyForwardedClientHeaders(request.headers, new Headers(init));
22
+ }
23
+
24
+ // src/internal.ts
25
+ function resolveValue(value) {
26
+ return typeof value === "function" ? value() : value;
27
+ }
28
+ var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
29
+ async function timedFetch(url, options = {}) {
30
+ const doFetch = options.fetch ?? options.fetchImpl ?? fetch;
31
+ const controller = options.timeoutMs ? new AbortController() : void 0;
32
+ const timer = controller ? setTimeout(() => controller.abort(), options.timeoutMs) : void 0;
33
+ try {
34
+ return await doFetch(url, {
35
+ ...options.init,
36
+ cache: "no-store",
37
+ ...controller ? { signal: controller.signal } : {}
38
+ });
39
+ } finally {
40
+ if (timer) clearTimeout(timer);
41
+ }
42
+ }
43
+
44
+ export { DEFAULT_FETCH_TIMEOUT_MS, applyForwardedClientHeaders, forwardedRequestHeaders, resolveValue, timedFetch };