@openlup/platform 0.15.0 → 0.16.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 (239) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +350 -3
  3. package/dist/identity/adminAuthBinding.d.ts +28 -0
  4. package/dist/identity/adminAuthBinding.js +66 -0
  5. package/dist/identity/adminAuthorization.d.ts +34 -0
  6. package/dist/identity/adminAuthorization.js +72 -0
  7. package/dist/identity/adminContracts.d.ts +70 -0
  8. package/dist/identity/adminContracts.js +43 -0
  9. package/dist/identity/adminMagicLinkHandler.d.ts +39 -0
  10. package/dist/identity/adminMagicLinkHandler.js +94 -0
  11. package/dist/identity/adminMeHandler.d.ts +15 -0
  12. package/dist/identity/adminMeHandler.js +49 -0
  13. package/dist/identity/adminPreflight.d.ts +16 -0
  14. package/dist/identity/adminPreflight.js +38 -0
  15. package/dist/identity/adminUserActionPorts.d.ts +31 -0
  16. package/dist/identity/adminUserActionPorts.js +51 -0
  17. package/dist/identity/adminUserHandlers.d.ts +38 -0
  18. package/dist/identity/adminUserHandlers.js +140 -0
  19. package/dist/identity/authVerifyUrl.d.ts +6 -0
  20. package/dist/identity/authVerifyUrl.js +54 -0
  21. package/dist/identity/contracts/contracts.d.ts +8 -0
  22. package/dist/identity/contracts/contracts.js +2 -0
  23. package/dist/identity/contracts/emailTrust.d.ts +3 -0
  24. package/dist/identity/contracts/emailTrust.js +4 -0
  25. package/dist/identity/contracts/index.d.ts +8 -0
  26. package/dist/identity/contracts/index.js +8 -0
  27. package/dist/identity/contracts/oidc.d.ts +3 -0
  28. package/dist/identity/contracts/oidc.js +9 -0
  29. package/dist/identity/contracts/operatorAuth.d.ts +35 -0
  30. package/dist/identity/contracts/operatorAuth.js +53 -0
  31. package/dist/identity/contracts/operatorInvitations.d.ts +37 -0
  32. package/dist/identity/contracts/operatorInvitations.js +1 -0
  33. package/dist/identity/contracts/ports.d.ts +22 -0
  34. package/dist/identity/contracts/ports.js +1 -0
  35. package/dist/identity/contracts/privacy.d.ts +62 -0
  36. package/dist/identity/contracts/privacy.js +99 -0
  37. package/dist/identity/contracts/providerList.d.ts +3 -0
  38. package/dist/identity/contracts/providerList.js +11 -0
  39. package/dist/identity/contracts/types.d.ts +16 -0
  40. package/dist/identity/contracts/types.js +4 -0
  41. package/dist/identity/contribution.d.ts +8 -0
  42. package/dist/identity/contribution.js +22 -0
  43. package/dist/identity/customerAuth.d.ts +8 -0
  44. package/dist/identity/customerAuth.js +1 -0
  45. package/dist/identity/customerAuthorization.d.ts +5 -0
  46. package/dist/identity/customerAuthorization.js +22 -0
  47. package/dist/identity/customerContracts.d.ts +42 -0
  48. package/dist/identity/customerContracts.js +43 -0
  49. package/dist/identity/customerIdentityBinding.d.ts +44 -0
  50. package/dist/identity/customerIdentityBinding.js +60 -0
  51. package/dist/identity/customerIdentityVerifier.d.ts +8 -0
  52. package/dist/identity/customerIdentityVerifier.js +101 -0
  53. package/dist/identity/customerMagicLinkHandler.d.ts +38 -0
  54. package/dist/identity/customerMagicLinkHandler.js +115 -0
  55. package/dist/identity/customerMeHandler.d.ts +11 -0
  56. package/dist/identity/customerMeHandler.js +46 -0
  57. package/dist/identity/customerPorts.d.ts +4 -0
  58. package/dist/identity/customerPorts.js +1 -0
  59. package/dist/identity/customerSessionBinding.d.ts +38 -0
  60. package/dist/identity/customerSessionBinding.js +80 -0
  61. package/dist/identity/customerSessionIssuer.d.ts +10 -0
  62. package/dist/identity/customerSessionIssuer.js +57 -0
  63. package/dist/identity/customerVerifyOtpHandler.d.ts +8 -0
  64. package/dist/identity/customerVerifyOtpHandler.js +41 -0
  65. package/dist/identity/directOperatorInvitationStore.d.ts +19 -0
  66. package/dist/identity/directOperatorInvitationStore.js +193 -0
  67. package/dist/identity/emailTrustPolicy.d.ts +14 -0
  68. package/dist/identity/emailTrustPolicy.js +16 -0
  69. package/dist/identity/identityPersonalDataContributor.d.ts +7 -0
  70. package/dist/identity/identityPersonalDataContributor.js +46 -0
  71. package/dist/identity/identityReadiness.d.ts +10 -0
  72. package/dist/identity/identityReadiness.js +25 -0
  73. package/dist/identity/index.d.ts +67 -0
  74. package/dist/identity/index.js +66 -0
  75. package/dist/identity/inviteAdminUserUseCase.d.ts +89 -0
  76. package/dist/identity/inviteAdminUserUseCase.js +75 -0
  77. package/dist/identity/jwtLibrary.d.ts +54 -0
  78. package/dist/identity/jwtLibrary.js +1 -0
  79. package/dist/identity/machineActorWriteFence.d.ts +4 -0
  80. package/dist/identity/machineActorWriteFence.js +13 -0
  81. package/dist/identity/magicLinkPorts.d.ts +28 -0
  82. package/dist/identity/magicLinkPorts.js +1 -0
  83. package/dist/identity/managedAccountLinkStore.d.ts +2 -0
  84. package/dist/identity/managedAccountLinkStore.js +93 -0
  85. package/dist/identity/managedAdminAuthVerifier.d.ts +7 -0
  86. package/dist/identity/managedAdminAuthVerifier.js +27 -0
  87. package/dist/identity/managedAdminUserGateways.d.ts +123 -0
  88. package/dist/identity/managedAdminUserGateways.js +127 -0
  89. package/dist/identity/managedClient.d.ts +90 -0
  90. package/dist/identity/managedClient.js +10 -0
  91. package/dist/identity/managedClientAuthLink.d.ts +3 -0
  92. package/dist/identity/managedClientAuthLink.js +115 -0
  93. package/dist/identity/managedCustomerMe.d.ts +13 -0
  94. package/dist/identity/managedCustomerMe.js +30 -0
  95. package/dist/identity/managedEmailTrustPolicy.d.ts +11 -0
  96. package/dist/identity/managedEmailTrustPolicy.js +105 -0
  97. package/dist/identity/managedIdentityVerifier.d.ts +2 -0
  98. package/dist/identity/managedIdentityVerifier.js +16 -0
  99. package/dist/identity/managedPrivacy.d.ts +4 -0
  100. package/dist/identity/managedPrivacy.js +39 -0
  101. package/dist/identity/messages.d.ts +29 -0
  102. package/dist/identity/messages.js +39 -0
  103. package/dist/identity/oidcIdentityVerifier.d.ts +45 -0
  104. package/dist/identity/oidcIdentityVerifier.js +309 -0
  105. package/dist/identity/oidcPrincipal.d.ts +2 -0
  106. package/dist/identity/oidcPrincipal.js +10 -0
  107. package/dist/identity/operatorAdminAuth.d.ts +6 -0
  108. package/dist/identity/operatorAdminAuth.js +13 -0
  109. package/dist/identity/operatorAuthHandlers.d.ts +18 -0
  110. package/dist/identity/operatorAuthHandlers.js +71 -0
  111. package/dist/identity/operatorIdentityVerifier.d.ts +8 -0
  112. package/dist/identity/operatorIdentityVerifier.js +19 -0
  113. package/dist/identity/operatorInvitationLifecycle.d.ts +7 -0
  114. package/dist/identity/operatorInvitationLifecycle.js +15 -0
  115. package/dist/identity/operatorInvitationPorts.d.ts +31 -0
  116. package/dist/identity/operatorInvitationPorts.js +1 -0
  117. package/dist/identity/operatorPorts.d.ts +37 -0
  118. package/dist/identity/operatorPorts.js +1 -0
  119. package/dist/identity/operatorSession.d.ts +13 -0
  120. package/dist/identity/operatorSession.js +31 -0
  121. package/dist/identity/operatorTokenAdminAuth.d.ts +15 -0
  122. package/dist/identity/operatorTokenAdminAuth.js +50 -0
  123. package/dist/identity/personalDataRegistry.d.ts +4 -0
  124. package/dist/identity/personalDataRegistry.js +11 -0
  125. package/dist/identity/platformAdminAuth.d.ts +15 -0
  126. package/dist/identity/platformAdminAuth.js +1 -0
  127. package/dist/identity/ports.d.ts +127 -0
  128. package/dist/identity/ports.js +38 -0
  129. package/dist/identity/postgresAccountLinkStore.d.ts +7 -0
  130. package/dist/identity/postgresAccountLinkStore.js +52 -0
  131. package/dist/identity/postgresCustomerMe.d.ts +3 -0
  132. package/dist/identity/postgresCustomerMe.js +21 -0
  133. package/dist/identity/postgresCustomerRecovery.d.ts +11 -0
  134. package/dist/identity/postgresCustomerRecovery.js +57 -0
  135. package/dist/identity/postgresOperatorIdentity.d.ts +5 -0
  136. package/dist/identity/postgresOperatorIdentity.js +137 -0
  137. package/dist/identity/postgresPrivacy.d.ts +7 -0
  138. package/dist/identity/postgresPrivacy.js +126 -0
  139. package/dist/identity/privacyHandlers.d.ts +10 -0
  140. package/dist/identity/privacyHandlers.js +35 -0
  141. package/dist/identity/privacyPorts.d.ts +72 -0
  142. package/dist/identity/privacyPorts.js +8 -0
  143. package/dist/identity/privacyUseCases.d.ts +5 -0
  144. package/dist/identity/privacyUseCases.js +55 -0
  145. package/dist/identity/reconcileAccountForPrincipal.d.ts +20 -0
  146. package/dist/identity/reconcileAccountForPrincipal.js +66 -0
  147. package/dist/identity/reconcileAccountHandler.d.ts +11 -0
  148. package/dist/identity/reconcileAccountHandler.js +30 -0
  149. package/dist/identity/removeAdminUserUseCase.d.ts +31 -0
  150. package/dist/identity/removeAdminUserUseCase.js +60 -0
  151. package/dist/identity/response.d.ts +3 -0
  152. package/dist/identity/response.js +2 -0
  153. package/dist/identity/sql.d.ts +44 -0
  154. package/dist/identity/sql.js +10 -0
  155. package/package.json +12 -2
  156. package/release-gates.json +22 -0
  157. package/sql/20260810180001_self_host_actor_auth.sql +40 -0
  158. package/sql/20260810204057_customer_identity_read.sql +31 -0
  159. package/sql/20260814090000_customer_selfservice_recovery.sql +477 -0
  160. package/sql/20260901143000_admin_membership_authority_foundation.sql +234 -0
  161. package/sql/20261018120000_identity_operator_invitations.sql +42 -0
  162. package/sql/20261018180000_identity_operator_challenges.sql +56 -0
  163. package/sql/20261018190000_identity_privacy_erasures.sql +15 -0
  164. package/src/identity/adminAuthBinding.ts +76 -0
  165. package/src/identity/adminAuthorization.ts +106 -0
  166. package/src/identity/adminContracts.ts +52 -0
  167. package/src/identity/adminMagicLinkHandler.ts +132 -0
  168. package/src/identity/adminMeHandler.ts +64 -0
  169. package/src/identity/adminPreflight.ts +46 -0
  170. package/src/identity/adminUserActionPorts.ts +78 -0
  171. package/src/identity/adminUserHandlers.ts +174 -0
  172. package/src/identity/authVerifyUrl.ts +56 -0
  173. package/src/identity/contracts/contracts.ts +15 -0
  174. package/src/identity/contracts/emailTrust.ts +5 -0
  175. package/src/identity/contracts/index.ts +8 -0
  176. package/src/identity/contracts/oidc.ts +10 -0
  177. package/src/identity/contracts/operatorAuth.ts +61 -0
  178. package/src/identity/contracts/operatorInvitations.ts +32 -0
  179. package/src/identity/contracts/ports.ts +25 -0
  180. package/src/identity/contracts/privacy.ts +86 -0
  181. package/src/identity/contracts/providerList.ts +13 -0
  182. package/src/identity/contracts/types.ts +25 -0
  183. package/src/identity/contribution.ts +29 -0
  184. package/src/identity/customerAuth.ts +3 -0
  185. package/src/identity/customerAuthorization.ts +38 -0
  186. package/src/identity/customerContracts.ts +52 -0
  187. package/src/identity/customerIdentityBinding.ts +125 -0
  188. package/src/identity/customerIdentityVerifier.ts +141 -0
  189. package/src/identity/customerMagicLinkHandler.ts +171 -0
  190. package/src/identity/customerMeHandler.ts +64 -0
  191. package/src/identity/customerPorts.ts +2 -0
  192. package/src/identity/customerSessionBinding.ts +114 -0
  193. package/src/identity/customerSessionIssuer.ts +71 -0
  194. package/src/identity/customerVerifyOtpHandler.ts +44 -0
  195. package/src/identity/directOperatorInvitationStore.ts +178 -0
  196. package/src/identity/emailTrustPolicy.ts +16 -0
  197. package/src/identity/identityPersonalDataContributor.ts +48 -0
  198. package/src/identity/identityReadiness.ts +26 -0
  199. package/src/identity/index.ts +69 -0
  200. package/src/identity/inviteAdminUserUseCase.ts +134 -0
  201. package/src/identity/jwtLibrary.ts +24 -0
  202. package/src/identity/machineActorWriteFence.ts +19 -0
  203. package/src/identity/magicLinkPorts.ts +7 -0
  204. package/src/identity/managedAccountLinkStore.ts +105 -0
  205. package/src/identity/managedAdminAuthVerifier.ts +40 -0
  206. package/src/identity/managedAdminUserGateways.ts +164 -0
  207. package/src/identity/managedClient.ts +58 -0
  208. package/src/identity/managedClientAuthLink.ts +120 -0
  209. package/src/identity/managedCustomerMe.ts +52 -0
  210. package/src/identity/managedEmailTrustPolicy.ts +90 -0
  211. package/src/identity/managedIdentityVerifier.ts +20 -0
  212. package/src/identity/managedPrivacy.ts +37 -0
  213. package/src/identity/messages.ts +41 -0
  214. package/src/identity/oidcIdentityVerifier.ts +229 -0
  215. package/src/identity/oidcPrincipal.ts +10 -0
  216. package/src/identity/operatorAdminAuth.ts +14 -0
  217. package/src/identity/operatorAuthHandlers.ts +52 -0
  218. package/src/identity/operatorIdentityVerifier.ts +19 -0
  219. package/src/identity/operatorInvitationLifecycle.ts +14 -0
  220. package/src/identity/operatorInvitationPorts.ts +11 -0
  221. package/src/identity/operatorPorts.ts +11 -0
  222. package/src/identity/operatorSession.ts +30 -0
  223. package/src/identity/operatorTokenAdminAuth.ts +79 -0
  224. package/src/identity/personalDataRegistry.ts +8 -0
  225. package/src/identity/platformAdminAuth.ts +7 -0
  226. package/src/identity/ports.ts +158 -0
  227. package/src/identity/postgresAccountLinkStore.ts +49 -0
  228. package/src/identity/postgresCustomerMe.ts +33 -0
  229. package/src/identity/postgresCustomerRecovery.ts +72 -0
  230. package/src/identity/postgresOperatorIdentity.ts +123 -0
  231. package/src/identity/postgresPrivacy.ts +117 -0
  232. package/src/identity/privacyHandlers.ts +33 -0
  233. package/src/identity/privacyPorts.ts +52 -0
  234. package/src/identity/privacyUseCases.ts +48 -0
  235. package/src/identity/reconcileAccountForPrincipal.ts +99 -0
  236. package/src/identity/reconcileAccountHandler.ts +36 -0
  237. package/src/identity/removeAdminUserUseCase.ts +85 -0
  238. package/src/identity/response.ts +3 -0
  239. package/src/identity/sql.ts +10 -0
@@ -0,0 +1,115 @@
1
+ export function createSupabaseClientAuthLinkStore(rawClient, emailTrustPolicy) {
2
+ const client = rawClient;
3
+ return {
4
+ emailTrustPolicy,
5
+ async findClientByEmail(email) {
6
+ const rows = await findManagedContactsByEmail(client, email, true);
7
+ const row = rows[0];
8
+ if (!row)
9
+ return null;
10
+ return { id: row.id, authUserId: row.auth_user_id ?? null };
11
+ },
12
+ async findAuthUserIdByEmail(email) {
13
+ const perPage = 1000;
14
+ const maxUsers = 5000;
15
+ let page = 1;
16
+ let scanned = 0;
17
+ while (scanned < maxUsers) {
18
+ const { data, error } = await client.auth.admin.listUsers({ page, perPage });
19
+ if (error)
20
+ throw error;
21
+ const users = data.users;
22
+ const match = users.find((u) => (u.email ?? "").trim().toLowerCase() === email);
23
+ if (match)
24
+ return match.id;
25
+ scanned += users.length;
26
+ const nextPage = "nextPage" in data ? data.nextPage : null;
27
+ if (!nextPage)
28
+ return null;
29
+ page = nextPage;
30
+ }
31
+ throw new Error("auth_user_lookup_cap_exceeded");
32
+ },
33
+ async findAuthUserEmailById(authUserId) {
34
+ const { data, error } = await client.auth.admin.getUserById(authUserId);
35
+ if (error)
36
+ throw error;
37
+ return data.user?.email ?? null;
38
+ },
39
+ async createAuthUser(email) {
40
+ const { data, error } = await client.auth.admin.createUser({
41
+ email,
42
+ email_confirm: false,
43
+ });
44
+ if (error && isAlreadyRegistered(error))
45
+ return null;
46
+ if (error)
47
+ throw error;
48
+ if (!data.user)
49
+ throw new Error("createUser returned no user");
50
+ return data.user.id;
51
+ },
52
+ async findAdminIdentity(authUserId) {
53
+ const { data, error } = await client
54
+ .from("admin_users")
55
+ .select("id, is_machine_actor")
56
+ .eq("id", authUserId)
57
+ .maybeSingle();
58
+ if (error)
59
+ throw error;
60
+ if (!data)
61
+ return null;
62
+ return { isMachineActor: data.is_machine_actor !== false };
63
+ },
64
+ async setClientAuthUserId(clientId, authUserId) {
65
+ const { data, error } = await client
66
+ .from("clients")
67
+ .update({ auth_user_id: authUserId })
68
+ .eq("id", clientId)
69
+ .is("auth_user_id", null)
70
+ .select("auth_user_id")
71
+ .maybeSingle();
72
+ if (error)
73
+ throw error;
74
+ if (data?.auth_user_id === authUserId) {
75
+ return { ok: true, alreadyLinked: false };
76
+ }
77
+ const { data: current, error: currentError } = await client
78
+ .from("clients")
79
+ .select("auth_user_id")
80
+ .eq("id", clientId)
81
+ .maybeSingle();
82
+ if (currentError)
83
+ throw currentError;
84
+ if (current?.auth_user_id === authUserId) {
85
+ return { ok: true, alreadyLinked: true };
86
+ }
87
+ return {
88
+ ok: false,
89
+ code: "CLIENT_AUTH_LINK_CONFLICT",
90
+ existingAuthUserId: current?.auth_user_id ?? null,
91
+ };
92
+ },
93
+ };
94
+ }
95
+ function isAlreadyRegistered(error) {
96
+ const failure = (error ?? {});
97
+ if (failure.code === "email_exists" || failure.code === "user_already_exists")
98
+ return true;
99
+ return failure.status === 422 && typeof failure.message === "string"
100
+ && /already (been )?registered/i.test(failure.message);
101
+ }
102
+ async function findManagedContactsByEmail(client, email, oldestFirst) {
103
+ const normalized = email.trim().toLowerCase();
104
+ // Escaping bounds the database prefilter; Unicode regex folding can still
105
+ // widen it, so only exact normalized addresses may cross this boundary.
106
+ let query = client.from("clients")
107
+ .select("id, email, auth_user_id")
108
+ .filter("email", "imatch", `^${normalized.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&")}$`);
109
+ if (oldestFirst)
110
+ query = query.order("created_at", { ascending: true });
111
+ const { data, error } = await query.limit(10);
112
+ if (error)
113
+ throw error;
114
+ return (data ?? []).filter((row) => row.email?.trim().toLowerCase() === normalized);
115
+ }
@@ -0,0 +1,13 @@
1
+ import type { CustomerMeResponse } from "./customerContracts.js";
2
+ import type { CustomerMePort } from "./customerPorts.js";
3
+ type LinkedCustomerRow = {
4
+ id: string;
5
+ email: string;
6
+ first_name: string | null;
7
+ last_name: string | null;
8
+ phone: string | null;
9
+ lifecycle_stage: CustomerMeResponse["lifecycleStage"];
10
+ };
11
+ export declare function readLinkedCustomer(rawClient: unknown, userId: string, includePhone?: boolean): Promise<LinkedCustomerRow | null>;
12
+ export declare function createSupabaseCustomerMePort(rawClient: unknown): CustomerMePort;
13
+ export {};
@@ -0,0 +1,30 @@
1
+ export async function readLinkedCustomer(rawClient, userId, includePhone = false) {
2
+ const client = rawClient;
3
+ const { data, error } = await client
4
+ .from("clients")
5
+ .select(includePhone
6
+ ? "id, email, first_name, last_name, phone, lifecycle_stage"
7
+ : "id, email, first_name, last_name, lifecycle_stage")
8
+ .eq("auth_user_id", userId)
9
+ .maybeSingle();
10
+ if (error)
11
+ throw error;
12
+ return data;
13
+ }
14
+ export function createSupabaseCustomerMePort(rawClient) {
15
+ const client = rawClient;
16
+ return {
17
+ async getCustomerMe(userId) {
18
+ const data = await readLinkedCustomer(client, userId);
19
+ if (!data)
20
+ return null;
21
+ return {
22
+ clientId: data.id,
23
+ email: data.email,
24
+ firstName: data.first_name,
25
+ lastName: data.last_name,
26
+ lifecycleStage: data.lifecycle_stage,
27
+ };
28
+ },
29
+ };
30
+ }
@@ -0,0 +1,11 @@
1
+ import type { EmailTrustPolicy } from "./emailTrustPolicy.js";
2
+ /** Server-side assertion by the adopter, never a provider certification. */
3
+ export declare function parseEmailVerifyingProviders(value: string): readonly string[];
4
+ /** One immutable origin/key/allowlist binding; import and construction perform no I/O. */
5
+ export declare function createManagedEmailTrustPolicy(options: {
6
+ authBaseUrl: string;
7
+ publicKey: string;
8
+ emailVerifyingProviders?: readonly string[];
9
+ fetch?: typeof globalThis.fetch;
10
+ now?: () => number;
11
+ }): EmailTrustPolicy;
@@ -0,0 +1,105 @@
1
+ const unavailable = { trusted: false, code: "EMAIL_TRUST_SETTINGS_UNAVAILABLE" };
2
+ const refused = { trusted: false, code: "EMAIL_TRUST_ENVELOPE_REFUSED" };
3
+ const providerId = /^[a-z][a-z0-9_-]*$/;
4
+ /** Server-side assertion by the adopter, never a provider certification. */
5
+ export function parseEmailVerifyingProviders(value) {
6
+ if (!value.trim())
7
+ return [];
8
+ const entries = value.split(",").map(entry => entry.trim().toLowerCase());
9
+ if (entries.some(entry => !providerId.test(entry)))
10
+ throw new Error("Invalid email-verifying provider declaration");
11
+ return [...new Set(entries)].sort();
12
+ }
13
+ /** One immutable origin/key/allowlist binding; import and construction perform no I/O. */
14
+ export function createManagedEmailTrustPolicy(options) {
15
+ const origin = new URL(options.authBaseUrl);
16
+ if (!["http:", "https:"].includes(origin.protocol) || origin.username || origin.password
17
+ || origin.pathname !== "/" || origin.search || origin.hash || !options.publicKey.trim()) {
18
+ throw new Error("Invalid identity email-trust configuration");
19
+ }
20
+ const url = new URL("/auth/v1/settings", origin);
21
+ const publicKey = options.publicKey;
22
+ const allowed = new Set(parseEmailVerifyingProviders((options.emailVerifyingProviders ?? []).join(",")));
23
+ const fetchSettings = options.fetch ?? globalThis.fetch;
24
+ const now = options.now ?? Date.now;
25
+ let cached;
26
+ let inFlight;
27
+ function decode(value) {
28
+ if (!value || typeof value !== "object" || Array.isArray(value))
29
+ return unavailable;
30
+ const { mailer_autoconfirm: automatic, external } = value;
31
+ if (typeof automatic !== "boolean" || !external || typeof external !== "object" || Array.isArray(external))
32
+ return unavailable;
33
+ const entries = Object.entries(external);
34
+ if (entries.some(([key, enabled]) => !providerId.test(key) || typeof enabled !== "boolean"))
35
+ return unavailable;
36
+ if (automatic || entries.some(([key, enabled]) => enabled && key !== "email" && !allowed.has(key)))
37
+ return refused;
38
+ return { trusted: true };
39
+ }
40
+ async function load() {
41
+ const controller = new AbortController();
42
+ let timer;
43
+ const deadline = new Promise((_, reject) => {
44
+ timer = setTimeout(() => { controller.abort(); reject(new Error("Identity settings deadline")); }, 3000);
45
+ });
46
+ async function read() {
47
+ const response = await fetchSettings(url, {
48
+ headers: { apikey: publicKey }, redirect: "error", signal: controller.signal,
49
+ });
50
+ if (!response.ok || response.redirected || !response.body)
51
+ return unavailable;
52
+ const reader = response.body.getReader();
53
+ let bytes = 0;
54
+ const chunks = [];
55
+ try {
56
+ while (true) {
57
+ const chunk = await reader.read();
58
+ if (chunk.done)
59
+ break;
60
+ bytes += chunk.value.byteLength;
61
+ if (bytes > 65536) {
62
+ controller.abort();
63
+ void reader.cancel().catch(() => { });
64
+ return unavailable;
65
+ }
66
+ chunks.push(chunk.value);
67
+ }
68
+ }
69
+ finally {
70
+ reader.releaseLock();
71
+ }
72
+ const body = new Uint8Array(bytes);
73
+ let offset = 0;
74
+ for (const chunk of chunks) {
75
+ body.set(chunk, offset);
76
+ offset += chunk.byteLength;
77
+ }
78
+ return decode(JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(body)));
79
+ }
80
+ try {
81
+ return await Promise.race([read(), deadline]);
82
+ }
83
+ catch {
84
+ return unavailable;
85
+ }
86
+ finally {
87
+ controller.abort();
88
+ clearTimeout(timer);
89
+ }
90
+ }
91
+ return {
92
+ async assess() {
93
+ if (cached && now() < cached.expires)
94
+ return cached.result;
95
+ if (!inFlight) {
96
+ inFlight = load().then(result => {
97
+ const ttl = !result.trusted && result.code === "EMAIL_TRUST_SETTINGS_UNAVAILABLE" ? 5000 : 60000;
98
+ cached = { result, expires: now() + ttl };
99
+ return result;
100
+ }).finally(() => { inFlight = undefined; });
101
+ }
102
+ return inFlight;
103
+ },
104
+ };
105
+ }
@@ -0,0 +1,2 @@
1
+ import type { IdentityVerifierPort } from "./ports.js";
2
+ export declare function createSupabaseIdentityVerifier(rawClient: unknown): IdentityVerifierPort;
@@ -0,0 +1,16 @@
1
+ export function createSupabaseIdentityVerifier(rawClient) {
2
+ const client = rawClient;
3
+ return {
4
+ async verifyAccessToken(accessToken) {
5
+ const { data, error } = await client.auth.getUser(accessToken);
6
+ if (error || !data.user)
7
+ return null;
8
+ const user = data.user;
9
+ return {
10
+ principalId: user.id,
11
+ email: user.email ?? null,
12
+ emailVerified: Boolean(user.email_confirmed_at),
13
+ };
14
+ },
15
+ };
16
+ }
@@ -0,0 +1,4 @@
1
+ import type { ManagedIdentityClient } from "./managedClient.js";
2
+ import { type PrivacyStore } from "./privacyPorts.js";
3
+ /** Reads only committed service projections. No managed write or provider delete is composed. */
4
+ export declare function createManagedPrivacyStore(): PrivacyStore<ManagedIdentityClient>;
@@ -0,0 +1,39 @@
1
+ import { PrivacyRefusal } from "./privacyPorts.js";
2
+ /** Reads only committed service projections. No managed write or provider delete is composed. */
3
+ export function createManagedPrivacyStore() {
4
+ const refuse = async () => { throw new PrivacyRefusal("MANAGED_PRIVACY_ERASURE_UNAVAILABLE"); };
5
+ return {
6
+ erasureSupported: false, prepareErasure: refuse, authorizeErasure: refuse, eraseIdentity: refuse, saveReceipt: refuse,
7
+ async findReceipt() { return null; },
8
+ async resolve(client, ref) {
9
+ const query = client.from("clients").select("id, email, auth_user_id, first_name, last_name, phone");
10
+ const { data: contact, error } = await query.eq(ref.kind === "contact" ? "id" : "auth_user_id", ref.kind === "contact" ? ref.contactId : ref.principalId).maybeSingle();
11
+ if (error)
12
+ throw new PrivacyRefusal("PRIVACY_ASSESSMENT_UNAVAILABLE");
13
+ const principalId = ref.kind === "principal" ? ref.principalId : contact?.auth_user_id ?? null;
14
+ let principal = null;
15
+ let membership = null;
16
+ if (principalId) {
17
+ const result = await client.auth.admin.getUserById(principalId);
18
+ if (result.error) {
19
+ const failure = result.error;
20
+ if (!contact && (failure.status === 404 || failure.code === "user_not_found"))
21
+ return null;
22
+ throw new PrivacyRefusal("PRIVACY_ASSESSMENT_UNAVAILABLE");
23
+ }
24
+ if (result.data.user)
25
+ principal = { principal_id: result.data.user.id, email: result.data.user.email ?? null, email_confirmed_at: result.data.user.email_confirmed_at ?? null };
26
+ const read = await client.from("admin_users").select("id, membership_state, role, is_machine_actor").eq("id", principalId).maybeSingle();
27
+ if (read.error)
28
+ throw new PrivacyRefusal("PRIVACY_ASSESSMENT_UNAVAILABLE");
29
+ membership = read.data ? JSON.parse(JSON.stringify(read.data)) : null;
30
+ }
31
+ if (!contact && !principal)
32
+ return null;
33
+ return { contactId: contact?.id ?? null, principalId, identity: {
34
+ contact: contact ? JSON.parse(JSON.stringify(contact)) : null, principal, membership,
35
+ unavailable: ["contact.country", "identity.audit-history", "identity.invitation-history", "identity.erasure-receipts"],
36
+ } };
37
+ },
38
+ };
39
+ }
@@ -0,0 +1,29 @@
1
+ type Role = "admin" | "distributor";
2
+ declare const en: {
3
+ unauthorized: string;
4
+ invalidToken: string;
5
+ inviteForbidden: string;
6
+ emailRequired: string;
7
+ invalidRole: string;
8
+ alreadyAdded: string;
9
+ addFailed: (detail: string) => string;
10
+ notificationFailed: (detail: string) => string;
11
+ roleAddedDisabled: (email: string, role: Role) => string;
12
+ roleAddedSuppressed: (email: string, role: Role) => string;
13
+ roleAdded: (email: string, role: Role) => string;
14
+ inviteFailed: (detail: string) => string;
15
+ roleInsertFailed: (detail: string) => string;
16
+ invited: (email: string, role: Role) => string;
17
+ removeForbidden: string;
18
+ userIdRequired: string;
19
+ selfRemoveForbidden: string;
20
+ lastAdmin: string;
21
+ selfRevoke: string;
22
+ machineRevoke: string;
23
+ adminNotFound: string;
24
+ adminRequired: string;
25
+ revokeFailed: (detail: string) => string;
26
+ };
27
+ export type IdentityMessages = typeof en;
28
+ export declare const identityMessages: Readonly<Record<"en" | "pl", IdentityMessages>>;
29
+ export {};
@@ -0,0 +1,39 @@
1
+ const roleName = (role) => role === "admin" ? "Admin" : "Dystrybutor";
2
+ const en = {
3
+ unauthorized: "Unauthorized", invalidToken: "Invalid token",
4
+ inviteForbidden: "Only admins may invite users", emailRequired: "Email required",
5
+ invalidRole: "Invalid role", alreadyAdded: "This user is already added",
6
+ addFailed: (detail) => `Adding user failed: ${detail}`,
7
+ notificationFailed: (detail) => `Role added, but notification failed: ${detail}`,
8
+ roleAddedDisabled: (email, role) => `User ${email} already had an account; added role ${role}, but notification was disabled and not sent.`,
9
+ roleAddedSuppressed: (email, role) => `User ${email} already had an account; added role ${role}, but notification was suppressed and not sent.`,
10
+ roleAdded: (email, role) => `User ${email} already had an account; added role ${role} and sent a notification.`,
11
+ inviteFailed: (detail) => `Invitation failed: ${detail}`,
12
+ roleInsertFailed: (detail) => `Account created, but adding role failed: ${detail}`,
13
+ invited: (email, role) => `Invitation with role ${role} sent to ${email}`,
14
+ removeForbidden: "Only admins may revoke user access", userIdRequired: "User ID required",
15
+ selfRemoveForbidden: "You cannot revoke your own access",
16
+ lastAdmin: "At least one active human admin must remain.",
17
+ selfRevoke: "You cannot revoke your own access.",
18
+ machineRevoke: "Machine actors cannot be revoked through this route.",
19
+ adminNotFound: "Admin user not found.", adminRequired: "Admin role required.",
20
+ revokeFailed: (detail) => `Admin access revoke failed: ${detail}`,
21
+ };
22
+ export const identityMessages = {
23
+ en,
24
+ pl: {
25
+ ...en,
26
+ inviteForbidden: "Tylko admini mogą zapraszać użytkowników", emailRequired: "Brak emaila",
27
+ invalidRole: "Nieprawidłowa rola", alreadyAdded: "Ten użytkownik jest już dodany",
28
+ addFailed: (detail) => `Błąd dodawania: ${detail}`,
29
+ notificationFailed: (detail) => `Rola dodana, ale nie udało się wysłać powiadomienia: ${detail}`,
30
+ roleAddedDisabled: (email, role) => `Użytkownik ${email} już miał konto — dodano rolę ${roleName(role)}, ale powiadomienie było wyłączone i nie zostało wysłane.`,
31
+ roleAddedSuppressed: (email, role) => `Użytkownik ${email} już miał konto — dodano rolę ${roleName(role)}, ale wysłanie powiadomienia zostało zablokowane i nie zostało wysłane.`,
32
+ roleAdded: (email, role) => `Użytkownik ${email} już miał konto — dodano rolę ${roleName(role)} i wysłano powiadomienie.`,
33
+ inviteFailed: (detail) => `Błąd zaproszenia: ${detail}`,
34
+ roleInsertFailed: (detail) => `Konto utworzone, ale błąd dodawania roli: ${detail}`,
35
+ invited: (email, role) => `Zaproszenie z rolą ${roleName(role)} wysłane na ${email}`,
36
+ removeForbidden: "Tylko administratorzy mogą odbierać dostęp użytkownikom",
37
+ userIdRequired: "Brak ID użytkownika", selfRemoveForbidden: "Nie możesz odebrać sobie dostępu",
38
+ },
39
+ };
@@ -0,0 +1,45 @@
1
+ import type { IdentityJwtLibraryLoader } from "./jwtLibrary.js";
2
+ import type { IdentityVerifierPort, VerifiedPrincipal } from "./ports.js";
3
+ import type { OidcFailureCode } from "./contracts/oidc.js";
4
+ export interface OidcIdentityVerifierConfig {
5
+ issuer: string;
6
+ audience: string;
7
+ algorithms: readonly ("RS256" | "ES256")[];
8
+ maxTokenLifetimeSeconds: number;
9
+ requireSingleAudience?: boolean;
10
+ /** Conformance only: literal loopback addresses, never arbitrary HTTP hosts. */
11
+ allowLoopbackHttp?: boolean;
12
+ }
13
+ export type OidcVerificationResult = {
14
+ ok: true;
15
+ principal: VerifiedPrincipal;
16
+ expiresAt: string;
17
+ } | {
18
+ ok: false;
19
+ code: OidcFailureCode;
20
+ };
21
+ export interface OidcReadinessReport {
22
+ state: "ready" | "unknown" | "unsatisfied";
23
+ discovery: {
24
+ reachable: boolean;
25
+ valid: boolean;
26
+ checkedAt: string | null;
27
+ expiresAt: string | null;
28
+ };
29
+ jwks: {
30
+ loaded: boolean;
31
+ checkedAt: string | null;
32
+ expiresAt: string | null;
33
+ };
34
+ code?: OidcFailureCode;
35
+ }
36
+ export interface OidcIdentityVerifier extends IdentityVerifierPort {
37
+ verifyToken(token: string): Promise<OidcVerificationResult>;
38
+ readiness(): Promise<OidcReadinessReport>;
39
+ }
40
+ /** One binding per host/configuration. Imports and construction perform no I/O or JWT loading. */
41
+ export declare function createOidcIdentityVerifier(config: OidcIdentityVerifierConfig, dependencies: {
42
+ loadJwt: IdentityJwtLibraryLoader;
43
+ fetch?: typeof globalThis.fetch;
44
+ now?: () => number;
45
+ }): OidcIdentityVerifier;