@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.
- package/CHANGELOG.md +40 -0
- package/README.md +350 -3
- package/dist/identity/adminAuthBinding.d.ts +28 -0
- package/dist/identity/adminAuthBinding.js +66 -0
- package/dist/identity/adminAuthorization.d.ts +34 -0
- package/dist/identity/adminAuthorization.js +72 -0
- package/dist/identity/adminContracts.d.ts +70 -0
- package/dist/identity/adminContracts.js +43 -0
- package/dist/identity/adminMagicLinkHandler.d.ts +39 -0
- package/dist/identity/adminMagicLinkHandler.js +94 -0
- package/dist/identity/adminMeHandler.d.ts +15 -0
- package/dist/identity/adminMeHandler.js +49 -0
- package/dist/identity/adminPreflight.d.ts +16 -0
- package/dist/identity/adminPreflight.js +38 -0
- package/dist/identity/adminUserActionPorts.d.ts +31 -0
- package/dist/identity/adminUserActionPorts.js +51 -0
- package/dist/identity/adminUserHandlers.d.ts +38 -0
- package/dist/identity/adminUserHandlers.js +140 -0
- package/dist/identity/authVerifyUrl.d.ts +6 -0
- package/dist/identity/authVerifyUrl.js +54 -0
- package/dist/identity/contracts/contracts.d.ts +8 -0
- package/dist/identity/contracts/contracts.js +2 -0
- package/dist/identity/contracts/emailTrust.d.ts +3 -0
- package/dist/identity/contracts/emailTrust.js +4 -0
- package/dist/identity/contracts/index.d.ts +8 -0
- package/dist/identity/contracts/index.js +8 -0
- package/dist/identity/contracts/oidc.d.ts +3 -0
- package/dist/identity/contracts/oidc.js +9 -0
- package/dist/identity/contracts/operatorAuth.d.ts +35 -0
- package/dist/identity/contracts/operatorAuth.js +53 -0
- package/dist/identity/contracts/operatorInvitations.d.ts +37 -0
- package/dist/identity/contracts/operatorInvitations.js +1 -0
- package/dist/identity/contracts/ports.d.ts +22 -0
- package/dist/identity/contracts/ports.js +1 -0
- package/dist/identity/contracts/privacy.d.ts +62 -0
- package/dist/identity/contracts/privacy.js +99 -0
- package/dist/identity/contracts/providerList.d.ts +3 -0
- package/dist/identity/contracts/providerList.js +11 -0
- package/dist/identity/contracts/types.d.ts +16 -0
- package/dist/identity/contracts/types.js +4 -0
- package/dist/identity/contribution.d.ts +8 -0
- package/dist/identity/contribution.js +22 -0
- package/dist/identity/customerAuth.d.ts +8 -0
- package/dist/identity/customerAuth.js +1 -0
- package/dist/identity/customerAuthorization.d.ts +5 -0
- package/dist/identity/customerAuthorization.js +22 -0
- package/dist/identity/customerContracts.d.ts +42 -0
- package/dist/identity/customerContracts.js +43 -0
- package/dist/identity/customerIdentityBinding.d.ts +44 -0
- package/dist/identity/customerIdentityBinding.js +60 -0
- package/dist/identity/customerIdentityVerifier.d.ts +8 -0
- package/dist/identity/customerIdentityVerifier.js +101 -0
- package/dist/identity/customerMagicLinkHandler.d.ts +38 -0
- package/dist/identity/customerMagicLinkHandler.js +115 -0
- package/dist/identity/customerMeHandler.d.ts +11 -0
- package/dist/identity/customerMeHandler.js +46 -0
- package/dist/identity/customerPorts.d.ts +4 -0
- package/dist/identity/customerPorts.js +1 -0
- package/dist/identity/customerSessionBinding.d.ts +38 -0
- package/dist/identity/customerSessionBinding.js +80 -0
- package/dist/identity/customerSessionIssuer.d.ts +10 -0
- package/dist/identity/customerSessionIssuer.js +57 -0
- package/dist/identity/customerVerifyOtpHandler.d.ts +8 -0
- package/dist/identity/customerVerifyOtpHandler.js +41 -0
- package/dist/identity/directOperatorInvitationStore.d.ts +19 -0
- package/dist/identity/directOperatorInvitationStore.js +193 -0
- package/dist/identity/emailTrustPolicy.d.ts +14 -0
- package/dist/identity/emailTrustPolicy.js +16 -0
- package/dist/identity/identityPersonalDataContributor.d.ts +7 -0
- package/dist/identity/identityPersonalDataContributor.js +46 -0
- package/dist/identity/identityReadiness.d.ts +10 -0
- package/dist/identity/identityReadiness.js +25 -0
- package/dist/identity/index.d.ts +67 -0
- package/dist/identity/index.js +66 -0
- package/dist/identity/inviteAdminUserUseCase.d.ts +89 -0
- package/dist/identity/inviteAdminUserUseCase.js +75 -0
- package/dist/identity/jwtLibrary.d.ts +54 -0
- package/dist/identity/jwtLibrary.js +1 -0
- package/dist/identity/machineActorWriteFence.d.ts +4 -0
- package/dist/identity/machineActorWriteFence.js +13 -0
- package/dist/identity/magicLinkPorts.d.ts +28 -0
- package/dist/identity/magicLinkPorts.js +1 -0
- package/dist/identity/managedAccountLinkStore.d.ts +2 -0
- package/dist/identity/managedAccountLinkStore.js +93 -0
- package/dist/identity/managedAdminAuthVerifier.d.ts +7 -0
- package/dist/identity/managedAdminAuthVerifier.js +27 -0
- package/dist/identity/managedAdminUserGateways.d.ts +123 -0
- package/dist/identity/managedAdminUserGateways.js +127 -0
- package/dist/identity/managedClient.d.ts +90 -0
- package/dist/identity/managedClient.js +10 -0
- package/dist/identity/managedClientAuthLink.d.ts +3 -0
- package/dist/identity/managedClientAuthLink.js +115 -0
- package/dist/identity/managedCustomerMe.d.ts +13 -0
- package/dist/identity/managedCustomerMe.js +30 -0
- package/dist/identity/managedEmailTrustPolicy.d.ts +11 -0
- package/dist/identity/managedEmailTrustPolicy.js +105 -0
- package/dist/identity/managedIdentityVerifier.d.ts +2 -0
- package/dist/identity/managedIdentityVerifier.js +16 -0
- package/dist/identity/managedPrivacy.d.ts +4 -0
- package/dist/identity/managedPrivacy.js +39 -0
- package/dist/identity/messages.d.ts +29 -0
- package/dist/identity/messages.js +39 -0
- package/dist/identity/oidcIdentityVerifier.d.ts +45 -0
- package/dist/identity/oidcIdentityVerifier.js +309 -0
- package/dist/identity/oidcPrincipal.d.ts +2 -0
- package/dist/identity/oidcPrincipal.js +10 -0
- package/dist/identity/operatorAdminAuth.d.ts +6 -0
- package/dist/identity/operatorAdminAuth.js +13 -0
- package/dist/identity/operatorAuthHandlers.d.ts +18 -0
- package/dist/identity/operatorAuthHandlers.js +71 -0
- package/dist/identity/operatorIdentityVerifier.d.ts +8 -0
- package/dist/identity/operatorIdentityVerifier.js +19 -0
- package/dist/identity/operatorInvitationLifecycle.d.ts +7 -0
- package/dist/identity/operatorInvitationLifecycle.js +15 -0
- package/dist/identity/operatorInvitationPorts.d.ts +31 -0
- package/dist/identity/operatorInvitationPorts.js +1 -0
- package/dist/identity/operatorPorts.d.ts +37 -0
- package/dist/identity/operatorPorts.js +1 -0
- package/dist/identity/operatorSession.d.ts +13 -0
- package/dist/identity/operatorSession.js +31 -0
- package/dist/identity/operatorTokenAdminAuth.d.ts +15 -0
- package/dist/identity/operatorTokenAdminAuth.js +50 -0
- package/dist/identity/personalDataRegistry.d.ts +4 -0
- package/dist/identity/personalDataRegistry.js +11 -0
- package/dist/identity/platformAdminAuth.d.ts +15 -0
- package/dist/identity/platformAdminAuth.js +1 -0
- package/dist/identity/ports.d.ts +127 -0
- package/dist/identity/ports.js +38 -0
- package/dist/identity/postgresAccountLinkStore.d.ts +7 -0
- package/dist/identity/postgresAccountLinkStore.js +52 -0
- package/dist/identity/postgresCustomerMe.d.ts +3 -0
- package/dist/identity/postgresCustomerMe.js +21 -0
- package/dist/identity/postgresCustomerRecovery.d.ts +11 -0
- package/dist/identity/postgresCustomerRecovery.js +57 -0
- package/dist/identity/postgresOperatorIdentity.d.ts +5 -0
- package/dist/identity/postgresOperatorIdentity.js +137 -0
- package/dist/identity/postgresPrivacy.d.ts +7 -0
- package/dist/identity/postgresPrivacy.js +126 -0
- package/dist/identity/privacyHandlers.d.ts +10 -0
- package/dist/identity/privacyHandlers.js +35 -0
- package/dist/identity/privacyPorts.d.ts +72 -0
- package/dist/identity/privacyPorts.js +8 -0
- package/dist/identity/privacyUseCases.d.ts +5 -0
- package/dist/identity/privacyUseCases.js +55 -0
- package/dist/identity/reconcileAccountForPrincipal.d.ts +20 -0
- package/dist/identity/reconcileAccountForPrincipal.js +66 -0
- package/dist/identity/reconcileAccountHandler.d.ts +11 -0
- package/dist/identity/reconcileAccountHandler.js +30 -0
- package/dist/identity/removeAdminUserUseCase.d.ts +31 -0
- package/dist/identity/removeAdminUserUseCase.js +60 -0
- package/dist/identity/response.d.ts +3 -0
- package/dist/identity/response.js +2 -0
- package/dist/identity/sql.d.ts +44 -0
- package/dist/identity/sql.js +10 -0
- package/package.json +12 -2
- package/release-gates.json +22 -0
- package/sql/20260810180001_self_host_actor_auth.sql +40 -0
- package/sql/20260810204057_customer_identity_read.sql +31 -0
- package/sql/20260814090000_customer_selfservice_recovery.sql +477 -0
- package/sql/20260901143000_admin_membership_authority_foundation.sql +234 -0
- package/sql/20261018120000_identity_operator_invitations.sql +42 -0
- package/sql/20261018180000_identity_operator_challenges.sql +56 -0
- package/sql/20261018190000_identity_privacy_erasures.sql +15 -0
- package/src/identity/adminAuthBinding.ts +76 -0
- package/src/identity/adminAuthorization.ts +106 -0
- package/src/identity/adminContracts.ts +52 -0
- package/src/identity/adminMagicLinkHandler.ts +132 -0
- package/src/identity/adminMeHandler.ts +64 -0
- package/src/identity/adminPreflight.ts +46 -0
- package/src/identity/adminUserActionPorts.ts +78 -0
- package/src/identity/adminUserHandlers.ts +174 -0
- package/src/identity/authVerifyUrl.ts +56 -0
- package/src/identity/contracts/contracts.ts +15 -0
- package/src/identity/contracts/emailTrust.ts +5 -0
- package/src/identity/contracts/index.ts +8 -0
- package/src/identity/contracts/oidc.ts +10 -0
- package/src/identity/contracts/operatorAuth.ts +61 -0
- package/src/identity/contracts/operatorInvitations.ts +32 -0
- package/src/identity/contracts/ports.ts +25 -0
- package/src/identity/contracts/privacy.ts +86 -0
- package/src/identity/contracts/providerList.ts +13 -0
- package/src/identity/contracts/types.ts +25 -0
- package/src/identity/contribution.ts +29 -0
- package/src/identity/customerAuth.ts +3 -0
- package/src/identity/customerAuthorization.ts +38 -0
- package/src/identity/customerContracts.ts +52 -0
- package/src/identity/customerIdentityBinding.ts +125 -0
- package/src/identity/customerIdentityVerifier.ts +141 -0
- package/src/identity/customerMagicLinkHandler.ts +171 -0
- package/src/identity/customerMeHandler.ts +64 -0
- package/src/identity/customerPorts.ts +2 -0
- package/src/identity/customerSessionBinding.ts +114 -0
- package/src/identity/customerSessionIssuer.ts +71 -0
- package/src/identity/customerVerifyOtpHandler.ts +44 -0
- package/src/identity/directOperatorInvitationStore.ts +178 -0
- package/src/identity/emailTrustPolicy.ts +16 -0
- package/src/identity/identityPersonalDataContributor.ts +48 -0
- package/src/identity/identityReadiness.ts +26 -0
- package/src/identity/index.ts +69 -0
- package/src/identity/inviteAdminUserUseCase.ts +134 -0
- package/src/identity/jwtLibrary.ts +24 -0
- package/src/identity/machineActorWriteFence.ts +19 -0
- package/src/identity/magicLinkPorts.ts +7 -0
- package/src/identity/managedAccountLinkStore.ts +105 -0
- package/src/identity/managedAdminAuthVerifier.ts +40 -0
- package/src/identity/managedAdminUserGateways.ts +164 -0
- package/src/identity/managedClient.ts +58 -0
- package/src/identity/managedClientAuthLink.ts +120 -0
- package/src/identity/managedCustomerMe.ts +52 -0
- package/src/identity/managedEmailTrustPolicy.ts +90 -0
- package/src/identity/managedIdentityVerifier.ts +20 -0
- package/src/identity/managedPrivacy.ts +37 -0
- package/src/identity/messages.ts +41 -0
- package/src/identity/oidcIdentityVerifier.ts +229 -0
- package/src/identity/oidcPrincipal.ts +10 -0
- package/src/identity/operatorAdminAuth.ts +14 -0
- package/src/identity/operatorAuthHandlers.ts +52 -0
- package/src/identity/operatorIdentityVerifier.ts +19 -0
- package/src/identity/operatorInvitationLifecycle.ts +14 -0
- package/src/identity/operatorInvitationPorts.ts +11 -0
- package/src/identity/operatorPorts.ts +11 -0
- package/src/identity/operatorSession.ts +30 -0
- package/src/identity/operatorTokenAdminAuth.ts +79 -0
- package/src/identity/personalDataRegistry.ts +8 -0
- package/src/identity/platformAdminAuth.ts +7 -0
- package/src/identity/ports.ts +158 -0
- package/src/identity/postgresAccountLinkStore.ts +49 -0
- package/src/identity/postgresCustomerMe.ts +33 -0
- package/src/identity/postgresCustomerRecovery.ts +72 -0
- package/src/identity/postgresOperatorIdentity.ts +123 -0
- package/src/identity/postgresPrivacy.ts +117 -0
- package/src/identity/privacyHandlers.ts +33 -0
- package/src/identity/privacyPorts.ts +52 -0
- package/src/identity/privacyUseCases.ts +48 -0
- package/src/identity/reconcileAccountForPrincipal.ts +99 -0
- package/src/identity/reconcileAccountHandler.ts +36 -0
- package/src/identity/removeAdminUserUseCase.ts +85 -0
- package/src/identity/response.ts +3 -0
- package/src/identity/sql.ts +10 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
function authVerifyType(actionType) {
|
|
2
|
+
return actionType === "email_change_current" || actionType === "email_change_new"
|
|
3
|
+
? "email_change"
|
|
4
|
+
: actionType;
|
|
5
|
+
}
|
|
6
|
+
function qpSafeTokenValue(token) {
|
|
7
|
+
if (token.length === 0)
|
|
8
|
+
return token;
|
|
9
|
+
return `%${token.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}${encodeURIComponent(token.slice(1))}`;
|
|
10
|
+
}
|
|
11
|
+
export function buildAuthVerifyUrl(authServiceUrl, actionType, tokenHash, redirectTo) {
|
|
12
|
+
const params = new URLSearchParams({ type: authVerifyType(actionType), redirect_to: redirectTo });
|
|
13
|
+
return `${authServiceUrl.replace(/\/+$/, "")}/auth/v1/verify?token=${qpSafeTokenValue(tokenHash)}&${params.toString()}`;
|
|
14
|
+
}
|
|
15
|
+
/** Only configured HTTP(S) origins authorize the default magic-link destination. */
|
|
16
|
+
export function createMagicLinkOriginResolver(configuredOrigins) {
|
|
17
|
+
const origins = configuredOrigins.map(normalizeOrigin);
|
|
18
|
+
const origin = origins.length > 0 && origins.every((value) => value !== null)
|
|
19
|
+
? (origins[0] ?? null)
|
|
20
|
+
: null;
|
|
21
|
+
return () => origin;
|
|
22
|
+
}
|
|
23
|
+
function normalizeOrigin(value) {
|
|
24
|
+
const candidate = value.trim();
|
|
25
|
+
// URL parsing alone accepts paths, credentials and repaired backslashes.
|
|
26
|
+
if (!/^https?:\/\/[^/?#\\\s]+\/?$/i.test(candidate))
|
|
27
|
+
return null;
|
|
28
|
+
try {
|
|
29
|
+
const url = new URL(candidate);
|
|
30
|
+
if (url.username || url.password || url.pathname !== "/" || url.search || url.hash)
|
|
31
|
+
return null;
|
|
32
|
+
return url.origin;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Invalid return targets use the callback's default landing page. */
|
|
39
|
+
export function normalizeMagicLinkReturnPath(value, origin) {
|
|
40
|
+
if (!value || !value.startsWith("/") || value.startsWith("//"))
|
|
41
|
+
return undefined;
|
|
42
|
+
// Reject encoded separators, controls and nested escapes before any later decoding.
|
|
43
|
+
if (value.includes("\\") || Array.from(value).some((character) => character.charCodeAt(0) <= 0x20 || character.charCodeAt(0) === 0x7f) || /%(?:2f|5c|0[0-9a-f]|1[0-9a-f]|7f|25)/i.test(value))
|
|
44
|
+
return undefined;
|
|
45
|
+
try {
|
|
46
|
+
const url = new URL(value, origin);
|
|
47
|
+
if (url.origin !== new URL(origin).origin || url.pathname.startsWith("//"))
|
|
48
|
+
return undefined;
|
|
49
|
+
return `${url.pathname}${url.search}${url.hash}`;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { EmailTrustFailureCode } from "./emailTrust.js";
|
|
2
|
+
export declare const AUTH_RECONCILE_CONTRACT_VERSION: "auth.reconcile.v1";
|
|
3
|
+
export interface AuthReconcileResponse {
|
|
4
|
+
accountId: string;
|
|
5
|
+
created: boolean;
|
|
6
|
+
linked: boolean;
|
|
7
|
+
}
|
|
8
|
+
export type AuthReconcileErrorCode = EmailTrustFailureCode | "NO_VERIFIED_EMAIL" | "RESERVED_PRINCIPAL_REFUSED" | "ACCOUNT_LINK_CONFLICT" | "EMAIL_OWNED_BY_OTHER_PRINCIPAL" | "PRINCIPAL_EMAIL_MISMATCH";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from "./types.js";
|
|
2
|
+
export * from "./ports.js";
|
|
3
|
+
export * from "./providerList.js";
|
|
4
|
+
export * from "./contracts.js";
|
|
5
|
+
export * from "./emailTrust.js";
|
|
6
|
+
export * from "./operatorInvitations.js";
|
|
7
|
+
export * from "./operatorAuth.js";
|
|
8
|
+
export * from "./privacy.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from "./types.js";
|
|
2
|
+
export * from "./ports.js";
|
|
3
|
+
export * from "./providerList.js";
|
|
4
|
+
export * from "./contracts.js";
|
|
5
|
+
export * from "./emailTrust.js";
|
|
6
|
+
export * from "./operatorInvitations.js";
|
|
7
|
+
export * from "./operatorAuth.js";
|
|
8
|
+
export * from "./privacy.js";
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Credential and dependency refusals; no token or transport details are exposed. */
|
|
2
|
+
export declare const OIDC_FAILURE_CODES: readonly ["OIDC_CONFIGURATION_INVALID", "OIDC_DISCOVERY_UNAVAILABLE", "OIDC_DISCOVERY_INVALID", "OIDC_JWKS_UNAVAILABLE", "OIDC_JWKS_INVALID", "OIDC_JWKS_REFRESH_RATE_LIMITED", "OIDC_JWT_LIBRARY_UNAVAILABLE", "OIDC_TOKEN_INVALID", "OIDC_KID_UNKNOWN", "OIDC_ISSUER_MISMATCH", "OIDC_AUDIENCE_MISMATCH", "OIDC_TOKEN_EXPIRED", "OIDC_TOKEN_NOT_ACTIVE", "OIDC_TOKEN_LIFETIME_INVALID", "OIDC_SUBJECT_INVALID", "OIDC_EMAIL_INVALID", "OIDC_EMAIL_UNVERIFIED", "OIDC_NATIVE_SESSION_REQUIRED"];
|
|
3
|
+
export type OidcFailureCode = typeof OIDC_FAILURE_CODES[number];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Credential and dependency refusals; no token or transport details are exposed. */
|
|
2
|
+
export const OIDC_FAILURE_CODES = [
|
|
3
|
+
"OIDC_CONFIGURATION_INVALID", "OIDC_DISCOVERY_UNAVAILABLE", "OIDC_DISCOVERY_INVALID",
|
|
4
|
+
"OIDC_JWKS_UNAVAILABLE", "OIDC_JWKS_INVALID", "OIDC_JWKS_REFRESH_RATE_LIMITED",
|
|
5
|
+
"OIDC_JWT_LIBRARY_UNAVAILABLE", "OIDC_TOKEN_INVALID", "OIDC_KID_UNKNOWN",
|
|
6
|
+
"OIDC_ISSUER_MISMATCH", "OIDC_AUDIENCE_MISMATCH", "OIDC_TOKEN_EXPIRED",
|
|
7
|
+
"OIDC_TOKEN_NOT_ACTIVE", "OIDC_TOKEN_LIFETIME_INVALID", "OIDC_SUBJECT_INVALID",
|
|
8
|
+
"OIDC_EMAIL_INVALID", "OIDC_EMAIL_UNVERIFIED", "OIDC_NATIVE_SESSION_REQUIRED",
|
|
9
|
+
];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface OperatorSession {
|
|
2
|
+
accessToken: string;
|
|
3
|
+
expiresAt: string;
|
|
4
|
+
user: {
|
|
5
|
+
id: string;
|
|
6
|
+
email: string;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export interface OperatorChallengeRequest {
|
|
10
|
+
email: string;
|
|
11
|
+
}
|
|
12
|
+
export interface OperatorChallengeVerification {
|
|
13
|
+
token: string;
|
|
14
|
+
}
|
|
15
|
+
export interface OperatorBrowserAuthPort {
|
|
16
|
+
getSession(): Promise<OperatorSession | null>;
|
|
17
|
+
requestChallenge(email: string): Promise<void>;
|
|
18
|
+
verifyChallenge(token: string): Promise<OperatorSession>;
|
|
19
|
+
signOut(): Promise<void>;
|
|
20
|
+
onAuthStateChange(listener: (session: OperatorSession | null) => void): () => void;
|
|
21
|
+
}
|
|
22
|
+
/** Strict parsers keep the contracts entry independent of the server validator. */
|
|
23
|
+
export interface OperatorSchema<T> {
|
|
24
|
+
parse(value: unknown): T;
|
|
25
|
+
safeParse(value: unknown): {
|
|
26
|
+
success: true;
|
|
27
|
+
data: T;
|
|
28
|
+
} | {
|
|
29
|
+
success: false;
|
|
30
|
+
error: Error;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export declare const operatorChallengeRequestSchema: OperatorSchema<OperatorChallengeRequest>;
|
|
34
|
+
export declare const operatorChallengeVerificationSchema: OperatorSchema<OperatorChallengeVerification>;
|
|
35
|
+
export declare const operatorSessionSchema: OperatorSchema<OperatorSession>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
function schema(parse) {
|
|
2
|
+
return { parse, safeParse(value) {
|
|
3
|
+
try {
|
|
4
|
+
return { success: true, data: parse(value) };
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
return { success: false, error: new Error("operator_contract_invalid") };
|
|
8
|
+
}
|
|
9
|
+
} };
|
|
10
|
+
}
|
|
11
|
+
function fields(value, keys) {
|
|
12
|
+
if (!value || typeof value !== "object" || Array.isArray(value)
|
|
13
|
+
|| Object.keys(value).length !== keys.length || !keys.every(key => Object.hasOwn(value, key)))
|
|
14
|
+
throw new Error("operator_contract_invalid");
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
function email(value) {
|
|
18
|
+
if (typeof value !== "string" || value.length > 320 || !/^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$/i.test(value))
|
|
19
|
+
throw new Error("operator_email_invalid");
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
export const operatorChallengeRequestSchema = schema(value => {
|
|
23
|
+
const row = fields(value, ["email"]);
|
|
24
|
+
if (typeof row.email !== "string")
|
|
25
|
+
throw new Error("operator_email_invalid");
|
|
26
|
+
return { email: email(row.email.trim().toLowerCase()) };
|
|
27
|
+
});
|
|
28
|
+
export const operatorChallengeVerificationSchema = schema(value => {
|
|
29
|
+
const row = fields(value, ["token"]);
|
|
30
|
+
if (typeof row.token !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(row.token))
|
|
31
|
+
throw new Error("operator_token_invalid");
|
|
32
|
+
return { token: row.token };
|
|
33
|
+
});
|
|
34
|
+
function timestamp(value) {
|
|
35
|
+
if (typeof value !== "string")
|
|
36
|
+
return false;
|
|
37
|
+
const parts = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)(?:\.\d+)?(?:Z|[+-]\d\d:\d\d)$/.exec(value);
|
|
38
|
+
if (!parts || !Number.isFinite(Date.parse(value)))
|
|
39
|
+
return false;
|
|
40
|
+
const [, year, month, day, hour, minute, second] = parts.map(Number);
|
|
41
|
+
return year >= 1970 && month >= 1 && month <= 12 && day >= 1
|
|
42
|
+
&& day <= new Date(Date.UTC(year, month, 0)).getUTCDate()
|
|
43
|
+
&& hour <= 23 && minute <= 59 && second <= 59;
|
|
44
|
+
}
|
|
45
|
+
export const operatorSessionSchema = schema(value => {
|
|
46
|
+
const row = fields(value, ["accessToken", "expiresAt", "user"]);
|
|
47
|
+
const user = fields(row.user, ["id", "email"]);
|
|
48
|
+
if (typeof row.accessToken !== "string" || !row.accessToken || row.accessToken.length > 8192
|
|
49
|
+
|| !timestamp(row.expiresAt)
|
|
50
|
+
|| typeof user.id !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(user.id))
|
|
51
|
+
throw new Error("operator_session_invalid");
|
|
52
|
+
return { accessToken: row.accessToken, expiresAt: row.expiresAt, user: { id: user.id, email: email(user.email) } };
|
|
53
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Pending invitations confer no authority; only accepted native memberships do. */
|
|
2
|
+
export type OperatorRole = "admin" | "distributor";
|
|
3
|
+
export type OperatorMembershipState = "invited" | "active" | "expired" | "revoked";
|
|
4
|
+
export type OperatorInvitationOutcome = "INVITATION_CREATED" | "ALREADY_INVITED" | "INVITATION_REISSUED";
|
|
5
|
+
export type OperatorInvitationConflict = "MEMBERSHIP_ALREADY_ACTIVE" | "INVITATION_ROLE_CONFLICT" | "IDENTITY_CONFLICT" | "MANAGED_REINVITE_UNSUPPORTED";
|
|
6
|
+
export interface OperatorInvitation {
|
|
7
|
+
principalId: string;
|
|
8
|
+
invitationId: string;
|
|
9
|
+
membershipState: "invited";
|
|
10
|
+
role: OperatorRole;
|
|
11
|
+
invitedAt: string;
|
|
12
|
+
expiresAt: string;
|
|
13
|
+
}
|
|
14
|
+
export interface OperatorInvitationResult extends OperatorInvitation {
|
|
15
|
+
code: OperatorInvitationOutcome;
|
|
16
|
+
message?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface OperatorMembershipSnapshot {
|
|
19
|
+
principalId: string;
|
|
20
|
+
membershipState: OperatorMembershipState;
|
|
21
|
+
role: OperatorRole;
|
|
22
|
+
}
|
|
23
|
+
export type OperatorInvitationAcceptance = {
|
|
24
|
+
code: "ACTIVATED" | "ALREADY_ACTIVE";
|
|
25
|
+
role: OperatorRole;
|
|
26
|
+
} | {
|
|
27
|
+
code: "INVITATION_EXPIRED" | "INVITATION_REVOKED" | "NO_INVITATION";
|
|
28
|
+
};
|
|
29
|
+
/** Managed invitations activate membership immediately; no pending attempt is stored. */
|
|
30
|
+
export interface ManagedOperatorMembershipResult {
|
|
31
|
+
principalId: string;
|
|
32
|
+
membershipState: "active";
|
|
33
|
+
role: OperatorRole;
|
|
34
|
+
code: "MEMBERSHIP_CREATED" | "ALREADY_ACTIVE";
|
|
35
|
+
message?: string;
|
|
36
|
+
}
|
|
37
|
+
export type OperatorInviteResult = OperatorInvitationResult | ManagedOperatorMembershipResult;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AuthActionResult, AuthSession, AuthStateListener, OAuthProviderKind, Unsubscribe } from "./types.js";
|
|
2
|
+
export interface CustomerAuthPort {
|
|
3
|
+
getSession(): Promise<AuthSession | null>;
|
|
4
|
+
onAuthStateChange(listener: AuthStateListener): Unsubscribe;
|
|
5
|
+
signInWithOtp(input: {
|
|
6
|
+
email: string;
|
|
7
|
+
locale: string;
|
|
8
|
+
}): Promise<AuthActionResult>;
|
|
9
|
+
verifyOtpCode(input: {
|
|
10
|
+
email: string;
|
|
11
|
+
token: string;
|
|
12
|
+
}): Promise<AuthActionResult>;
|
|
13
|
+
signInWithOAuth(input: {
|
|
14
|
+
provider: OAuthProviderKind;
|
|
15
|
+
redirectTo: string;
|
|
16
|
+
}): Promise<AuthActionResult>;
|
|
17
|
+
linkIdentity(input: {
|
|
18
|
+
provider: OAuthProviderKind;
|
|
19
|
+
redirectTo: string;
|
|
20
|
+
}): Promise<AuthActionResult>;
|
|
21
|
+
signOut(): Promise<void>;
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { OperatorSchema } from "./operatorAuth.js";
|
|
2
|
+
export type PrivacySubjectRef = {
|
|
3
|
+
kind: "contact";
|
|
4
|
+
contactId: string;
|
|
5
|
+
} | {
|
|
6
|
+
kind: "principal";
|
|
7
|
+
principalId: string;
|
|
8
|
+
};
|
|
9
|
+
export type PrivacyJson = null | boolean | number | string | PrivacyJson[] | {
|
|
10
|
+
[key: string]: PrivacyJson;
|
|
11
|
+
};
|
|
12
|
+
export interface PersonalDataObligation {
|
|
13
|
+
owner: string;
|
|
14
|
+
resource: string;
|
|
15
|
+
disposition: "erase" | "retain" | "unresolved";
|
|
16
|
+
reasonCode: string;
|
|
17
|
+
remote: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface PersonalDataDocument {
|
|
20
|
+
contributorId: string;
|
|
21
|
+
version: string;
|
|
22
|
+
data: PrivacyJson;
|
|
23
|
+
coverage: {
|
|
24
|
+
complete: boolean;
|
|
25
|
+
included: string[];
|
|
26
|
+
unavailable: string[];
|
|
27
|
+
};
|
|
28
|
+
obligations: PersonalDataObligation[];
|
|
29
|
+
}
|
|
30
|
+
export interface ErasureReceipt {
|
|
31
|
+
version: "identity.erasure.v1";
|
|
32
|
+
erasureId: string;
|
|
33
|
+
completedAt: string;
|
|
34
|
+
outcome: "ERASED" | "ALREADY_ERASED";
|
|
35
|
+
obligations: PersonalDataObligation[];
|
|
36
|
+
}
|
|
37
|
+
export interface PrivacyEraseRequest {
|
|
38
|
+
subject: PrivacySubjectRef;
|
|
39
|
+
idempotencyKey: string;
|
|
40
|
+
}
|
|
41
|
+
export interface PrivacyExport {
|
|
42
|
+
format: "personal-data.v1";
|
|
43
|
+
generatedAt: string;
|
|
44
|
+
subject: PrivacySubjectRef;
|
|
45
|
+
documents: PersonalDataDocument[];
|
|
46
|
+
missingContributors: string[];
|
|
47
|
+
erasure: ErasureReceipt | null;
|
|
48
|
+
}
|
|
49
|
+
export declare const privacySubjectSchema: OperatorSchema<PrivacySubjectRef>;
|
|
50
|
+
export declare const personalDataObligationSchema: OperatorSchema<PersonalDataObligation>;
|
|
51
|
+
export declare const personalDataDocumentSchema: OperatorSchema<PersonalDataDocument>;
|
|
52
|
+
export declare const erasureReceiptSchema: OperatorSchema<ErasureReceipt>;
|
|
53
|
+
export declare const privacyExportRequestSchema: OperatorSchema<{
|
|
54
|
+
subject: PrivacySubjectRef;
|
|
55
|
+
}>;
|
|
56
|
+
export declare const privacyEraseRequestSchema: OperatorSchema<PrivacyEraseRequest>;
|
|
57
|
+
export declare const privacyExportResponseSchema: OperatorSchema<PrivacyExport>;
|
|
58
|
+
export declare const privacyEraseResponseSchema: OperatorSchema<ErasureReceipt>;
|
|
59
|
+
declare const refusalCodes: readonly ["PRIVACY_SUBJECT_NOT_FOUND", "PRIVACY_CONTRIBUTOR_DUPLICATE", "PRIVACY_CONTRIBUTOR_MISSING", "PRIVACY_ASSESSMENT_UNAVAILABLE", "PRIVACY_OWNERSHIP_UNDECLARED", "PRIVACY_ACTIVE_SUBSCRIPTION", "PRIVACY_RESUMABLE_SUBSCRIPTION", "PRIVACY_PROTECTED_OPERATOR", "PRIVACY_IDEMPOTENCY_CONFLICT", "PRIVACY_SUBJECT_ERASED", "PRIVACY_STORE_PRIVILEGES_UNSAFE", "MANAGED_PRIVACY_ERASURE_UNAVAILABLE"];
|
|
60
|
+
export type PrivacyRefusalCode = typeof refusalCodes[number];
|
|
61
|
+
export declare const privacyRefusalCodeSchema: OperatorSchema<PrivacyRefusalCode>;
|
|
62
|
+
export {};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
function invalid() { throw new Error("privacy_contract_invalid"); }
|
|
2
|
+
function schema(parse) {
|
|
3
|
+
return { parse, safeParse(value) { try {
|
|
4
|
+
return { success: true, data: parse(value) };
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
return { success: false, error: new Error("privacy_contract_invalid") };
|
|
8
|
+
} } };
|
|
9
|
+
}
|
|
10
|
+
function fields(value, keys) {
|
|
11
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || Object.getPrototypeOf(value) !== Object.prototype
|
|
12
|
+
|| Object.keys(value).length !== keys.length || !keys.every(key => Object.hasOwn(value, key)))
|
|
13
|
+
invalid();
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
function text(value, max = 200) {
|
|
17
|
+
if (typeof value !== "string" || !value.length || value.length > max)
|
|
18
|
+
invalid();
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
function uuid(value) {
|
|
22
|
+
const result = text(value, 36);
|
|
23
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(result))
|
|
24
|
+
invalid();
|
|
25
|
+
return result.toLowerCase();
|
|
26
|
+
}
|
|
27
|
+
function timestamp(value) {
|
|
28
|
+
const result = text(value, 30);
|
|
29
|
+
if (!/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d{3})?Z$/.test(result) || !Number.isFinite(Date.parse(result))
|
|
30
|
+
|| new Date(result).toISOString() !== result.replace(/Z$/, result.length === 20 ? ".000Z" : "Z"))
|
|
31
|
+
invalid();
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
function list(value, parse) {
|
|
35
|
+
if (!Array.isArray(value) || value.length > 10000)
|
|
36
|
+
invalid();
|
|
37
|
+
return value.map(element => parse(element));
|
|
38
|
+
}
|
|
39
|
+
function boolean(value) { if (typeof value !== "boolean")
|
|
40
|
+
invalid(); return value; }
|
|
41
|
+
function choice(value, allowed) { if (!allowed.includes(value))
|
|
42
|
+
invalid(); return value; }
|
|
43
|
+
function json(value, depth = 0) {
|
|
44
|
+
if (depth > 32)
|
|
45
|
+
invalid();
|
|
46
|
+
if (value === null || typeof value === "boolean" || typeof value === "string") {
|
|
47
|
+
if (typeof value === "string" && value.length > 1000000)
|
|
48
|
+
invalid();
|
|
49
|
+
return value;
|
|
50
|
+
}
|
|
51
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
52
|
+
return value;
|
|
53
|
+
if (Array.isArray(value))
|
|
54
|
+
return list(value, element => json(element, depth + 1));
|
|
55
|
+
if (!value || typeof value !== "object" || Object.getPrototypeOf(value) !== Object.prototype)
|
|
56
|
+
invalid();
|
|
57
|
+
const forbidden = new Set(["__proto__", "constructor", "prototype", "password", "password_hash", "token_hash", "access_token", "refresh_token", "accessToken", "refreshToken", "secret", "credentials"]);
|
|
58
|
+
const entries = Object.entries(value);
|
|
59
|
+
if (entries.length > 10000 || entries.some(([key]) => forbidden.has(key)))
|
|
60
|
+
invalid();
|
|
61
|
+
return Object.fromEntries(entries.map(([key, child]) => [key, json(child, depth + 1)]));
|
|
62
|
+
}
|
|
63
|
+
export const privacySubjectSchema = schema(value => {
|
|
64
|
+
if (!value || typeof value !== "object" || !("kind" in value))
|
|
65
|
+
invalid();
|
|
66
|
+
if (value.kind === "contact") {
|
|
67
|
+
const row = fields(value, ["kind", "contactId"]);
|
|
68
|
+
return { kind: "contact", contactId: uuid(row.contactId) };
|
|
69
|
+
}
|
|
70
|
+
const row = fields(value, ["kind", "principalId"]);
|
|
71
|
+
return { kind: choice(row.kind, ["principal"]), principalId: uuid(row.principalId) };
|
|
72
|
+
});
|
|
73
|
+
export const personalDataObligationSchema = schema(value => {
|
|
74
|
+
const row = fields(value, ["owner", "resource", "disposition", "reasonCode", "remote"]);
|
|
75
|
+
return { owner: text(row.owner, 100), resource: text(row.resource), disposition: choice(row.disposition, ["erase", "retain", "unresolved"]), reasonCode: text(row.reasonCode, 100), remote: boolean(row.remote) };
|
|
76
|
+
});
|
|
77
|
+
export const personalDataDocumentSchema = schema(value => {
|
|
78
|
+
const row = fields(value, ["contributorId", "version", "data", "coverage", "obligations"]), coverage = fields(row.coverage, ["complete", "included", "unavailable"]);
|
|
79
|
+
const contributorId = text(row.contributorId, 100), version = text(row.version);
|
|
80
|
+
if (!/^[a-z][a-z0-9.-]{0,99}$/.test(contributorId) || !/^[a-z][a-z0-9.-]*\.v[1-9][0-9]*$/.test(version))
|
|
81
|
+
invalid();
|
|
82
|
+
return { contributorId, version, data: json(row.data), coverage: { complete: boolean(coverage.complete), included: list(coverage.included, text), unavailable: list(coverage.unavailable, text) }, obligations: list(row.obligations, personalDataObligationSchema.parse) };
|
|
83
|
+
});
|
|
84
|
+
export const erasureReceiptSchema = schema(value => {
|
|
85
|
+
const row = fields(value, ["version", "erasureId", "completedAt", "outcome", "obligations"]);
|
|
86
|
+
return { version: choice(row.version, ["identity.erasure.v1"]), erasureId: uuid(row.erasureId), completedAt: timestamp(row.completedAt), outcome: choice(row.outcome, ["ERASED", "ALREADY_ERASED"]), obligations: list(row.obligations, personalDataObligationSchema.parse) };
|
|
87
|
+
});
|
|
88
|
+
export const privacyExportRequestSchema = schema(value => ({ subject: privacySubjectSchema.parse(fields(value, ["subject"]).subject) }));
|
|
89
|
+
export const privacyEraseRequestSchema = schema(value => {
|
|
90
|
+
const row = fields(value, ["subject", "idempotencyKey"]);
|
|
91
|
+
return { subject: privacySubjectSchema.parse(row.subject), idempotencyKey: uuid(row.idempotencyKey) };
|
|
92
|
+
});
|
|
93
|
+
export const privacyExportResponseSchema = schema(value => {
|
|
94
|
+
const row = fields(value, ["format", "generatedAt", "subject", "documents", "missingContributors", "erasure"]);
|
|
95
|
+
return { format: choice(row.format, ["personal-data.v1"]), generatedAt: timestamp(row.generatedAt), subject: privacySubjectSchema.parse(row.subject), documents: list(row.documents, personalDataDocumentSchema.parse), missingContributors: list(row.missingContributors, text), erasure: row.erasure === null ? null : erasureReceiptSchema.parse(row.erasure) };
|
|
96
|
+
});
|
|
97
|
+
export const privacyEraseResponseSchema = erasureReceiptSchema;
|
|
98
|
+
const refusalCodes = ["PRIVACY_SUBJECT_NOT_FOUND", "PRIVACY_CONTRIBUTOR_DUPLICATE", "PRIVACY_CONTRIBUTOR_MISSING", "PRIVACY_ASSESSMENT_UNAVAILABLE", "PRIVACY_OWNERSHIP_UNDECLARED", "PRIVACY_ACTIVE_SUBSCRIPTION", "PRIVACY_RESUMABLE_SUBSCRIPTION", "PRIVACY_PROTECTED_OPERATOR", "PRIVACY_IDEMPOTENCY_CONFLICT", "PRIVACY_SUBJECT_ERASED", "PRIVACY_STORE_PRIVILEGES_UNSAFE", "MANAGED_PRIVACY_ERASURE_UNAVAILABLE"];
|
|
99
|
+
export const privacyRefusalCodeSchema = schema(value => choice(value, refusalCodes));
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { isOAuthProviderKind } from "./types.js";
|
|
2
|
+
export const DEFAULT_SOCIAL_PROVIDERS = ["google"];
|
|
3
|
+
export function parseSocialProviders(raw) {
|
|
4
|
+
if (raw === undefined)
|
|
5
|
+
return [...DEFAULT_SOCIAL_PROVIDERS];
|
|
6
|
+
const kinds = raw
|
|
7
|
+
.split(",")
|
|
8
|
+
.map((token) => token.trim().toLowerCase())
|
|
9
|
+
.filter((token) => isOAuthProviderKind(token));
|
|
10
|
+
return [...new Set(kinds)];
|
|
11
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const OAUTH_PROVIDER_KINDS: readonly ["google", "apple", "facebook"];
|
|
2
|
+
export type OAuthProviderKind = (typeof OAUTH_PROVIDER_KINDS)[number];
|
|
3
|
+
export declare function isOAuthProviderKind(value: string): value is OAuthProviderKind;
|
|
4
|
+
export interface AuthUser {
|
|
5
|
+
id: string;
|
|
6
|
+
email: string | null;
|
|
7
|
+
}
|
|
8
|
+
export interface AuthSession {
|
|
9
|
+
accessToken: string;
|
|
10
|
+
user: AuthUser;
|
|
11
|
+
}
|
|
12
|
+
export type AuthStateListener = (session: AuthSession | null) => void;
|
|
13
|
+
export type Unsubscribe = () => void;
|
|
14
|
+
export interface AuthActionResult {
|
|
15
|
+
error: string | null;
|
|
16
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Contribution, RequiredSchema } from "@openlup/core/readiness";
|
|
2
|
+
/** Host-selected schema inventory: managed and direct installations have different objects. */
|
|
3
|
+
export declare function createIdentityContribution(options: {
|
|
4
|
+
version: string;
|
|
5
|
+
requiredSchema: RequiredSchema;
|
|
6
|
+
routes?: Contribution["routes"];
|
|
7
|
+
requiredPorts?: readonly string[];
|
|
8
|
+
}): Contribution;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Host-selected schema inventory: managed and direct installations have different objects. */
|
|
2
|
+
export function createIdentityContribution(options) {
|
|
3
|
+
const routes = options.routes ?? [];
|
|
4
|
+
return {
|
|
5
|
+
manifest: {
|
|
6
|
+
name: "@openlup/platform", version: options.version, kind: "rail",
|
|
7
|
+
module: {
|
|
8
|
+
id: "identity", kind: "capability", provides: ["./identity", "./identity/contracts"],
|
|
9
|
+
dependsOn: [
|
|
10
|
+
{ module: "bff", entries: ["./bff"] },
|
|
11
|
+
{ module: "data", entries: ["./data"] },
|
|
12
|
+
{ module: "kernel", entries: ["./kernel"] },
|
|
13
|
+
{ module: "http", entries: ["./http"] },
|
|
14
|
+
],
|
|
15
|
+
},
|
|
16
|
+
requiredSchema: options.requiredSchema,
|
|
17
|
+
emits: [], handles: [], schedules: [], routes: routes.map((route) => route.id),
|
|
18
|
+
requiredPorts: [...new Set(["emailTrustPolicy", ...(options.requiredPorts ?? [])])], env: [],
|
|
19
|
+
},
|
|
20
|
+
handlers: [], schedules: [], routes,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CustomerUserAuthenticationResult } from "./customerAuth.js";
|
|
2
|
+
import type { IdentityVerifierPort } from "./ports.js";
|
|
3
|
+
export declare function authenticateCustomerUserWith(verifier: IdentityVerifierPort, accessToken: string | null): Promise<CustomerUserAuthenticationResult>;
|
|
4
|
+
export declare function optionalCustomerSubject(rawClient: unknown, accessToken: string | null): Promise<string | null>;
|
|
5
|
+
export declare function authenticateCustomerUser(rawClient: unknown, accessToken: string | null): Promise<CustomerUserAuthenticationResult>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createSupabaseIdentityVerifier } from "./managedIdentityVerifier.js";
|
|
2
|
+
export async function authenticateCustomerUserWith(verifier, accessToken) {
|
|
3
|
+
if (!accessToken) {
|
|
4
|
+
return { ok: false, code: "UNAUTHORIZED", message: "Customer session required" };
|
|
5
|
+
}
|
|
6
|
+
const principal = await verifier.verifyAccessToken(accessToken);
|
|
7
|
+
if (!principal) {
|
|
8
|
+
return { ok: false, code: "UNAUTHORIZED", message: "Customer session required" };
|
|
9
|
+
}
|
|
10
|
+
return { ok: true, userId: principal.principalId };
|
|
11
|
+
}
|
|
12
|
+
export async function optionalCustomerSubject(rawClient, accessToken) {
|
|
13
|
+
const client = rawClient;
|
|
14
|
+
if (!accessToken)
|
|
15
|
+
return null;
|
|
16
|
+
const result = await authenticateCustomerUser(client, accessToken);
|
|
17
|
+
return result.ok ? result.userId : null;
|
|
18
|
+
}
|
|
19
|
+
export async function authenticateCustomerUser(rawClient, accessToken) {
|
|
20
|
+
const client = rawClient;
|
|
21
|
+
return authenticateCustomerUserWith(createSupabaseIdentityVerifier(client), accessToken);
|
|
22
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const customerLifecycleStageSchema: z.ZodEnum<{
|
|
3
|
+
lead: "lead";
|
|
4
|
+
tester: "tester";
|
|
5
|
+
customer: "customer";
|
|
6
|
+
inactive: "inactive";
|
|
7
|
+
}>;
|
|
8
|
+
export declare const customerMeRequestSchema: z.ZodObject<{}, z.core.$strict>;
|
|
9
|
+
export declare const customerMeResponseSchema: z.ZodObject<{
|
|
10
|
+
clientId: z.ZodString;
|
|
11
|
+
email: z.ZodString;
|
|
12
|
+
firstName: z.ZodNullable<z.ZodString>;
|
|
13
|
+
lastName: z.ZodNullable<z.ZodString>;
|
|
14
|
+
lifecycleStage: z.ZodEnum<{
|
|
15
|
+
lead: "lead";
|
|
16
|
+
tester: "tester";
|
|
17
|
+
customer: "customer";
|
|
18
|
+
inactive: "inactive";
|
|
19
|
+
}>;
|
|
20
|
+
subscriptions: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
21
|
+
subscriptionId: z.ZodGUID;
|
|
22
|
+
status: z.ZodString;
|
|
23
|
+
cadenceDays: z.ZodNumber;
|
|
24
|
+
nextCycleAt: z.ZodNullable<z.ZodString>;
|
|
25
|
+
paymentMethodKind: z.ZodNullable<z.ZodString>;
|
|
26
|
+
}, z.core.$strict>>>;
|
|
27
|
+
recentOrders: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
28
|
+
orderId: z.ZodGUID;
|
|
29
|
+
orderRef: z.ZodString;
|
|
30
|
+
status: z.ZodString;
|
|
31
|
+
mode: z.ZodEnum<{
|
|
32
|
+
one_time: "one_time";
|
|
33
|
+
subscription_cycle: "subscription_cycle";
|
|
34
|
+
}>;
|
|
35
|
+
total: z.ZodObject<{
|
|
36
|
+
amountMinor: z.ZodNumber;
|
|
37
|
+
currency: z.ZodString;
|
|
38
|
+
}, z.core.$strip>;
|
|
39
|
+
createdAt: z.ZodString;
|
|
40
|
+
}, z.core.$strict>>>;
|
|
41
|
+
}, z.core.$strip>;
|
|
42
|
+
export type CustomerMeResponse = z.infer<typeof customerMeResponseSchema>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
z.config({ jitless: true });
|
|
3
|
+
const platformCurrencySchema = z.string().regex(/^[A-Z]{3}$/);
|
|
4
|
+
export const customerLifecycleStageSchema = z.enum([
|
|
5
|
+
"lead",
|
|
6
|
+
"tester",
|
|
7
|
+
"customer",
|
|
8
|
+
"inactive",
|
|
9
|
+
]);
|
|
10
|
+
export const customerMeRequestSchema = z.object({}).strict();
|
|
11
|
+
export const customerMeResponseSchema = z.object({
|
|
12
|
+
clientId: z.string().min(1),
|
|
13
|
+
email: z.string().min(1),
|
|
14
|
+
firstName: z.string().nullable(),
|
|
15
|
+
lastName: z.string().nullable(),
|
|
16
|
+
lifecycleStage: customerLifecycleStageSchema,
|
|
17
|
+
subscriptions: z
|
|
18
|
+
.array(z
|
|
19
|
+
.object({
|
|
20
|
+
subscriptionId: z.guid(),
|
|
21
|
+
status: z.string().min(1),
|
|
22
|
+
cadenceDays: z.number().int().positive(),
|
|
23
|
+
nextCycleAt: z.string().datetime({ offset: true }).nullable(),
|
|
24
|
+
paymentMethodKind: z.string().nullable(),
|
|
25
|
+
})
|
|
26
|
+
.strict())
|
|
27
|
+
.optional(),
|
|
28
|
+
recentOrders: z
|
|
29
|
+
.array(z
|
|
30
|
+
.object({
|
|
31
|
+
orderId: z.guid(),
|
|
32
|
+
orderRef: z.string().regex(/^order_[a-z0-9-]+$/),
|
|
33
|
+
status: z.string().min(1),
|
|
34
|
+
mode: z.enum(["one_time", "subscription_cycle"]),
|
|
35
|
+
total: z.object({
|
|
36
|
+
amountMinor: z.number().int().nonnegative(),
|
|
37
|
+
currency: platformCurrencySchema,
|
|
38
|
+
}),
|
|
39
|
+
createdAt: z.string().datetime({ offset: true }),
|
|
40
|
+
})
|
|
41
|
+
.strict())
|
|
42
|
+
.optional(),
|
|
43
|
+
});
|