@oxy.so/contracts 4.16.0 → 4.21.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/dist/cjs/accountAccess.js +72 -0
- package/dist/cjs/accountEmail.js +12 -34
- package/dist/cjs/accountGraph.js +22 -320
- package/dist/cjs/agency.js +112 -1
- package/dist/cjs/agentKeys.js +11 -13
- package/dist/cjs/applicationRoles.js +211 -0
- package/dist/cjs/client/accountEmail.js +48 -0
- package/dist/cjs/client/accountGraph.js +323 -0
- package/dist/cjs/client/agentKeys.js +77 -0
- package/dist/cjs/client/check.js +475 -0
- package/dist/cjs/client/deviceBoot.js +22 -0
- package/dist/cjs/client/deviceDirectory.js +51 -0
- package/dist/cjs/client/deviceSession.js +63 -0
- package/dist/cjs/client/externalIdentity.js +19 -0
- package/dist/cjs/client/identity.js +45 -0
- package/dist/cjs/client/identityLink.js +55 -0
- package/dist/cjs/client/identityProof.js +109 -0
- package/dist/cjs/client/index.js +211 -0
- package/dist/cjs/client/organizations.js +114 -0
- package/dist/cjs/client/products.js +153 -0
- package/dist/cjs/client/recommendations.js +33 -0
- package/dist/cjs/client/reputation.js +41 -0
- package/dist/cjs/client/safety.js +196 -0
- package/dist/cjs/client/sessionStatus.js +23 -0
- package/dist/cjs/client/signIn.js +159 -0
- package/dist/cjs/client/userResponse.js +15 -0
- package/dist/cjs/client/username.js +173 -0
- package/dist/cjs/deviceSession.js +3 -16
- package/dist/cjs/identityLink.js +13 -34
- package/dist/cjs/identityProof.js +18 -106
- package/dist/cjs/index.js +13 -3
- package/dist/cjs/organizations.js +21 -41
- package/dist/cjs/reputation.js +3 -34
- package/dist/cjs/safety.js +430 -1
- package/dist/cjs/serviceToken.js +14 -0
- package/dist/cjs/signIn.js +33 -111
- package/dist/cjs/username.js +23 -158
- package/dist/esm/accountAccess.js +69 -0
- package/dist/esm/accountEmail.js +4 -32
- package/dist/esm/accountGraph.js +4 -309
- package/dist/esm/agency.js +111 -0
- package/dist/esm/agentKeys.js +4 -10
- package/dist/esm/applicationRoles.js +207 -0
- package/dist/esm/client/accountEmail.js +45 -0
- package/dist/esm/client/accountGraph.js +314 -0
- package/dist/esm/client/agentKeys.js +74 -0
- package/dist/esm/client/check.js +436 -0
- package/dist/esm/client/deviceBoot.js +19 -0
- package/dist/esm/client/deviceDirectory.js +48 -0
- package/dist/esm/client/deviceSession.js +60 -0
- package/dist/esm/client/externalIdentity.js +16 -0
- package/dist/esm/client/identity.js +42 -0
- package/dist/esm/client/identityLink.js +50 -0
- package/dist/esm/client/identityProof.js +104 -0
- package/dist/esm/client/index.js +37 -0
- package/dist/esm/client/organizations.js +111 -0
- package/dist/esm/client/products.js +150 -0
- package/dist/esm/client/recommendations.js +30 -0
- package/dist/esm/client/reputation.js +37 -0
- package/dist/esm/client/safety.js +193 -0
- package/dist/esm/client/sessionStatus.js +20 -0
- package/dist/esm/client/signIn.js +153 -0
- package/dist/esm/client/userResponse.js +12 -0
- package/dist/esm/client/username.js +167 -0
- package/dist/esm/deviceSession.js +1 -15
- package/dist/esm/identityLink.js +4 -27
- package/dist/esm/identityProof.js +4 -99
- package/dist/esm/index.js +4 -1
- package/dist/esm/organizations.js +4 -31
- package/dist/esm/reputation.js +1 -32
- package/dist/esm/safety.js +423 -0
- package/dist/esm/serviceToken.js +11 -0
- package/dist/esm/signIn.js +4 -96
- package/dist/esm/username.js +4 -148
- package/dist/types/accountAccess.d.ts +128 -0
- package/dist/types/accountEmail.d.ts +4 -36
- package/dist/types/accountGraph.d.ts +12 -208
- package/dist/types/agency.d.ts +351 -26
- package/dist/types/agentKeys.d.ts +14 -12
- package/dist/types/applicationRoles.d.ts +438 -0
- package/dist/types/client/accountEmail.d.ts +44 -0
- package/dist/types/client/accountGraph.d.ts +203 -0
- package/dist/types/client/agentKeys.d.ts +113 -0
- package/dist/types/client/check.d.ts +187 -0
- package/dist/types/client/deviceBoot.d.ts +6 -0
- package/dist/types/client/deviceDirectory.d.ts +134 -0
- package/dist/types/client/deviceSession.d.ts +91 -0
- package/dist/types/client/externalIdentity.d.ts +14 -0
- package/dist/types/client/identity.d.ts +8 -0
- package/dist/types/client/identityLink.d.ts +21 -0
- package/dist/types/client/identityProof.d.ts +73 -0
- package/dist/types/client/index.d.ts +37 -0
- package/dist/types/client/organizations.d.ts +167 -0
- package/dist/types/client/products.d.ts +145 -0
- package/dist/types/client/recommendations.d.ts +27 -0
- package/dist/types/client/reputation.d.ts +28 -0
- package/dist/types/client/safety.d.ts +213 -0
- package/dist/types/client/sessionStatus.d.ts +19 -0
- package/dist/types/client/signIn.d.ts +107 -0
- package/dist/types/client/userResponse.d.ts +7 -0
- package/dist/types/client/username.d.ts +126 -0
- package/dist/types/deviceDirectory.d.ts +28 -28
- package/dist/types/deviceSession.d.ts +5 -16
- package/dist/types/email/messages.d.ts +2 -2
- package/dist/types/externalIdentity.d.ts +15 -15
- package/dist/types/identityLink.d.ts +3 -11
- package/dist/types/identityProof.d.ts +2 -65
- package/dist/types/index.d.ts +5 -2
- package/dist/types/inference/accountBilling.d.ts +2 -2
- package/dist/types/inference/catalogue.d.ts +22 -22
- package/dist/types/inference/decisions.d.ts +10 -10
- package/dist/types/inference/embeddings.d.ts +16 -16
- package/dist/types/inference/errors.d.ts +8 -8
- package/dist/types/inference/inbox.d.ts +8 -8
- package/dist/types/inference/privateAutoExecution.d.ts +6 -6
- package/dist/types/inference/providerConnection.d.ts +4 -4
- package/dist/types/inference/realtime.d.ts +92 -92
- package/dist/types/inference/request.d.ts +48 -48
- package/dist/types/inference/streamEvents.d.ts +16 -16
- package/dist/types/inference/usage.d.ts +4 -4
- package/dist/types/oauth.d.ts +2 -2
- package/dist/types/organizations.d.ts +8 -10
- package/dist/types/products/access.d.ts +4 -4
- package/dist/types/products/billing.d.ts +3 -0
- package/dist/types/reputation.d.ts +1 -11
- package/dist/types/safety.d.ts +2374 -2
- package/dist/types/serviceToken.d.ts +11 -0
- package/dist/types/sessionStatus.d.ts +2 -2
- package/dist/types/signIn.d.ts +11 -94
- package/dist/types/userResponse.d.ts +22 -22
- package/dist/types/username.d.ts +1 -96
- package/package.json +24 -20
- package/dist/cjs/.tsbuildinfo +0 -1
- package/dist/esm/.tsbuildinfo +0 -1
- package/dist/types/.tsbuildinfo +0 -1
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The identity-proof message format, without Zod. Schemas: `../identityProof.ts`.
|
|
3
|
+
*
|
|
4
|
+
* Part of `@oxy.so/contracts/client`: never import `zod` here (see `./check.ts`).
|
|
5
|
+
*/
|
|
6
|
+
export const IDENTITY_PROOF_VERSION = 2;
|
|
7
|
+
export const IDENTITY_PROOF_DOMAIN = 'oxy-identity-proof';
|
|
8
|
+
/** The audience every API-verified identity proof names. */
|
|
9
|
+
export const IDENTITY_PROOF_AUDIENCE = 'oxy-api/identity';
|
|
10
|
+
/** How long a proof challenge lives: one interactive ceremony. */
|
|
11
|
+
export const IDENTITY_PROOF_CHALLENGE_TTL_MS = 5 * 60 * 1000;
|
|
12
|
+
/**
|
|
13
|
+
* Everything a root proof may authorize. A challenge is minted for exactly one
|
|
14
|
+
* action and spent only by a proof for that action.
|
|
15
|
+
*/
|
|
16
|
+
export const IDENTITY_PROOF_ACTIONS = {
|
|
17
|
+
/**
|
|
18
|
+
* Link Commons' root to an account that has none (`POST /auth/link`).
|
|
19
|
+
* The account becomes self-custodied and its email is deleted
|
|
20
|
+
* (ADR 0029 D3).
|
|
21
|
+
*/
|
|
22
|
+
link: 'link_identity',
|
|
23
|
+
};
|
|
24
|
+
export const IDENTITY_PROOF_ACTION_VALUES = Object.values(IDENTITY_PROOF_ACTIONS);
|
|
25
|
+
export const HEX_DIGEST = /^[0-9a-f]{64}$/;
|
|
26
|
+
export const ROOT_KEY = /^04[0-9a-f]{128}$/;
|
|
27
|
+
export const CHALLENGE = /^[0-9a-f]{64}$/;
|
|
28
|
+
/**
|
|
29
|
+
* Canonical JSON: object keys sorted by UTF-16 code unit, no whitespace,
|
|
30
|
+
* `undefined` members omitted, arrays in order. Numbers must be finite. This is
|
|
31
|
+
* the ONLY serializer for anything digested into a proof.
|
|
32
|
+
*/
|
|
33
|
+
export function canonicalJson(value) {
|
|
34
|
+
if (value === null)
|
|
35
|
+
return 'null';
|
|
36
|
+
switch (typeof value) {
|
|
37
|
+
case 'string':
|
|
38
|
+
case 'boolean':
|
|
39
|
+
return JSON.stringify(value);
|
|
40
|
+
case 'number':
|
|
41
|
+
if (!Number.isFinite(value))
|
|
42
|
+
throw new Error('canonicalJson: non-finite number');
|
|
43
|
+
return JSON.stringify(value);
|
|
44
|
+
case 'object': {
|
|
45
|
+
if (Array.isArray(value)) {
|
|
46
|
+
return `[${value.map((entry) => (entry === undefined ? 'null' : canonicalJson(entry))).join(',')}]`;
|
|
47
|
+
}
|
|
48
|
+
const record = value;
|
|
49
|
+
const keys = Object.keys(record)
|
|
50
|
+
.filter((key) => record[key] !== undefined)
|
|
51
|
+
.sort();
|
|
52
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(',')}}`;
|
|
53
|
+
}
|
|
54
|
+
default:
|
|
55
|
+
throw new Error(`canonicalJson: unsupported ${typeof value}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The exact bytes a root signs. Throws on a malformed claim rather than signing
|
|
60
|
+
* (or verifying) something ambiguous.
|
|
61
|
+
*/
|
|
62
|
+
export function buildIdentityProofMessage(claims) {
|
|
63
|
+
if (!IDENTITY_PROOF_ACTION_VALUES.includes(claims.action))
|
|
64
|
+
throw new Error('identity proof: unknown action');
|
|
65
|
+
if (!claims.subject || !claims.actor)
|
|
66
|
+
throw new Error('identity proof: subject and actor are required');
|
|
67
|
+
if (!ROOT_KEY.test(claims.rootPublicKey))
|
|
68
|
+
throw new Error('identity proof: rootPublicKey must be canonical');
|
|
69
|
+
if (claims.payloadDigest !== null && !HEX_DIGEST.test(claims.payloadDigest)) {
|
|
70
|
+
throw new Error('identity proof: payloadDigest must be a lowercase SHA-256 hex digest');
|
|
71
|
+
}
|
|
72
|
+
if (claims.expectedRevision !== null &&
|
|
73
|
+
(!Number.isSafeInteger(claims.expectedRevision) || claims.expectedRevision < 0)) {
|
|
74
|
+
throw new Error('identity proof: expectedRevision must be a non-negative integer');
|
|
75
|
+
}
|
|
76
|
+
if (!CHALLENGE.test(claims.challenge))
|
|
77
|
+
throw new Error('identity proof: challenge must be 64 lowercase hex characters');
|
|
78
|
+
if (!Number.isSafeInteger(claims.expiresAt) || claims.expiresAt <= 0)
|
|
79
|
+
throw new Error('identity proof: expiresAt must be unix milliseconds');
|
|
80
|
+
return canonicalJson({
|
|
81
|
+
v: IDENTITY_PROOF_VERSION,
|
|
82
|
+
domain: IDENTITY_PROOF_DOMAIN,
|
|
83
|
+
action: claims.action,
|
|
84
|
+
subject: claims.subject,
|
|
85
|
+
actor: claims.actor,
|
|
86
|
+
rootPublicKey: claims.rootPublicKey,
|
|
87
|
+
payloadDigest: claims.payloadDigest,
|
|
88
|
+
expectedRevision: claims.expectedRevision,
|
|
89
|
+
audience: claims.audience,
|
|
90
|
+
challenge: claims.challenge,
|
|
91
|
+
expiresAt: claims.expiresAt,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Stable error codes the root routes answer with (`error.code` in the API error
|
|
96
|
+
* body). Clients map these through their localization, never the English message.
|
|
97
|
+
*/
|
|
98
|
+
export const IDENTITY_ERROR_CODES = {
|
|
99
|
+
proofInvalid: 'IDENTITY_PROOF_INVALID',
|
|
100
|
+
rootAlreadyLinked: 'IDENTITY_ROOT_ALREADY_LINKED',
|
|
101
|
+
rootLinkedElsewhere: 'IDENTITY_ROOT_LINKED_ELSEWHERE',
|
|
102
|
+
freshFactorRequired: 'IDENTITY_FRESH_FACTOR_REQUIRED',
|
|
103
|
+
notPersonal: 'IDENTITY_NOT_PERSONAL_ACCOUNT',
|
|
104
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@oxy.so/contracts/client` — the contracts without Zod, for client bundles.
|
|
3
|
+
*
|
|
4
|
+
* The root entry is the contract: Zod schemas the API parses with, and the
|
|
5
|
+
* types inferred from them. Zod's runtime is ~120 KB of JavaScript, evaluated
|
|
6
|
+
* at startup, that a client only needed to answer "does this response have
|
|
7
|
+
* the shape its type promises". This entry answers that with small structural
|
|
8
|
+
* checks (`*Check`, used through {@link checkContract} / {@link parseContract}),
|
|
9
|
+
* and carries the constants and pure helpers clients use, so an app or SDK
|
|
10
|
+
* that imports only from here ships no Zod at all.
|
|
11
|
+
*
|
|
12
|
+
* Every check is the twin of one schema in the root entry: it is tied to the
|
|
13
|
+
* schema's type at compile time (`exact` in `./check.ts`) and to its runtime
|
|
14
|
+
* behaviour by `__tests__/clientChecks.parity.test.ts`. Types are not repeated
|
|
15
|
+
* here; import them from `@oxy.so/contracts` with `import type`.
|
|
16
|
+
*/
|
|
17
|
+
export { ContractCheckError, checkContract, formatCheckPath, parseContract, } from './check.js';
|
|
18
|
+
export { ACCOUNT_CATEGORY_IDS, ACCOUNT_CATEGORY_KINDS, ACCOUNT_KINDS, CHILD_ACCOUNT_KINDS, MAX_ACCOUNT_CATEGORIES, RETIRED_ACCOUNT_CATEGORY_IDS, SELECTABLE_ACCOUNT_CATEGORY_IDS, isAccountKind, isDelegatedActAsEligibleKind, isOperatorSwitchTargetKind, isSelectableAccountCategoryId, kindAcceptsAccountCategories, newlyAddedRetiredCategories, } from './accountGraph.js';
|
|
19
|
+
export { EMAIL_CODE_LENGTH, EMAIL_CODE_MAX_ATTEMPTS, EMAIL_CODE_TTL_MS, EMAIL_TICKET_TTL_MS, EMAIL_VERIFICATION_ERROR_CODES, EMAIL_VERIFICATION_PURPOSES, emailAddressCheck, emailTicketCheck, emailVerificationConfirmResponseCheck, emailVerificationStartResponseCheck, } from './accountEmail.js';
|
|
20
|
+
export { ACCOUNT_AUTH_METHODS, AGENT_PROOF_ACTIONS, AGENT_PROOF_AUDIENCE, AGENT_PROOF_TTL_MS, agentChallengeRequestCheck, agentKeyListResponseCheck, agentKeyOperationChallengeCheck, agentKeyOperationCheck, agentKeyOperationResultCheck, agentProofClaimsCheck, agentVerifyRequestCheck, executeAgentKeyOperationCheck, } from './agentKeys.js';
|
|
21
|
+
export { isLoopbackOrigin } from '../capabilityBindings.js';
|
|
22
|
+
export { loginResultCheck } from './deviceBoot.js';
|
|
23
|
+
export { externalIdentityReferenceCheck } from './externalIdentity.js';
|
|
24
|
+
export { signedRecordEnvelopeCheck } from './identity.js';
|
|
25
|
+
export { deviceActivateResponseCheck, deviceDirectoryCheck, deviceDirectorySyncCheck, } from './deviceDirectory.js';
|
|
26
|
+
export { SESSION_ACCOUNTS_CHANGED_EVENT, activeTokenCheck, deviceBackgroundCredentialResponseCheck, deviceJoinCodeResponseCheck, deviceJoinResponseCheck, deviceRegisterResponseCheck, deviceSessionStateCheck, deviceSessionSyncCheck, deviceTokenMintResponseCheck, sessionAccountsChangedEventCheck, } from './deviceSession.js';
|
|
27
|
+
export { IDENTITY_LINK_QR_PREFIX, IDENTITY_LINK_STATUSES, buildIdentityLinkQrPayload, identityLinkCreateResponseCheck, identityLinkStateCheck, parseIdentityLinkQrPayload, } from './identityLink.js';
|
|
28
|
+
export { IDENTITY_ERROR_CODES, IDENTITY_PROOF_ACTIONS, IDENTITY_PROOF_ACTION_VALUES, IDENTITY_PROOF_AUDIENCE, IDENTITY_PROOF_CHALLENGE_TTL_MS, IDENTITY_PROOF_DOMAIN, IDENTITY_PROOF_VERSION, buildIdentityProofMessage, canonicalJson, } from './identityProof.js';
|
|
29
|
+
export { ORGANIZATION_ALIAS_KINDS, ORGANIZATION_CLAIM_DECISIONS, ORGANIZATION_CLAIM_STATUSES, ORGANIZATION_ERROR_CODES, ORGANIZATION_NAME_MAX_LENGTH, ORGANIZATION_STATUSES, ORGANIZATION_TYPES, createOrganizationCheck, organizationBatchCheck, organizationCheck, organizationClaimCheck, organizationClaimDetailCheck, organizationClaimInputCheck, organizationClaimListCheck, organizationClaimReviewCheck, organizationCurationResultCheck, organizationExternalCheck, organizationExternalResolutionCheck, organizationImportBatchResultCheck, organizationListCheck, organizationNameCheck, updateOrganizationCheck, } from './organizations.js';
|
|
30
|
+
export { cancelProductSubscriptionCheck, personalPlanCatalogueCheck, personalPlanCheckoutRequestCheck, personalPlanCheckoutResultCheck, productSubscriptionCancellationResultCheck, productSubscriptionsResponseCheck, subjectProductAccessCheck, subjectProductAccessQueryCheck, subscriptionCreditGrantsResponseCheck, } from './products.js';
|
|
31
|
+
export { recommendationRequestCheck } from './recommendations.js';
|
|
32
|
+
export { isFullReputationBalance } from './reputation.js';
|
|
33
|
+
export { PURCHASE_APPROVAL_STATUSES, SAFETY_POLICY_VERSION, SUPERVISED_ACTIONS, SUPERVISION_CONSENT_VERSION, SUPERVISION_INVITATION_STATUSES, SUPERVISION_STATUSES, profileAccessDecisionCheck, profileAccessInputCheck, purchaseApprovalCheck, purchaseApprovalListCheck, purchaseReviewInputCheck, safetyOrganizationsInputCheck, safetyOrganizationsResponseCheck, safetyPeopleInputCheck, safetyPeopleResponseCheck, supervisionAcceptInputCheck, supervisionContactInputCheck, supervisionContactsResponseCheck, supervisionInvitationCheck, supervisionInviteInputCheck, supervisionReviewInputCheck, supervisionStateCheck, } from './safety.js';
|
|
34
|
+
export { publicApplicationCheck } from './sessionStatus.js';
|
|
35
|
+
export { EMAIL_ACCOUNTS_MAX, EMAIL_SIGNIN_CHOICE_TTL_MS, EMAIL_SIGNIN_LINK_TTL_MS, EMAIL_SIGNIN_LONG_CODE_ALPHABET, EMAIL_SIGNIN_LONG_CODE_LENGTH, PASSWORD_MAX_LENGTH, PASSWORD_MIN_LENGTH, REAUTH_ACTIONS, SIGNIN_SECOND_FACTOR_MAX_ATTEMPTS, SIGNIN_SECOND_FACTOR_TTL_MS, SIGN_IN_ERROR_CODES, TOTP_BACKUP_CODE_COUNT, TOTP_DIGITS, TOTP_PERIOD_SECONDS, emailSignInChooseAccountCheck, emailSignInLinkResponseCheck, emailSignInPendingCheck, emailSignInStartResponseCheck, isEmailSignInChooseAccount, isSecondFactorRequired, normalizeEmailSignInCode, reauthProofCheck, secondFactorRequiredCheck, signInMethodsCheck, totpBackupCodesResponseCheck, totpEnrollResponseCheck, } from './signIn.js';
|
|
36
|
+
export { BOT_USERNAME_INVALID_MESSAGE, BOT_USERNAME_SUFFIX, NUMERIC_USERNAME_MESSAGE, RESERVED_USERNAMES, RESERVED_USERNAME_MESSAGE, USERNAME_INVALID_MESSAGE, USERNAME_MAX_LENGTH, USERNAME_MIN_LENGTH, USERNAME_PATTERN_SOURCE, applyBotUsernameSuffix, botUsernameCheck, stripDisallowedUsernameCharacters, usernameCheck, usernameCheckForAccountKind, } from './username.js';
|
|
37
|
+
export { userNameCheck } from './userResponse.js';
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Organization vocabularies and checks, without Zod. Schemas: `../organizations.ts`.
|
|
3
|
+
*
|
|
4
|
+
* Part of `@oxy.so/contracts/client`: never import `zod` here (see `./check.ts`).
|
|
5
|
+
*/
|
|
6
|
+
import { array, boolean, datetime, exact, maxItems, maxLength, minLength, nullable, oneOf, optional, pattern, refine, strictObject, string, trim, url, } from './check.js';
|
|
7
|
+
/** Real-world identity classification, independent of account topics and permissions. */
|
|
8
|
+
export const ORGANIZATION_TYPES = [
|
|
9
|
+
'business',
|
|
10
|
+
'nonprofit',
|
|
11
|
+
'public_body',
|
|
12
|
+
'community',
|
|
13
|
+
'other',
|
|
14
|
+
];
|
|
15
|
+
export const ORGANIZATION_STATUSES = ['active', 'inactive', 'merged', 'suppressed'];
|
|
16
|
+
export const ORGANIZATION_CLAIM_STATUSES = ['unclaimed', 'claimed'];
|
|
17
|
+
export const ORGANIZATION_ALIAS_KINDS = [
|
|
18
|
+
'name_variant',
|
|
19
|
+
'former_name',
|
|
20
|
+
'localized_name',
|
|
21
|
+
'marketing_name',
|
|
22
|
+
'misspelling',
|
|
23
|
+
];
|
|
24
|
+
export const ORGANIZATION_CLAIM_DECISIONS = ['pending', 'approved', 'rejected'];
|
|
25
|
+
export const ORGANIZATION_NAME_MAX_LENGTH = 300;
|
|
26
|
+
export const ORGANIZATION_ERROR_CODES = [
|
|
27
|
+
'bad_request',
|
|
28
|
+
'unauthorized',
|
|
29
|
+
'forbidden',
|
|
30
|
+
'not_found',
|
|
31
|
+
'unknown_route',
|
|
32
|
+
'conflict',
|
|
33
|
+
'validation_failed',
|
|
34
|
+
'rate_limited',
|
|
35
|
+
'internal_error',
|
|
36
|
+
'service_unavailable',
|
|
37
|
+
];
|
|
38
|
+
/* -------------------------------------------------------------------------- */
|
|
39
|
+
/* Checks */
|
|
40
|
+
/* -------------------------------------------------------------------------- */
|
|
41
|
+
const id = string(minLength(1), maxLength(128));
|
|
42
|
+
const optionalText = (max) => optional(nullable(string(trim, minLength(1), maxLength(max))));
|
|
43
|
+
const evidenceReference = string(trim, minLength(1), maxLength(1000));
|
|
44
|
+
const createdAt = string(datetime);
|
|
45
|
+
const organizationAlias = strictObject({
|
|
46
|
+
alias: string(trim, minLength(1), maxLength(300)),
|
|
47
|
+
kind: oneOf(ORGANIZATION_ALIAS_KINDS),
|
|
48
|
+
language: optional(string(minLength(2), maxLength(35))),
|
|
49
|
+
});
|
|
50
|
+
/** The twin of `organizationNameSchema`. */
|
|
51
|
+
export const organizationNameCheck = refine(string(trim, minLength(1), maxLength(ORGANIZATION_NAME_MAX_LENGTH)), (value) => !/[<>]/.test(value) &&
|
|
52
|
+
Array.from(value).every((character) => character.charCodeAt(0) >= 32 && character.charCodeAt(0) !== 127), 'Organization name must be plain text');
|
|
53
|
+
const organizationFields = {
|
|
54
|
+
name: organizationNameCheck,
|
|
55
|
+
organizationType: oneOf(ORGANIZATION_TYPES),
|
|
56
|
+
legalName: optionalText(300),
|
|
57
|
+
websiteUrl: optional(nullable(refine(string(url, maxLength(2048)), (value) => /^https?:\/\//.test(value), 'Use HTTP or HTTPS'))),
|
|
58
|
+
countryCode: optional(nullable(string(pattern(/^[A-Z]{2}$/)))),
|
|
59
|
+
aliases: optional(array(organizationAlias, maxItems(100))),
|
|
60
|
+
};
|
|
61
|
+
export const createOrganizationCheck = exact()(strictObject(organizationFields));
|
|
62
|
+
export const updateOrganizationCheck = exact()(strictObject({
|
|
63
|
+
...organizationFields,
|
|
64
|
+
name: optional(organizationNameCheck),
|
|
65
|
+
organizationType: optional(organizationFields.organizationType),
|
|
66
|
+
}));
|
|
67
|
+
export const organizationCheck = exact()(strictObject({
|
|
68
|
+
name: organizationNameCheck,
|
|
69
|
+
organizationType: oneOf(ORGANIZATION_TYPES),
|
|
70
|
+
legalName: nullable(string()),
|
|
71
|
+
websiteUrl: nullable(string()),
|
|
72
|
+
countryCode: nullable(string()),
|
|
73
|
+
aliases: array(organizationAlias),
|
|
74
|
+
accountId: id,
|
|
75
|
+
username: nullable(string()),
|
|
76
|
+
avatar: nullable(string()),
|
|
77
|
+
verifiedDomains: array(string()),
|
|
78
|
+
claimStatus: oneOf(ORGANIZATION_CLAIM_STATUSES),
|
|
79
|
+
status: oneOf(ORGANIZATION_STATUSES),
|
|
80
|
+
mergedIntoAccountId: nullable(id),
|
|
81
|
+
createdAt,
|
|
82
|
+
updatedAt: string(datetime),
|
|
83
|
+
}));
|
|
84
|
+
export const organizationListCheck = exact()(strictObject({ items: array(organizationCheck), nextCursor: nullable(string()) }));
|
|
85
|
+
export const organizationBatchCheck = exact()(strictObject({
|
|
86
|
+
items: array(strictObject({ requestedAccountId: id, organization: organizationCheck })),
|
|
87
|
+
missingIds: array(id),
|
|
88
|
+
}));
|
|
89
|
+
export const organizationClaimInputCheck = exact()(strictObject({ evidenceReference }));
|
|
90
|
+
const organizationClaimFields = {
|
|
91
|
+
id,
|
|
92
|
+
accountId: id,
|
|
93
|
+
claimantUserId: id,
|
|
94
|
+
status: oneOf(ORGANIZATION_CLAIM_DECISIONS),
|
|
95
|
+
createdAt,
|
|
96
|
+
};
|
|
97
|
+
export const organizationClaimCheck = exact()(strictObject(organizationClaimFields));
|
|
98
|
+
export const organizationClaimDetailCheck = exact()(strictObject({
|
|
99
|
+
...organizationClaimFields,
|
|
100
|
+
evidenceReference: string(),
|
|
101
|
+
reviewEvidenceReference: nullable(string()),
|
|
102
|
+
reviewedAt: nullable(string(datetime)),
|
|
103
|
+
}));
|
|
104
|
+
export const organizationClaimListCheck = exact()(strictObject({ items: array(organizationClaimDetailCheck), nextCursor: nullable(string()) }));
|
|
105
|
+
export const organizationClaimReviewCheck = exact()(strictObject({ decision: oneOf(['approved', 'rejected']), evidenceReference }));
|
|
106
|
+
export const organizationImportBatchResultCheck = exact()(strictObject({
|
|
107
|
+
items: array(strictObject({ externalId: string(), accountId: id, created: boolean })),
|
|
108
|
+
}));
|
|
109
|
+
export const organizationExternalCheck = exact()(strictObject({ accountId: id, created: boolean }));
|
|
110
|
+
export const organizationExternalResolutionCheck = exact()(strictObject({ accountId: id }));
|
|
111
|
+
export const organizationCurationResultCheck = exact()(strictObject({ accountId: id, status: oneOf(['active', 'inactive', 'suppressed']) }));
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Product access, plans, checkout and subscription checks, without Zod.
|
|
3
|
+
* Schemas: `../products/*.ts`.
|
|
4
|
+
*
|
|
5
|
+
* Part of `@oxy.so/contracts/client`: never import `zod` here (see `./check.ts`).
|
|
6
|
+
*/
|
|
7
|
+
import { array, boolean, branded, datetime, discriminatedUnion, exact, int, literal, maxLength, minLength, nonnegative, nullable, number, oneOf, optional, pattern, positive, refine, safe, startsWith, strictObject, string, union, url, } from './check.js';
|
|
8
|
+
const identifier = string(minLength(1), maxLength(160));
|
|
9
|
+
const displayName = string(minLength(1), maxLength(100));
|
|
10
|
+
const count = number(int, nonnegative, safe);
|
|
11
|
+
const version = number(int, positive, safe);
|
|
12
|
+
const oxyAccountId = branded(string(minLength(1), maxLength(64)));
|
|
13
|
+
const actionKey = string(minLength(8), maxLength(160), pattern(/^[a-zA-Z0-9_-]+$/));
|
|
14
|
+
const combination = oneOf(['maximum', 'sum', 'exclusive']);
|
|
15
|
+
const origin = oneOf(['individual', 'bundle']);
|
|
16
|
+
const period = (message) => refine(strictObject({ start: string(datetime), end: string(datetime) }), (value) => Date.parse(value.end) > Date.parse(value.start), message);
|
|
17
|
+
const productBenefit = discriminatedUnion('kind', {
|
|
18
|
+
capability: strictObject({ kind: literal('capability'), productId: identifier, key: identifier }),
|
|
19
|
+
quota: strictObject({
|
|
20
|
+
kind: literal('quota'),
|
|
21
|
+
productId: identifier,
|
|
22
|
+
key: identifier,
|
|
23
|
+
unit: identifier,
|
|
24
|
+
included: count,
|
|
25
|
+
combination,
|
|
26
|
+
}),
|
|
27
|
+
});
|
|
28
|
+
export const subjectProductAccessQueryCheck = exact()(strictObject({
|
|
29
|
+
schemaVersion: literal(1),
|
|
30
|
+
subjectAccountId: oxyAccountId,
|
|
31
|
+
productId: identifier,
|
|
32
|
+
}));
|
|
33
|
+
export const subjectProductAccessCheck = exact()(strictObject({
|
|
34
|
+
schemaVersion: literal(1),
|
|
35
|
+
subjectAccountId: oxyAccountId,
|
|
36
|
+
productId: identifier,
|
|
37
|
+
evaluatedAt: string(datetime),
|
|
38
|
+
capabilities: array(strictObject({ key: identifier, grantIds: array(identifier) })),
|
|
39
|
+
quotas: array(strictObject({
|
|
40
|
+
key: identifier,
|
|
41
|
+
unit: identifier,
|
|
42
|
+
included: count,
|
|
43
|
+
combination,
|
|
44
|
+
grantIds: array(identifier),
|
|
45
|
+
})),
|
|
46
|
+
conflicts: array(strictObject({
|
|
47
|
+
key: identifier,
|
|
48
|
+
reason: oneOf([
|
|
49
|
+
'combination_mismatch',
|
|
50
|
+
'unit_mismatch',
|
|
51
|
+
'exclusive_overlap',
|
|
52
|
+
'unsafe_total',
|
|
53
|
+
]),
|
|
54
|
+
grantIds: array(identifier),
|
|
55
|
+
})),
|
|
56
|
+
}));
|
|
57
|
+
export const personalPlanCatalogueCheck = exact()(refine(strictObject({
|
|
58
|
+
schemaVersion: literal(1),
|
|
59
|
+
state: oneOf(['unconfigured', 'configured']),
|
|
60
|
+
purchase: literal('unavailable'),
|
|
61
|
+
plans: array(strictObject({
|
|
62
|
+
offerId: identifier,
|
|
63
|
+
offerVersion: version,
|
|
64
|
+
displayName,
|
|
65
|
+
audience: literal('personal'),
|
|
66
|
+
kind: literal('oxy_one'),
|
|
67
|
+
price: optional(strictObject({
|
|
68
|
+
currency: string(pattern(/^[A-Z]{3}$/)),
|
|
69
|
+
amountMinorUnits: version,
|
|
70
|
+
interval: literal('month'),
|
|
71
|
+
trial: literal('none'),
|
|
72
|
+
taxTreatment: literal('inclusive'),
|
|
73
|
+
merchantTotal: literal('final'),
|
|
74
|
+
})),
|
|
75
|
+
benefits: array(strictObject({ displayName, benefit: productBenefit })),
|
|
76
|
+
})),
|
|
77
|
+
}), (value) => (value.state === 'unconfigured') === (value.plans.length === 0), 'Catalogue state must match published plans'));
|
|
78
|
+
export const personalPlanCheckoutRequestCheck = exact()(strictObject({
|
|
79
|
+
expectedSubjectAccountId: oxyAccountId,
|
|
80
|
+
offerId: identifier,
|
|
81
|
+
offerVersion: version,
|
|
82
|
+
idempotencyKey: actionKey,
|
|
83
|
+
}));
|
|
84
|
+
export const personalPlanCheckoutResultCheck = exact()(discriminatedUnion('state', {
|
|
85
|
+
unconfigured: strictObject({
|
|
86
|
+
state: literal('unconfigured'),
|
|
87
|
+
reason: oneOf(['provider_unconfigured', 'offer_unconfigured', 'price_unconfigured']),
|
|
88
|
+
}),
|
|
89
|
+
pending: strictObject({
|
|
90
|
+
state: literal('pending'),
|
|
91
|
+
intentId: identifier,
|
|
92
|
+
checkoutUrl: optional(string(url, startsWith('https://'))),
|
|
93
|
+
}),
|
|
94
|
+
closed: strictObject({ state: literal('closed'), intentId: identifier }),
|
|
95
|
+
fulfilled: strictObject({ state: literal('fulfilled'), intentId: identifier }),
|
|
96
|
+
}));
|
|
97
|
+
const billingPeriod = period('Period end must follow start');
|
|
98
|
+
export const productSubscriptionsResponseCheck = exact()(strictObject({
|
|
99
|
+
subscriptions: array(strictObject({
|
|
100
|
+
sourceId: identifier,
|
|
101
|
+
status: oneOf([
|
|
102
|
+
'active',
|
|
103
|
+
'trialing',
|
|
104
|
+
'past_due',
|
|
105
|
+
'unpaid',
|
|
106
|
+
'canceled',
|
|
107
|
+
'paused',
|
|
108
|
+
'incomplete',
|
|
109
|
+
'incomplete_expired',
|
|
110
|
+
]),
|
|
111
|
+
period: billingPeriod,
|
|
112
|
+
cancelAtPeriodEnd: boolean,
|
|
113
|
+
canCancel: boolean,
|
|
114
|
+
offers: array(strictObject({
|
|
115
|
+
segmentId: identifier,
|
|
116
|
+
offerId: identifier,
|
|
117
|
+
offerVersion: version,
|
|
118
|
+
displayName,
|
|
119
|
+
origin,
|
|
120
|
+
period: billingPeriod,
|
|
121
|
+
current: boolean,
|
|
122
|
+
products: array(strictObject({ id: identifier, displayName })),
|
|
123
|
+
})),
|
|
124
|
+
})),
|
|
125
|
+
}));
|
|
126
|
+
export const subscriptionCreditGrantsResponseCheck = exact()(strictObject({
|
|
127
|
+
grants: array(refine(strictObject({
|
|
128
|
+
id: identifier,
|
|
129
|
+
invoiceId: identifier,
|
|
130
|
+
origin: oneOf([
|
|
131
|
+
'subscription_payment',
|
|
132
|
+
'subscription_proration',
|
|
133
|
+
'subscription_promotional_grant',
|
|
134
|
+
]),
|
|
135
|
+
period: billingPeriod,
|
|
136
|
+
granted: count,
|
|
137
|
+
consumed: count,
|
|
138
|
+
clawed: count,
|
|
139
|
+
remaining: count,
|
|
140
|
+
promotionId: nullable(identifier),
|
|
141
|
+
createdAt: string(datetime),
|
|
142
|
+
}), (value) => BigInt(value.consumed) + BigInt(value.clawed) + BigInt(value.remaining) ===
|
|
143
|
+
BigInt(value.granted), 'Grant conservation differs')),
|
|
144
|
+
}));
|
|
145
|
+
export const cancelProductSubscriptionCheck = exact()(strictObject({
|
|
146
|
+
sourceId: identifier,
|
|
147
|
+
actionId: optional(actionKey),
|
|
148
|
+
expectedSubjectAccountId: optional(oxyAccountId),
|
|
149
|
+
}));
|
|
150
|
+
export const productSubscriptionCancellationResultCheck = exact()(union(strictObject({ sourceId: identifier, cancelAtPeriodEnd: literal(true) }), strictObject({ sourceId: identifier, reconciliationPending: literal(true) })));
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The recommendation-request check, without Zod. Schemas: `../recommendations.ts`.
|
|
3
|
+
*
|
|
4
|
+
* Part of `@oxy.so/contracts/client`: never import `zod` here (see `./check.ts`).
|
|
5
|
+
*/
|
|
6
|
+
import { array, atLeast, atMost, exact, int, maxItems, maxLength, minItems, minLength, number, object, oneOf, optional, string, trim, } from './check.js';
|
|
7
|
+
const id = string(trim, minLength(1));
|
|
8
|
+
const weight = optional(number(atLeast(0), atMost(10)));
|
|
9
|
+
export const recommendationRequestCheck = exact()(object({
|
|
10
|
+
clientId: optional(string(trim, minLength(1))),
|
|
11
|
+
limit: optional(number(int, atLeast(1), atMost(100))),
|
|
12
|
+
offset: optional(number(int, atLeast(0))),
|
|
13
|
+
excludeTypes: optional(array(oneOf(['federated', 'agent', 'automated']))),
|
|
14
|
+
excludeIds: optional(array(id, maxItems(500))),
|
|
15
|
+
boosts: optional(array(object({
|
|
16
|
+
userIds: array(id, minItems(1), maxItems(200)),
|
|
17
|
+
weight: number(atLeast(-5), atMost(5)),
|
|
18
|
+
reason: optional(string(trim, maxLength(120))),
|
|
19
|
+
}), maxItems(50))),
|
|
20
|
+
signalWeights: optional(object({
|
|
21
|
+
graph: weight,
|
|
22
|
+
completeness: weight,
|
|
23
|
+
verified: weight,
|
|
24
|
+
curation: weight,
|
|
25
|
+
interest: weight,
|
|
26
|
+
appBoost: weight,
|
|
27
|
+
repCandidate: weight,
|
|
28
|
+
affinity: weight,
|
|
29
|
+
})),
|
|
30
|
+
}));
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The reputation view guard, without Zod. Schemas: `../reputation.ts`.
|
|
3
|
+
*
|
|
4
|
+
* Part of `@oxy.so/contracts/client`: never import `zod` here (see `./check.ts`).
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* The fields the full {@link ReputationBalance} carries beyond the public
|
|
8
|
+
* {@link ReputationBalanceSummary} that the API sends ALL-OR-NOTHING. The
|
|
9
|
+
* runtime discriminant between the two views.
|
|
10
|
+
*
|
|
11
|
+
* The V2 blocks (`conduct`, `contribution`, …) are deliberately NOT listed:
|
|
12
|
+
* they are optional on the wire, so requiring them here would make a balance
|
|
13
|
+
* from a server that predates them fail to narrow, hiding the whole private
|
|
14
|
+
* view. Read a V2 block by checking that block.
|
|
15
|
+
*/
|
|
16
|
+
export const FULL_BALANCE_FIELDS = [
|
|
17
|
+
'positive',
|
|
18
|
+
'negative',
|
|
19
|
+
'breakdown',
|
|
20
|
+
'influence',
|
|
21
|
+
'reliability',
|
|
22
|
+
'recalculatedAt',
|
|
23
|
+
'updatedAt',
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* Whether a balance came back as the SUBJECT view, and so carries the
|
|
27
|
+
* breakdown / influence / reliability blocks.
|
|
28
|
+
*
|
|
29
|
+
* Checks every extra field rather than one representative: the point of the
|
|
30
|
+
* guard is that the caller then dereferences those blocks, so a partial payload
|
|
31
|
+
* must not narrow.
|
|
32
|
+
*
|
|
33
|
+
* @param balance - A balance from `getReputationBalance`.
|
|
34
|
+
*/
|
|
35
|
+
export function isFullReputationBalance(balance) {
|
|
36
|
+
return FULL_BALANCE_FIELDS.every((field) => field in balance);
|
|
37
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safety and supervision checks, without Zod. Schemas: `../safety.ts`.
|
|
3
|
+
*
|
|
4
|
+
* Only what a browser or device sends and reads is here: the profile-access
|
|
5
|
+
* read, supervision management and the guardian's purchase review. The service
|
|
6
|
+
* decision lanes are server-only (`@oxy.so/core/server`) and parse with the
|
|
7
|
+
* Zod schemas directly.
|
|
8
|
+
*
|
|
9
|
+
* Part of `@oxy.so/contracts/client`: never import `zod` here (see `./check.ts`).
|
|
10
|
+
*/
|
|
11
|
+
import { array, atLeast, atMost, boolean, datetime, exact, int, literal, maxItems, maxLength, minItems, minLength, nonnegative, nullable, number, oneOf, optional, pattern, refine, refineAt, strictObject, string, transform, trim, union, } from './check.js';
|
|
12
|
+
import { ORGANIZATION_TYPES } from './organizations.js';
|
|
13
|
+
import { reauthProofCheck } from './signIn.js';
|
|
14
|
+
/** Supervision is a personal-account safety policy, never a new login/account kind. */
|
|
15
|
+
export const SUPERVISED_ACTIONS = ['profile', 'messaging', 'content', 'purchases'];
|
|
16
|
+
export const SAFETY_POLICY_VERSION = '2';
|
|
17
|
+
export const SUPERVISION_CONSENT_VERSION = '1';
|
|
18
|
+
export const SUPERVISION_STATUSES = ['active', 'orphaned', 'ended'];
|
|
19
|
+
export const SUPERVISION_INVITATION_STATUSES = ['pending', 'accepted', 'cancelled'];
|
|
20
|
+
export const PURCHASE_APPROVAL_STATUSES = [
|
|
21
|
+
'pending',
|
|
22
|
+
'approved',
|
|
23
|
+
'rejected',
|
|
24
|
+
'reserved',
|
|
25
|
+
'confirmed',
|
|
26
|
+
'cancelled',
|
|
27
|
+
];
|
|
28
|
+
export const profileAccessInputCheck = exact()(strictObject({ subjectAccountId: string(minLength(1), maxLength(128)) }));
|
|
29
|
+
export const profileAccessDecisionCheck = exact()(strictObject({
|
|
30
|
+
allowed: boolean,
|
|
31
|
+
reason: oneOf(['public', 'owner', 'private', 'blocked', 'unavailable']),
|
|
32
|
+
policyVersion: literal('1'),
|
|
33
|
+
}));
|
|
34
|
+
const accountId = string(minLength(1), maxLength(128));
|
|
35
|
+
const resourceId = string(trim, minLength(1), maxLength(256));
|
|
36
|
+
const uniqueIds = (max) => refine(array(accountId, minItems(1), maxItems(max)), (ids) => new Set(ids).size === ids.length, 'Account IDs must be unique');
|
|
37
|
+
/* -------------------------------------------------------------------------- */
|
|
38
|
+
/* Supervision management */
|
|
39
|
+
/* -------------------------------------------------------------------------- */
|
|
40
|
+
const supervisionCheck = exact()(strictObject({
|
|
41
|
+
childAccountId: accountId,
|
|
42
|
+
guardianAccountId: nullable(accountId),
|
|
43
|
+
status: oneOf(SUPERVISION_STATUSES),
|
|
44
|
+
policyVersion: literal(SAFETY_POLICY_VERSION),
|
|
45
|
+
consentVersion: literal(SUPERVISION_CONSENT_VERSION),
|
|
46
|
+
createdAt: string(datetime),
|
|
47
|
+
}));
|
|
48
|
+
export const supervisionInvitationCheck = exact()(strictObject({
|
|
49
|
+
id: accountId,
|
|
50
|
+
childAccountId: accountId,
|
|
51
|
+
guardianAccountId: accountId,
|
|
52
|
+
status: oneOf(SUPERVISION_INVITATION_STATUSES),
|
|
53
|
+
expiresAt: string(datetime),
|
|
54
|
+
consentVersion: literal(SUPERVISION_CONSENT_VERSION),
|
|
55
|
+
createdAt: string(datetime),
|
|
56
|
+
}));
|
|
57
|
+
export const supervisionStateCheck = exact()(strictObject({
|
|
58
|
+
self: nullable(supervisionCheck),
|
|
59
|
+
children: array(supervisionCheck, maxItems(100)),
|
|
60
|
+
invitations: array(supervisionInvitationCheck, maxItems(100)),
|
|
61
|
+
}));
|
|
62
|
+
export const supervisionContactsResponseCheck = exact()(strictObject({
|
|
63
|
+
items: array(strictObject({
|
|
64
|
+
childAccountId: accountId,
|
|
65
|
+
contactAccountId: accountId,
|
|
66
|
+
approvedAt: string(datetime),
|
|
67
|
+
}), maxItems(1000)),
|
|
68
|
+
}));
|
|
69
|
+
export const supervisionInviteInputCheck = exact()(strictObject({
|
|
70
|
+
guardianAccountId: accountId,
|
|
71
|
+
consentVersion: literal(SUPERVISION_CONSENT_VERSION),
|
|
72
|
+
reauth: reauthProofCheck,
|
|
73
|
+
}));
|
|
74
|
+
export const supervisionAcceptInputCheck = exact()(strictObject({ consentVersion: literal(SUPERVISION_CONSENT_VERSION), reauth: reauthProofCheck }));
|
|
75
|
+
export const supervisionContactInputCheck = exact()(strictObject({
|
|
76
|
+
childAccountId: accountId,
|
|
77
|
+
contactAccountId: accountId,
|
|
78
|
+
reauth: reauthProofCheck,
|
|
79
|
+
}));
|
|
80
|
+
export const supervisionReviewInputCheck = exact()(strictObject({
|
|
81
|
+
childAccountId: accountId,
|
|
82
|
+
action: oneOf(['end', 'transfer']),
|
|
83
|
+
guardianAccountId: optional(accountId),
|
|
84
|
+
evidenceReference: string(trim, minLength(1), maxLength(2048)),
|
|
85
|
+
reauth: reauthProofCheck,
|
|
86
|
+
}));
|
|
87
|
+
/* -------------------------------------------------------------------------- */
|
|
88
|
+
/* Minimal identities for the management screens */
|
|
89
|
+
/* -------------------------------------------------------------------------- */
|
|
90
|
+
const safetyPersonCheck = exact()(strictObject({ accountId, username: nullable(string()), displayName: nullable(string()) }));
|
|
91
|
+
export const safetyPeopleInputCheck = exact()(union(strictObject({ query: string(trim, minLength(2), maxLength(100)) }), strictObject({ accountIds: uniqueIds(100) })));
|
|
92
|
+
export const safetyPeopleResponseCheck = exact()(strictObject({ items: array(safetyPersonCheck, maxItems(100)) }));
|
|
93
|
+
export const safetyOrganizationsInputCheck = exact()(union(strictObject({
|
|
94
|
+
query: string(trim, minLength(1), maxLength(100)),
|
|
95
|
+
limit: optional(number(int, atLeast(1), atMost(20))),
|
|
96
|
+
}), strictObject({ ids: uniqueIds(100) })));
|
|
97
|
+
export const safetyOrganizationsResponseCheck = exact()(strictObject({
|
|
98
|
+
items: array(strictObject({
|
|
99
|
+
requestedAccountId: accountId,
|
|
100
|
+
organization: strictObject({
|
|
101
|
+
accountId,
|
|
102
|
+
username: nullable(string()),
|
|
103
|
+
name: string(),
|
|
104
|
+
organizationType: oneOf(ORGANIZATION_TYPES),
|
|
105
|
+
}),
|
|
106
|
+
}), maxItems(100)),
|
|
107
|
+
missingIds: array(accountId, maxItems(100)),
|
|
108
|
+
}));
|
|
109
|
+
/* -------------------------------------------------------------------------- */
|
|
110
|
+
/* Purchase approvals */
|
|
111
|
+
/* -------------------------------------------------------------------------- */
|
|
112
|
+
const fingerprint = string(pattern(/^[a-f0-9]{64}$/));
|
|
113
|
+
const minorUnits = number(int, nonnegative, atMost(Number.MAX_SAFE_INTEGER));
|
|
114
|
+
const purchaseApprovalLineCheck = exact()(strictObject({
|
|
115
|
+
itemReference: resourceId,
|
|
116
|
+
title: string(trim, minLength(1), maxLength(300)),
|
|
117
|
+
optionsText: string(trim, maxLength(300)),
|
|
118
|
+
merchantAccountId: accountId,
|
|
119
|
+
quantity: number(int, atLeast(1), atMost(1000000)),
|
|
120
|
+
unitAmountMinor: minorUnits,
|
|
121
|
+
lineAmountMinor: minorUnits,
|
|
122
|
+
}));
|
|
123
|
+
/** The effect fields every approval shape shares, in the schema's key order. */
|
|
124
|
+
const purchaseEffectFields = {
|
|
125
|
+
operationId: resourceId,
|
|
126
|
+
resourceId,
|
|
127
|
+
cartFingerprint: fingerprint,
|
|
128
|
+
summary: string(trim, minLength(1), maxLength(300)),
|
|
129
|
+
merchantAccountIds: transform(refine(array(accountId, minItems(1), maxItems(100)), (ids) => new Set(ids).size === ids.length, 'Merchant accounts must be unique'), (ids) => [...ids].sort()),
|
|
130
|
+
};
|
|
131
|
+
const purchaseAmountFields = {
|
|
132
|
+
shippingMinor: minorUnits,
|
|
133
|
+
taxMinor: minorUnits,
|
|
134
|
+
discountMinor: minorUnits,
|
|
135
|
+
roundingAdjustmentMinor: number(int, atLeast(-Number.MAX_SAFE_INTEGER), atMost(Number.MAX_SAFE_INTEGER)),
|
|
136
|
+
paymentFingerprint: fingerprint,
|
|
137
|
+
amountMinor: minorUnits,
|
|
138
|
+
currency: string(pattern(/^[A-Z]{3}$/)),
|
|
139
|
+
expiresAt: string(datetime),
|
|
140
|
+
};
|
|
141
|
+
const purchaseApprovalFields = {
|
|
142
|
+
id: accountId,
|
|
143
|
+
childAccountId: accountId,
|
|
144
|
+
applicationId: accountId,
|
|
145
|
+
status: oneOf(PURCHASE_APPROVAL_STATUSES),
|
|
146
|
+
createdAt: string(datetime),
|
|
147
|
+
receiptReference: nullable(string(maxLength(512))),
|
|
148
|
+
};
|
|
149
|
+
/** The first problem `validatePurchaseEvidence` in `../safety.ts` reports, in its order. */
|
|
150
|
+
function purchaseEvidenceProblem(value) {
|
|
151
|
+
const merchants = new Set(value.merchantAccountIds);
|
|
152
|
+
const references = new Set();
|
|
153
|
+
for (const line of value.lineItems) {
|
|
154
|
+
const key = `${line.merchantAccountId}:${line.itemReference}`;
|
|
155
|
+
if (!merchants.has(line.merchantAccountId) || references.has(key)) {
|
|
156
|
+
return {
|
|
157
|
+
path: [],
|
|
158
|
+
message: 'Purchase lines must name an included merchant and unique item reference',
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
references.add(key);
|
|
162
|
+
}
|
|
163
|
+
if (merchants.size !== new Set(value.lineItems.map((line) => line.merchantAccountId)).size) {
|
|
164
|
+
return { path: [], message: 'Every merchant must have an approval line' };
|
|
165
|
+
}
|
|
166
|
+
const total = value.lineItems.reduce((sum, line) => sum + BigInt(line.lineAmountMinor), 0n) +
|
|
167
|
+
BigInt(value.shippingMinor) +
|
|
168
|
+
BigInt(value.taxMinor) -
|
|
169
|
+
BigInt(value.discountMinor) +
|
|
170
|
+
BigInt(value.roundingAdjustmentMinor);
|
|
171
|
+
if (total < 0n ||
|
|
172
|
+
total > BigInt(Number.MAX_SAFE_INTEGER) ||
|
|
173
|
+
total !== BigInt(value.amountMinor)) {
|
|
174
|
+
return { path: [], message: 'Approval evidence must equal the exact payment total' };
|
|
175
|
+
}
|
|
176
|
+
if (Math.abs(value.roundingAdjustmentMinor) >
|
|
177
|
+
value.lineItems.length + 3 * value.merchantAccountIds.length) {
|
|
178
|
+
return { path: [], message: 'Rounding adjustment exceeds the bounded conversion stages' };
|
|
179
|
+
}
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
export const purchaseApprovalCheck = exact()(refineAt(strictObject({
|
|
183
|
+
...purchaseEffectFields,
|
|
184
|
+
lineItems: array(purchaseApprovalLineCheck, minItems(1), maxItems(200)),
|
|
185
|
+
...purchaseAmountFields,
|
|
186
|
+
...purchaseApprovalFields,
|
|
187
|
+
}), purchaseEvidenceProblem));
|
|
188
|
+
const purchaseApprovalSummaryCheck = exact()(strictObject({ ...purchaseEffectFields, ...purchaseAmountFields, ...purchaseApprovalFields }));
|
|
189
|
+
export const purchaseApprovalListCheck = exact()(strictObject({
|
|
190
|
+
items: array(purchaseApprovalSummaryCheck, maxItems(100)),
|
|
191
|
+
nextCursor: nullable(string()),
|
|
192
|
+
}));
|
|
193
|
+
export const purchaseReviewInputCheck = exact()(strictObject({ decision: oneOf(['approved', 'rejected']), reauth: reauthProofCheck }));
|