@ai-matrx/auth 0.0.0 → 0.1.1

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 (44) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/LICENSE +21 -0
  3. package/README.md +31 -1
  4. package/dist/auth-cookie-DWx6Q3xo.d.cts +168 -0
  5. package/dist/auth-cookie-DWx6Q3xo.d.ts +168 -0
  6. package/dist/cookies.cjs +233 -0
  7. package/dist/cookies.d.cts +109 -0
  8. package/dist/cookies.d.ts +109 -0
  9. package/dist/cookies.js +210 -0
  10. package/dist/credentials.cjs +68 -0
  11. package/dist/credentials.d.cts +82 -0
  12. package/dist/credentials.d.ts +82 -0
  13. package/dist/credentials.js +47 -0
  14. package/dist/index.cjs +189 -0
  15. package/dist/index.d.cts +86 -0
  16. package/dist/index.d.ts +86 -0
  17. package/dist/index.js +166 -0
  18. package/dist/next.cjs +655 -0
  19. package/dist/next.d.cts +389 -0
  20. package/dist/next.d.ts +389 -0
  21. package/dist/next.js +637 -0
  22. package/dist/node.cjs +61 -0
  23. package/dist/node.d.cts +33 -0
  24. package/dist/node.d.ts +33 -0
  25. package/dist/node.js +36 -0
  26. package/dist/organizations.cjs +389 -0
  27. package/dist/organizations.d.cts +226 -0
  28. package/dist/organizations.d.ts +226 -0
  29. package/dist/organizations.js +366 -0
  30. package/dist/react.cjs +206 -0
  31. package/dist/react.d.cts +163 -0
  32. package/dist/react.d.ts +163 -0
  33. package/dist/react.js +186 -0
  34. package/dist/session-DhRO4euL.d.cts +102 -0
  35. package/dist/session-DhRO4euL.d.ts +102 -0
  36. package/dist/session-identity.cjs +125 -0
  37. package/dist/session-identity.d.cts +100 -0
  38. package/dist/session-identity.d.ts +100 -0
  39. package/dist/session-identity.js +104 -0
  40. package/dist/testing.cjs +67 -0
  41. package/dist/testing.d.cts +38 -0
  42. package/dist/testing.d.ts +38 -0
  43. package/dist/testing.js +46 -0
  44. package/package.json +186 -6
@@ -0,0 +1,109 @@
1
+ export { A as AuthCookie, a as AuthCookieOptions, b as AuthCookieValue, C as CreateAuthCookieArgs, D as DEFAULT_AUTH_COOKIE_NAME, R as ReconciledAuthCookies, c as createAuthCookie, p as parseCookieHeader } from './auth-cookie-DWx6Q3xo.cjs';
2
+
3
+ /**
4
+ * THE ACTIVE-ORGANIZATION COOKIE — one browser-stored "which organization am I
5
+ * acting in" shared by EVERY Matrx web surface on the same apex.
6
+ *
7
+ * WHY (2026-09-08, the Workflow Studio `organization_not_selected` incident):
8
+ * the main app (www.aimatrx.com) remembered the last active organization in
9
+ * its own IndexedDB/localStorage cache; Workflow Studio (workflows.aimatrx.com)
10
+ * remembered its own choice under `matrx_studio_org`; the admin dashboard
11
+ * under `matrx_dashboard_org`. Three origins, three memories, and a person who
12
+ * had been working in one organization for weeks opened Studio and was
13
+ * refused on every request until they picked again — a choice they had
14
+ * already made. Browser storage is per-origin; a cookie issued at the apex
15
+ * (`Domain=.aimatrx.com`) is the ONE browser-stored value every subdomain can
16
+ * read, exactly how the platform already shares its login (see
17
+ * `auth-cookie.ts`). Cookies also ignore ports, so localhost:3000 and
18
+ * localhost:3101 share it in development.
19
+ *
20
+ * WHAT IT IS, AND IS NOT:
21
+ * - It is the FIRST rung of the canonical resolution order (stored selection
22
+ * → sole membership → make the user pick; a saved default is display-only;
23
+ * ruling: common-docs/projects/no-db-assigned-org/PLAN.md row EX-T05). A
24
+ * consumer still validates the stored id against live membership and
25
+ * drops it when the membership is gone — this module stores, it never
26
+ * decides.
27
+ * - It is IDENTITY-KEYED: the value carries the user id it was written for,
28
+ * and `read(userId)` answers null for anybody else. A second person
29
+ * signing in on the same browser must never inherit the first person's
30
+ * workspace (the same rule the sync engine applies to every persisted
31
+ * slice). Sign-out calls `clear()` on top of that.
32
+ * - It is NOT a security boundary. The server admits a request only when
33
+ * the `X-Organization-Id` it carries is a verified membership of the
34
+ * bearer; this cookie only remembers which of those the person chose.
35
+ * - It is NOT httpOnly — hosts read and write it from the browser. Nothing
36
+ * secret is in it.
37
+ *
38
+ * The Domain attribute is set ONLY on a real apex host (the same rule and the
39
+ * same reason as the auth cookie: a browser silently rejects a Set-Cookie
40
+ * whose Domain does not cover the current host). Studio runs on TWO apexes
41
+ * (workflows.aimatrx.com and studio.app.matrxserver.com), so this takes a
42
+ * list; the first apex that covers the host wins.
43
+ */
44
+ declare const DEFAULT_ACTIVE_ORG_COOKIE_NAME = "matrx-active-org";
45
+ /** One year — the same horizon the platform's other preference cookies use. */
46
+ declare const DEFAULT_ACTIVE_ORG_COOKIE_MAX_AGE: number;
47
+ interface ActiveOrgCookieOptions {
48
+ name: string;
49
+ domain?: string;
50
+ }
51
+ /** The stored fact: WHO chose WHICH organization. */
52
+ interface ActiveOrgSelection {
53
+ userId: string;
54
+ organizationId: string;
55
+ }
56
+ interface CreateActiveOrgCookieArgs {
57
+ /**
58
+ * The apex domains one selection must span, e.g.
59
+ * `["aimatrx.com", "matrxserver.com"]`. Order matters only when two apexes
60
+ * could cover one host, which they never should.
61
+ */
62
+ apexDomains: readonly string[];
63
+ /** Override only for a second, unrelated deployment — never per-app. */
64
+ cookieName?: string;
65
+ /** Lifetime in seconds. Defaults to one year. */
66
+ maxAgeSeconds?: number;
67
+ }
68
+ interface ActiveOrgCookie {
69
+ cookieName: string;
70
+ /**
71
+ * Cookie options for the host serving the current request.
72
+ * @param host `Host` header or `window.location.hostname` (port tolerated).
73
+ */
74
+ optionsForHost(host: string | null | undefined): ActiveOrgCookieOptions;
75
+ /**
76
+ * The organization `userId` last chose, from a raw `Cookie` request header
77
+ * (SSR) — or null when nothing is stored, the value is malformed, or it
78
+ * belongs to a different person.
79
+ */
80
+ readFromCookieHeader(header: string | null | undefined, userId: string | null | undefined): string | null;
81
+ /** Browser read of the same fact from `document.cookie`. SSR-safe (null). */
82
+ read(userId: string | null | undefined): string | null;
83
+ /**
84
+ * Record `userId`'s choice in the browser at the scope `optionsForHost`
85
+ * decides for this host. A previous host-only copy is expired first so the
86
+ * jar never holds two same-name cookies at two scopes (whichever the
87
+ * browser sent first would win — see `auth-cookie.ts` on split jars).
88
+ */
89
+ write(selection: ActiveOrgSelection): void;
90
+ /**
91
+ * Forget the selection in the browser — at EVERY scope this host could have
92
+ * issued it (domain-wide and host-only), so sign-out cannot leave a copy
93
+ * behind for the next person.
94
+ */
95
+ clear(): void;
96
+ /**
97
+ * A `Set-Cookie` header value recording `selection` for `host` (null →
98
+ * an expiring cookie). For servers that want to write it; browsers use
99
+ * `write`.
100
+ */
101
+ serialize(host: string | null | undefined, selection: ActiveOrgSelection | null, secure?: boolean): string;
102
+ }
103
+ /** `<userId>:<organizationId>` — two UUIDs, one separator. */
104
+ declare function encodeActiveOrgSelection(selection: ActiveOrgSelection): string;
105
+ /** Inverse of {@link encodeActiveOrgSelection}. Null for anything malformed. */
106
+ declare function decodeActiveOrgSelection(raw: string | null | undefined): ActiveOrgSelection | null;
107
+ declare function createActiveOrgCookie(args: CreateActiveOrgCookieArgs): ActiveOrgCookie;
108
+
109
+ export { type ActiveOrgCookie, type ActiveOrgCookieOptions, type ActiveOrgSelection, type CreateActiveOrgCookieArgs, DEFAULT_ACTIVE_ORG_COOKIE_MAX_AGE, DEFAULT_ACTIVE_ORG_COOKIE_NAME, createActiveOrgCookie, decodeActiveOrgSelection, encodeActiveOrgSelection };
@@ -0,0 +1,109 @@
1
+ export { A as AuthCookie, a as AuthCookieOptions, b as AuthCookieValue, C as CreateAuthCookieArgs, D as DEFAULT_AUTH_COOKIE_NAME, R as ReconciledAuthCookies, c as createAuthCookie, p as parseCookieHeader } from './auth-cookie-DWx6Q3xo.js';
2
+
3
+ /**
4
+ * THE ACTIVE-ORGANIZATION COOKIE — one browser-stored "which organization am I
5
+ * acting in" shared by EVERY Matrx web surface on the same apex.
6
+ *
7
+ * WHY (2026-09-08, the Workflow Studio `organization_not_selected` incident):
8
+ * the main app (www.aimatrx.com) remembered the last active organization in
9
+ * its own IndexedDB/localStorage cache; Workflow Studio (workflows.aimatrx.com)
10
+ * remembered its own choice under `matrx_studio_org`; the admin dashboard
11
+ * under `matrx_dashboard_org`. Three origins, three memories, and a person who
12
+ * had been working in one organization for weeks opened Studio and was
13
+ * refused on every request until they picked again — a choice they had
14
+ * already made. Browser storage is per-origin; a cookie issued at the apex
15
+ * (`Domain=.aimatrx.com`) is the ONE browser-stored value every subdomain can
16
+ * read, exactly how the platform already shares its login (see
17
+ * `auth-cookie.ts`). Cookies also ignore ports, so localhost:3000 and
18
+ * localhost:3101 share it in development.
19
+ *
20
+ * WHAT IT IS, AND IS NOT:
21
+ * - It is the FIRST rung of the canonical resolution order (stored selection
22
+ * → sole membership → make the user pick; a saved default is display-only;
23
+ * ruling: common-docs/projects/no-db-assigned-org/PLAN.md row EX-T05). A
24
+ * consumer still validates the stored id against live membership and
25
+ * drops it when the membership is gone — this module stores, it never
26
+ * decides.
27
+ * - It is IDENTITY-KEYED: the value carries the user id it was written for,
28
+ * and `read(userId)` answers null for anybody else. A second person
29
+ * signing in on the same browser must never inherit the first person's
30
+ * workspace (the same rule the sync engine applies to every persisted
31
+ * slice). Sign-out calls `clear()` on top of that.
32
+ * - It is NOT a security boundary. The server admits a request only when
33
+ * the `X-Organization-Id` it carries is a verified membership of the
34
+ * bearer; this cookie only remembers which of those the person chose.
35
+ * - It is NOT httpOnly — hosts read and write it from the browser. Nothing
36
+ * secret is in it.
37
+ *
38
+ * The Domain attribute is set ONLY on a real apex host (the same rule and the
39
+ * same reason as the auth cookie: a browser silently rejects a Set-Cookie
40
+ * whose Domain does not cover the current host). Studio runs on TWO apexes
41
+ * (workflows.aimatrx.com and studio.app.matrxserver.com), so this takes a
42
+ * list; the first apex that covers the host wins.
43
+ */
44
+ declare const DEFAULT_ACTIVE_ORG_COOKIE_NAME = "matrx-active-org";
45
+ /** One year — the same horizon the platform's other preference cookies use. */
46
+ declare const DEFAULT_ACTIVE_ORG_COOKIE_MAX_AGE: number;
47
+ interface ActiveOrgCookieOptions {
48
+ name: string;
49
+ domain?: string;
50
+ }
51
+ /** The stored fact: WHO chose WHICH organization. */
52
+ interface ActiveOrgSelection {
53
+ userId: string;
54
+ organizationId: string;
55
+ }
56
+ interface CreateActiveOrgCookieArgs {
57
+ /**
58
+ * The apex domains one selection must span, e.g.
59
+ * `["aimatrx.com", "matrxserver.com"]`. Order matters only when two apexes
60
+ * could cover one host, which they never should.
61
+ */
62
+ apexDomains: readonly string[];
63
+ /** Override only for a second, unrelated deployment — never per-app. */
64
+ cookieName?: string;
65
+ /** Lifetime in seconds. Defaults to one year. */
66
+ maxAgeSeconds?: number;
67
+ }
68
+ interface ActiveOrgCookie {
69
+ cookieName: string;
70
+ /**
71
+ * Cookie options for the host serving the current request.
72
+ * @param host `Host` header or `window.location.hostname` (port tolerated).
73
+ */
74
+ optionsForHost(host: string | null | undefined): ActiveOrgCookieOptions;
75
+ /**
76
+ * The organization `userId` last chose, from a raw `Cookie` request header
77
+ * (SSR) — or null when nothing is stored, the value is malformed, or it
78
+ * belongs to a different person.
79
+ */
80
+ readFromCookieHeader(header: string | null | undefined, userId: string | null | undefined): string | null;
81
+ /** Browser read of the same fact from `document.cookie`. SSR-safe (null). */
82
+ read(userId: string | null | undefined): string | null;
83
+ /**
84
+ * Record `userId`'s choice in the browser at the scope `optionsForHost`
85
+ * decides for this host. A previous host-only copy is expired first so the
86
+ * jar never holds two same-name cookies at two scopes (whichever the
87
+ * browser sent first would win — see `auth-cookie.ts` on split jars).
88
+ */
89
+ write(selection: ActiveOrgSelection): void;
90
+ /**
91
+ * Forget the selection in the browser — at EVERY scope this host could have
92
+ * issued it (domain-wide and host-only), so sign-out cannot leave a copy
93
+ * behind for the next person.
94
+ */
95
+ clear(): void;
96
+ /**
97
+ * A `Set-Cookie` header value recording `selection` for `host` (null →
98
+ * an expiring cookie). For servers that want to write it; browsers use
99
+ * `write`.
100
+ */
101
+ serialize(host: string | null | undefined, selection: ActiveOrgSelection | null, secure?: boolean): string;
102
+ }
103
+ /** `<userId>:<organizationId>` — two UUIDs, one separator. */
104
+ declare function encodeActiveOrgSelection(selection: ActiveOrgSelection): string;
105
+ /** Inverse of {@link encodeActiveOrgSelection}. Null for anything malformed. */
106
+ declare function decodeActiveOrgSelection(raw: string | null | undefined): ActiveOrgSelection | null;
107
+ declare function createActiveOrgCookie(args: CreateActiveOrgCookieArgs): ActiveOrgCookie;
108
+
109
+ export { type ActiveOrgCookie, type ActiveOrgCookieOptions, type ActiveOrgSelection, type CreateActiveOrgCookieArgs, DEFAULT_ACTIVE_ORG_COOKIE_MAX_AGE, DEFAULT_ACTIVE_ORG_COOKIE_NAME, createActiveOrgCookie, decodeActiveOrgSelection, encodeActiveOrgSelection };
@@ -0,0 +1,210 @@
1
+ // src/cookies/auth-cookie.ts
2
+ var DEFAULT_AUTH_COOKIE_NAME = "sb-matrx-auth";
3
+ function parseCookieHeader(header) {
4
+ if (!header) return [];
5
+ const out = [];
6
+ for (const pair of header.split(";")) {
7
+ const eq = pair.indexOf("=");
8
+ const name = (eq === -1 ? pair : pair.slice(0, eq)).trim();
9
+ if (!name) continue;
10
+ const raw = eq === -1 ? "" : pair.slice(eq + 1).trim();
11
+ let value = raw;
12
+ try {
13
+ value = decodeURIComponent(raw);
14
+ } catch {
15
+ }
16
+ out.push({ name, value });
17
+ }
18
+ return out;
19
+ }
20
+ function belongsToStorageKey(name, storageKey) {
21
+ if (name === storageKey) return true;
22
+ if (!name.startsWith(`${storageKey}.`)) return false;
23
+ return /^(0|[1-9][0-9]*)$/.test(name.slice(storageKey.length + 1));
24
+ }
25
+ function renamedCookie(cookie, from, to) {
26
+ if (cookie.name === from) return { ...cookie, name: to };
27
+ const prefix = `${from}.`;
28
+ if (!cookie.name.startsWith(prefix)) return null;
29
+ const chunk = cookie.name.slice(prefix.length);
30
+ if (!/^(0|[1-9][0-9]*)$/.test(chunk)) return null;
31
+ return { ...cookie, name: `${to}.${chunk}` };
32
+ }
33
+ function createAuthCookie(args) {
34
+ const cookieName = args.cookieName ?? DEFAULT_AUTH_COOKIE_NAME;
35
+ const legacyCookieName = args.legacyCookieName;
36
+ const apex = args.apexDomain.toLowerCase();
37
+ const optionsForHost = (host) => {
38
+ const normalized = (host ?? "").toLowerCase();
39
+ const hostname = normalized.startsWith("[") ? normalized.slice(1, normalized.indexOf("]")) : normalized === "::1" ? normalized : normalized.split(":")[0] ?? "";
40
+ const loopback = hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "::1" || /^127(?:\.(?:0|[1-9]\d{0,2})){3}$/.test(hostname) && hostname.split(".").every((part) => Number(part) <= 255);
41
+ const secure = !loopback;
42
+ const onApex = hostname === apex || hostname.endsWith(`.${apex}`);
43
+ return onApex ? { name: cookieName, domain: `.${apex}`, secure } : { name: cookieName, secure };
44
+ };
45
+ const isCurrentCookie = (name) => belongsToStorageKey(name, cookieName);
46
+ const isAuthCookie = (name) => isCurrentCookie(name) || legacyCookieName !== void 0 && (name === legacyCookieName || name.startsWith(`${legacyCookieName}.`));
47
+ const reconcile = (cookies) => {
48
+ const seen = /* @__PURE__ */ new Map();
49
+ const out = [];
50
+ const shadowed = /* @__PURE__ */ new Set();
51
+ for (const cookie of cookies) {
52
+ const at = seen.get(cookie.name);
53
+ if (at === void 0) {
54
+ seen.set(cookie.name, out.length);
55
+ out.push({ name: cookie.name, value: cookie.value });
56
+ continue;
57
+ }
58
+ if (!isAuthCookie(cookie.name)) continue;
59
+ shadowed.add(cookie.name);
60
+ const held = out[at];
61
+ if (held !== void 0 && held.value.length === 0) {
62
+ out[at] = { name: cookie.name, value: cookie.value };
63
+ }
64
+ }
65
+ const ambiguous = /* @__PURE__ */ new Set();
66
+ const ambiguousKeys = [];
67
+ const byName = new Map(out.map((cookie) => [cookie.name, cookie]));
68
+ const storageKeys = [cookieName, ...legacyCookieName ? [legacyCookieName] : []];
69
+ for (const storageKey of storageKeys) {
70
+ const unchunked = byName.get(storageKey);
71
+ const firstChunk = byName.get(`${storageKey}.0`);
72
+ if (!unchunked?.value || !firstChunk?.value) continue;
73
+ ambiguousKeys.push(storageKey);
74
+ for (const { name } of out) {
75
+ if (belongsToStorageKey(name, storageKey)) ambiguous.add(name);
76
+ }
77
+ }
78
+ const resolved = ambiguous.size === 0 ? out : out.filter(({ name }) => !ambiguous.has(name));
79
+ const shadowedNames = [...shadowed];
80
+ const ambiguousNames = [...ambiguous];
81
+ return {
82
+ cookies: resolved,
83
+ shadowedNames,
84
+ ambiguousNames,
85
+ ambiguousKeys,
86
+ split: shadowedNames.length > 0 || ambiguousNames.length > 0
87
+ };
88
+ };
89
+ return {
90
+ cookieName,
91
+ legacyCookieName,
92
+ optionsForHost,
93
+ reconcile,
94
+ browserOptions: () => optionsForHost(
95
+ typeof window !== "undefined" ? window.location.hostname : null
96
+ ),
97
+ isCurrentCookie,
98
+ migrateLegacyCookies: (cookies) => {
99
+ if (!legacyCookieName) return [];
100
+ if (cookies.some(({ name }) => isCurrentCookie(name))) return [];
101
+ return cookies.flatMap((cookie) => {
102
+ const renamed = renamedCookie(cookie, legacyCookieName, cookieName);
103
+ return renamed ? [renamed] : [];
104
+ });
105
+ }
106
+ };
107
+ }
108
+
109
+ // src/cookies/active-org-cookie.ts
110
+ import { isUuidShape } from "@ai-matrx/kit/uuid";
111
+ var DEFAULT_ACTIVE_ORG_COOKIE_NAME = "matrx-active-org";
112
+ var DEFAULT_ACTIVE_ORG_COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
113
+ function encodeActiveOrgSelection(selection) {
114
+ if (!isUuidShape(selection.userId) || !isUuidShape(selection.organizationId)) {
115
+ throw new Error(
116
+ "encodeActiveOrgSelection: userId and organizationId must both be UUIDs."
117
+ );
118
+ }
119
+ return `${selection.userId.toLowerCase()}:${selection.organizationId.toLowerCase()}`;
120
+ }
121
+ function decodeActiveOrgSelection(raw) {
122
+ if (!raw) return null;
123
+ const parts = raw.split(":");
124
+ if (parts.length !== 2) return null;
125
+ const [userId, organizationId] = parts;
126
+ if (!isUuidShape(userId) || !isUuidShape(organizationId)) return null;
127
+ return {
128
+ userId: userId.toLowerCase(),
129
+ organizationId: organizationId.toLowerCase()
130
+ };
131
+ }
132
+ function selectionFor(cookies, cookieName, userId) {
133
+ if (!userId) return null;
134
+ const wanted = userId.toLowerCase();
135
+ for (const cookie of cookies) {
136
+ if (cookie.name !== cookieName) continue;
137
+ const decoded = decodeActiveOrgSelection(cookie.value);
138
+ if (decoded && decoded.userId === wanted) return decoded.organizationId;
139
+ }
140
+ return null;
141
+ }
142
+ function attributes(options, maxAge, secure) {
143
+ const parts = [`Path=/`, `Max-Age=${maxAge}`, `SameSite=Lax`];
144
+ if (options.domain) parts.push(`Domain=${options.domain}`);
145
+ if (secure) parts.push("Secure");
146
+ return parts.join("; ");
147
+ }
148
+ function hasDocument() {
149
+ return typeof document !== "undefined" && typeof document.cookie === "string";
150
+ }
151
+ function createActiveOrgCookie(args) {
152
+ const cookieName = args.cookieName ?? DEFAULT_ACTIVE_ORG_COOKIE_NAME;
153
+ const maxAge = args.maxAgeSeconds ?? DEFAULT_ACTIVE_ORG_COOKIE_MAX_AGE;
154
+ const apexes = args.apexDomains.map((apex) => apex.toLowerCase());
155
+ const optionsForHost = (host) => {
156
+ const hostname = (host ?? "").split(":")[0]?.toLowerCase() ?? "";
157
+ const apex = apexes.find(
158
+ (candidate) => hostname === candidate || hostname.endsWith(`.${candidate}`)
159
+ );
160
+ return apex ? { name: cookieName, domain: `.${apex}` } : { name: cookieName };
161
+ };
162
+ const serialize = (host, selection, secure = false) => {
163
+ const options = optionsForHost(host);
164
+ if (!selection) {
165
+ return `${cookieName}=; ${attributes(options, 0, secure)}`;
166
+ }
167
+ return `${cookieName}=${encodeActiveOrgSelection(selection)}; ${attributes(options, maxAge, secure)}`;
168
+ };
169
+ const browserHost = () => typeof window !== "undefined" ? window.location.hostname : null;
170
+ const browserSecure = () => typeof window !== "undefined" && window.location.protocol === "https:";
171
+ const expireHostOnly = (secure) => {
172
+ document.cookie = `${cookieName}=; ${attributes({ name: cookieName }, 0, secure)}`;
173
+ };
174
+ return {
175
+ cookieName,
176
+ optionsForHost,
177
+ serialize,
178
+ readFromCookieHeader: (header, userId) => selectionFor(parseCookieHeader(header), cookieName, userId),
179
+ read: (userId) => {
180
+ if (!hasDocument()) return null;
181
+ return selectionFor(parseCookieHeader(document.cookie), cookieName, userId);
182
+ },
183
+ write: (selection) => {
184
+ if (!hasDocument()) return;
185
+ const host = browserHost();
186
+ const secure = browserSecure();
187
+ if (optionsForHost(host).domain) expireHostOnly(secure);
188
+ document.cookie = serialize(host, selection, secure);
189
+ },
190
+ clear: () => {
191
+ if (!hasDocument()) return;
192
+ const host = browserHost();
193
+ const secure = browserSecure();
194
+ expireHostOnly(secure);
195
+ if (optionsForHost(host).domain) {
196
+ document.cookie = serialize(host, null, secure);
197
+ }
198
+ }
199
+ };
200
+ }
201
+ export {
202
+ DEFAULT_ACTIVE_ORG_COOKIE_MAX_AGE,
203
+ DEFAULT_ACTIVE_ORG_COOKIE_NAME,
204
+ DEFAULT_AUTH_COOKIE_NAME,
205
+ createActiveOrgCookie,
206
+ createAuthCookie,
207
+ decodeActiveOrgSelection,
208
+ encodeActiveOrgSelection,
209
+ parseCookieHeader
210
+ };
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/credentials.ts
21
+ var credentials_exports = {};
22
+ __export(credentials_exports, {
23
+ API_KEY_PREFIX: () => API_KEY_PREFIX,
24
+ MATRX_HEADERS: () => MATRX_HEADERS,
25
+ credentialToHeaders: () => credentialToHeaders,
26
+ toCredentialsPort: () => toCredentialsPort
27
+ });
28
+ module.exports = __toCommonJS(credentials_exports);
29
+ var MATRX_HEADERS = {
30
+ authorization: "Authorization",
31
+ fingerprint: "X-Fingerprint-ID",
32
+ organization: "X-Organization-Id",
33
+ actingUser: "X-Matrx-User-Id",
34
+ requestId: "X-Request-Id"
35
+ };
36
+ var API_KEY_PREFIX = "mx_";
37
+ function credentialToHeaders(credential) {
38
+ if (credential === null) return {};
39
+ switch (credential.kind) {
40
+ case "user":
41
+ return { [MATRX_HEADERS.authorization]: `Bearer ${credential.accessToken}` };
42
+ case "guest":
43
+ return { [MATRX_HEADERS.fingerprint]: credential.fingerprintId };
44
+ case "scoped":
45
+ return { [MATRX_HEADERS.authorization]: `Bearer ${credential.token}` };
46
+ case "service": {
47
+ const header = credential.secretHeader ?? MATRX_HEADERS.authorization;
48
+ const headers = header.toLowerCase() === "authorization" ? { [MATRX_HEADERS.authorization]: `Bearer ${credential.secret}` } : { [header]: credential.secret };
49
+ if (credential.actingUserId) headers[MATRX_HEADERS.actingUser] = credential.actingUserId;
50
+ return headers;
51
+ }
52
+ case "api_key": {
53
+ if (!credential.apiKey.startsWith(API_KEY_PREFIX)) {
54
+ throw new Error(
55
+ `credentialToHeaders: an api_key credential must start with "${API_KEY_PREFIX}" (AI Matrx API keys look like mx_live_...)`
56
+ );
57
+ }
58
+ return { [MATRX_HEADERS.authorization]: `Bearer ${credential.apiKey}` };
59
+ }
60
+ }
61
+ }
62
+ function toCredentialsPort(setting) {
63
+ if (setting === void 0 || setting === null) return { get: async () => null };
64
+ if (typeof setting === "function") return { get: async () => setting() };
65
+ if ("get" in setting && typeof setting.get === "function") return setting;
66
+ const fixed = setting;
67
+ return { get: async () => fixed };
68
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The credentials port — the ONE credential→header mapping for every client
3
+ * of every AI Matrx server (package lines v3 §3.4; API-key lane C16).
4
+ *
5
+ * Five credential kinds, one mapping, in exactly one place. The kind names are
6
+ * the contract's own `x-matrx-auth` tier names, so a generated operation's
7
+ * tier and the credential that satisfies it read the same in every language
8
+ * (TypeScript here; the Python `matrx-api` and future Swift/Kotlin clients
9
+ * share these header names and shapes — see {@link MATRX_HEADERS}):
10
+ *
11
+ * - `user` — a signed-in person: `Authorization: Bearer <Supabase JWT>`.
12
+ * - `guest` — a fingerprint: `X-Fingerprint-ID: <id>`.
13
+ * - `api_key` — an AI Matrx API key: `Authorization: Bearer mx_live_...`.
14
+ * The server's ONE AuthMiddleware disambiguates by shape (`mx_` prefix vs
15
+ * three-dot JWT), so `user` and `api_key` ride the same header.
16
+ * - `service` — one of our servers calling another: the server secret as
17
+ * `Authorization: Bearer <secret>` (or in `secretHeader`), plus the acting
18
+ * person in `X-Matrx-User-Id` when there is one
19
+ * (`matrx_connect.service_auth`).
20
+ * - `scoped` — a short-lived capability token (sandbox, desktop app, broker
21
+ * client): `Authorization: Bearer <token>` (`matrx_connect.scoped_tokens`).
22
+ * Short-lived means the PORT re-reads it per call: hand a `CredentialsPort`
23
+ * whose `get()` returns the current token.
24
+ *
25
+ * No polling, no readiness loop: readiness is the port's promise.
26
+ */
27
+ /** Header names every AI Matrx client and server shares (TS, Python, native). */
28
+ declare const MATRX_HEADERS: {
29
+ readonly authorization: "Authorization";
30
+ readonly fingerprint: "X-Fingerprint-ID";
31
+ readonly organization: "X-Organization-Id";
32
+ readonly actingUser: "X-Matrx-User-Id";
33
+ readonly requestId: "X-Request-Id";
34
+ };
35
+ /** The credential kinds — identical to the contract's `x-matrx-auth` tiers that carry a credential. */
36
+ type MatrxCredentialKind = MatrxCredential["kind"];
37
+ /** A credential a Matrx client can present. */
38
+ type MatrxCredential = {
39
+ kind: "user";
40
+ accessToken: string;
41
+ } | {
42
+ kind: "guest";
43
+ fingerprintId: string;
44
+ } | {
45
+ kind: "api_key";
46
+ apiKey: string;
47
+ } | {
48
+ kind: "service";
49
+ /** The server secret (an existing env value, e.g. MATRX_AIDREAM_SERVICE_TOKEN). */
50
+ secret: string;
51
+ /** The person the call acts for, when there is one (`X-Matrx-User-Id`). */
52
+ actingUserId?: string;
53
+ /** Where the secret rides. Default `Authorization` (as a Bearer value). */
54
+ secretHeader?: string;
55
+ } | {
56
+ kind: "scoped";
57
+ token: string;
58
+ };
59
+ /**
60
+ * The async source of the current credential. `get()` resolves when the host
61
+ * knows its auth state (null = anonymous); `onChange` lets transports refresh
62
+ * cached headers when the credential rotates.
63
+ */
64
+ interface CredentialsPort {
65
+ get(): Promise<MatrxCredential | null>;
66
+ onChange?(cb: () => void): () => void;
67
+ }
68
+ /** Prefix that marks a bearer token as an AI Matrx API key, never a JWT. */
69
+ declare const API_KEY_PREFIX = "mx_";
70
+ /**
71
+ * Map a credential to the exact request headers the AI Matrx platform expects.
72
+ * This is the single place that knowledge lives — transports spread the result
73
+ * into their request headers and add `X-Organization-Id` from the org context
74
+ * binding separately.
75
+ */
76
+ declare function credentialToHeaders(credential: MatrxCredential | null): Record<string, string>;
77
+ /** What `createApi({ credentials })` accepts: a fixed credential, a getter, or a port. */
78
+ type CredentialsSetting = MatrxCredential | null | (() => MatrxCredential | null | Promise<MatrxCredential | null>) | CredentialsPort;
79
+ /** Normalize the ONE credentials setting into a `CredentialsPort`. */
80
+ declare function toCredentialsPort(setting: CredentialsSetting | undefined): CredentialsPort;
81
+
82
+ export { API_KEY_PREFIX, type CredentialsPort, type CredentialsSetting, MATRX_HEADERS, type MatrxCredential, type MatrxCredentialKind, credentialToHeaders, toCredentialsPort };
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The credentials port — the ONE credential→header mapping for every client
3
+ * of every AI Matrx server (package lines v3 §3.4; API-key lane C16).
4
+ *
5
+ * Five credential kinds, one mapping, in exactly one place. The kind names are
6
+ * the contract's own `x-matrx-auth` tier names, so a generated operation's
7
+ * tier and the credential that satisfies it read the same in every language
8
+ * (TypeScript here; the Python `matrx-api` and future Swift/Kotlin clients
9
+ * share these header names and shapes — see {@link MATRX_HEADERS}):
10
+ *
11
+ * - `user` — a signed-in person: `Authorization: Bearer <Supabase JWT>`.
12
+ * - `guest` — a fingerprint: `X-Fingerprint-ID: <id>`.
13
+ * - `api_key` — an AI Matrx API key: `Authorization: Bearer mx_live_...`.
14
+ * The server's ONE AuthMiddleware disambiguates by shape (`mx_` prefix vs
15
+ * three-dot JWT), so `user` and `api_key` ride the same header.
16
+ * - `service` — one of our servers calling another: the server secret as
17
+ * `Authorization: Bearer <secret>` (or in `secretHeader`), plus the acting
18
+ * person in `X-Matrx-User-Id` when there is one
19
+ * (`matrx_connect.service_auth`).
20
+ * - `scoped` — a short-lived capability token (sandbox, desktop app, broker
21
+ * client): `Authorization: Bearer <token>` (`matrx_connect.scoped_tokens`).
22
+ * Short-lived means the PORT re-reads it per call: hand a `CredentialsPort`
23
+ * whose `get()` returns the current token.
24
+ *
25
+ * No polling, no readiness loop: readiness is the port's promise.
26
+ */
27
+ /** Header names every AI Matrx client and server shares (TS, Python, native). */
28
+ declare const MATRX_HEADERS: {
29
+ readonly authorization: "Authorization";
30
+ readonly fingerprint: "X-Fingerprint-ID";
31
+ readonly organization: "X-Organization-Id";
32
+ readonly actingUser: "X-Matrx-User-Id";
33
+ readonly requestId: "X-Request-Id";
34
+ };
35
+ /** The credential kinds — identical to the contract's `x-matrx-auth` tiers that carry a credential. */
36
+ type MatrxCredentialKind = MatrxCredential["kind"];
37
+ /** A credential a Matrx client can present. */
38
+ type MatrxCredential = {
39
+ kind: "user";
40
+ accessToken: string;
41
+ } | {
42
+ kind: "guest";
43
+ fingerprintId: string;
44
+ } | {
45
+ kind: "api_key";
46
+ apiKey: string;
47
+ } | {
48
+ kind: "service";
49
+ /** The server secret (an existing env value, e.g. MATRX_AIDREAM_SERVICE_TOKEN). */
50
+ secret: string;
51
+ /** The person the call acts for, when there is one (`X-Matrx-User-Id`). */
52
+ actingUserId?: string;
53
+ /** Where the secret rides. Default `Authorization` (as a Bearer value). */
54
+ secretHeader?: string;
55
+ } | {
56
+ kind: "scoped";
57
+ token: string;
58
+ };
59
+ /**
60
+ * The async source of the current credential. `get()` resolves when the host
61
+ * knows its auth state (null = anonymous); `onChange` lets transports refresh
62
+ * cached headers when the credential rotates.
63
+ */
64
+ interface CredentialsPort {
65
+ get(): Promise<MatrxCredential | null>;
66
+ onChange?(cb: () => void): () => void;
67
+ }
68
+ /** Prefix that marks a bearer token as an AI Matrx API key, never a JWT. */
69
+ declare const API_KEY_PREFIX = "mx_";
70
+ /**
71
+ * Map a credential to the exact request headers the AI Matrx platform expects.
72
+ * This is the single place that knowledge lives — transports spread the result
73
+ * into their request headers and add `X-Organization-Id` from the org context
74
+ * binding separately.
75
+ */
76
+ declare function credentialToHeaders(credential: MatrxCredential | null): Record<string, string>;
77
+ /** What `createApi({ credentials })` accepts: a fixed credential, a getter, or a port. */
78
+ type CredentialsSetting = MatrxCredential | null | (() => MatrxCredential | null | Promise<MatrxCredential | null>) | CredentialsPort;
79
+ /** Normalize the ONE credentials setting into a `CredentialsPort`. */
80
+ declare function toCredentialsPort(setting: CredentialsSetting | undefined): CredentialsPort;
81
+
82
+ export { API_KEY_PREFIX, type CredentialsPort, type CredentialsSetting, MATRX_HEADERS, type MatrxCredential, type MatrxCredentialKind, credentialToHeaders, toCredentialsPort };