@oxy.so/contracts 2.2.0 → 4.0.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 (41) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/accountEmail.js +21 -25
  3. package/dist/cjs/deviceBoot.js +2 -2
  4. package/dist/cjs/deviceSession.js +73 -16
  5. package/dist/cjs/externalIdentity.js +9 -3
  6. package/dist/cjs/identity.js +1 -3
  7. package/dist/cjs/identityLink.js +22 -19
  8. package/dist/cjs/identityProof.js +2 -2
  9. package/dist/cjs/index.js +59 -28
  10. package/dist/cjs/reputation.js +10 -55
  11. package/dist/cjs/signIn.js +304 -0
  12. package/dist/esm/.tsbuildinfo +1 -1
  13. package/dist/esm/accountEmail.js +21 -25
  14. package/dist/esm/deviceBoot.js +2 -2
  15. package/dist/esm/deviceSession.js +72 -15
  16. package/dist/esm/externalIdentity.js +8 -2
  17. package/dist/esm/identity.js +1 -3
  18. package/dist/esm/identityLink.js +21 -18
  19. package/dist/esm/identityProof.js +2 -2
  20. package/dist/esm/index.js +11 -11
  21. package/dist/esm/reputation.js +9 -54
  22. package/dist/esm/signIn.js +299 -0
  23. package/dist/types/.tsbuildinfo +1 -1
  24. package/dist/types/accountEmail.d.ts +20 -28
  25. package/dist/types/accountGraph.d.ts +4 -4
  26. package/dist/types/deviceBoot.d.ts +2 -2
  27. package/dist/types/deviceSession.d.ts +127 -15
  28. package/dist/types/externalIdentity.d.ts +53 -46
  29. package/dist/types/identity.d.ts +4 -8
  30. package/dist/types/identityLink.d.ts +54 -118
  31. package/dist/types/identityProof.d.ts +3 -3
  32. package/dist/types/index.d.ts +8 -8
  33. package/dist/types/inference/entitlement.d.ts +2 -2
  34. package/dist/types/oauth.d.ts +16 -16
  35. package/dist/types/reputation.d.ts +32 -127
  36. package/dist/types/signIn.d.ts +717 -0
  37. package/dist/types/userResponse.d.ts +2 -2
  38. package/package.json +1 -1
  39. package/dist/cjs/webauthn.js +0 -114
  40. package/dist/esm/webauthn.js +0 -111
  41. package/dist/types/webauthn.d.ts +0 -173
@@ -4,9 +4,8 @@
4
4
  *
5
5
  * SINGLE SOURCE OF TRUTH for the reputation ledger's wire shapes: the closed
6
6
  * value sets (`REPUTATION_CATEGORIES`, `TRUST_TIERS`, …), the response entities
7
- * (`ReputationTransaction`, the two balance views, `ReputationDispute`,
8
- * `ReputationRule`, the leaderboard entry) and the request bodies the write
9
- * endpoints accept. The API validates its OUTPUT against these schemas and its
7
+ * (`ReputationTransaction`, the two balance views, `ReputationRule`, the
8
+ * leaderboard entry) and the award body. The API validates its OUTPUT against these schemas and its
10
9
  * INPUT with the same request schemas the SDK's input types are derived from;
11
10
  * `@oxy.so/core`'s reputation mixin imports every type from here rather than
12
11
  * declaring its own.
@@ -41,7 +40,7 @@
41
40
  * `require()`).
42
41
  */
43
42
  Object.defineProperty(exports, "__esModule", { value: true });
44
- exports.reverseReputationTransactionSchema = exports.upsertReputationRuleSchema = exports.resolveReputationDisputeSchema = exports.createReputationDisputeSchema = exports.awardReputationSchema = exports.reverseReputationTransactionResultSchema = exports.reputationInfluenceResultSchema = exports.reputationLeaderboardEntrySchema = exports.reputationLeaderboardUserSchema = exports.reputationRuleSchema = exports.reputationDisputeSchema = exports.reputationBalanceSchema = exports.reputationBalanceSummarySchema = exports.reputationReliabilitySchema = exports.reputationInfluenceSchema = exports.reputationBalanceBreakdownSchema = exports.reputationTransactionSchema = exports.reputationInfluenceContextSchema = exports.REPUTATION_INFLUENCE_CONTEXTS = exports.reputationDisputeStatusSchema = exports.REPUTATION_DISPUTE_STATUSES = exports.reputationTargetEntityTypeSchema = exports.REPUTATION_TARGET_ENTITY_TYPES = exports.trustTierSchema = exports.TRUST_TIERS = exports.reputationTransactionStatusSchema = exports.REPUTATION_TRANSACTION_STATUSES = exports.reputationCategorySchema = exports.REPUTATION_CATEGORIES = void 0;
43
+ exports.awardReputationSchema = exports.reputationInfluenceResultSchema = exports.reputationLeaderboardEntrySchema = exports.reputationLeaderboardUserSchema = exports.reputationRulesResponseSchema = exports.reputationRuleSchema = exports.reputationBalanceSchema = exports.reputationBalanceSummarySchema = exports.reputationReliabilitySchema = exports.reputationInfluenceSchema = exports.reputationBalanceBreakdownSchema = exports.reputationTransactionSchema = exports.reputationInfluenceContextSchema = exports.REPUTATION_INFLUENCE_CONTEXTS = exports.reputationTargetEntityTypeSchema = exports.REPUTATION_TARGET_ENTITY_TYPES = exports.trustTierSchema = exports.TRUST_TIERS = exports.reputationTransactionStatusSchema = exports.REPUTATION_TRANSACTION_STATUSES = exports.reputationCategorySchema = exports.REPUTATION_CATEGORIES = void 0;
45
44
  exports.isFullReputationBalance = isFullReputationBalance;
46
45
  const zod_1 = require("zod");
47
46
  const userResponse_1 = require("./userResponse");
@@ -75,15 +74,13 @@ exports.reputationCategorySchema = zod_1.z.enum(exports.REPUTATION_CATEGORIES);
75
74
  * Transaction lifecycle status.
76
75
  *
77
76
  * - `active` — counts toward the balance.
78
- * - `disputed` — under dispute; still counts until the dispute resolves.
79
- * - `reversed` — superseded by a compensating reversal transaction; excluded.
80
- * - `voided` — administratively excluded with no compensating entry.
77
+ * - `reversed` — superseded by a compensating reversal transaction (written
78
+ * only by policy-driven code, never by a person); the pair nets
79
+ * to zero.
81
80
  */
82
81
  exports.REPUTATION_TRANSACTION_STATUSES = [
83
82
  'active',
84
- 'disputed',
85
83
  'reversed',
86
- 'voided',
87
84
  ];
88
85
  exports.reputationTransactionStatusSchema = zod_1.z.enum(exports.REPUTATION_TRANSACTION_STATUSES);
89
86
  /**
@@ -108,15 +105,6 @@ exports.REPUTATION_TARGET_ENTITY_TYPES = [
108
105
  'other',
109
106
  ];
110
107
  exports.reputationTargetEntityTypeSchema = zod_1.z.enum(exports.REPUTATION_TARGET_ENTITY_TYPES);
111
- /** Dispute lifecycle status. */
112
- exports.REPUTATION_DISPUTE_STATUSES = [
113
- 'open',
114
- 'accepted',
115
- 'rejected',
116
- 'needs_review',
117
- ];
118
- exports.reputationDisputeStatusSchema = zod_1.z.enum(exports.REPUTATION_DISPUTE_STATUSES);
119
- /** Influence context selecting which capped weight axis to read. */
120
108
  exports.REPUTATION_INFLUENCE_CONTEXTS = [
121
109
  'default',
122
110
  'report',
@@ -221,26 +209,16 @@ const FULL_BALANCE_FIELDS = [
221
209
  function isFullReputationBalance(balance) {
222
210
  return FULL_BALANCE_FIELDS.every((field) => field in balance);
223
211
  }
224
- exports.reputationDisputeSchema = zod_1.z.object({
225
- id: zod_1.z.string(),
226
- transactionId: zod_1.z.string(),
227
- userId: zod_1.z.string(),
228
- reason: zod_1.z.string(),
229
- status: exports.reputationDisputeStatusSchema,
230
- evidence: zod_1.z.array(zod_1.z.string()).optional(),
231
- resolvedAt: zod_1.z.string().optional(),
232
- resolvedByUserId: zod_1.z.string().optional(),
233
- createdAt: zod_1.z.string(),
234
- updatedAt: zod_1.z.string(),
235
- });
236
212
  exports.reputationRuleSchema = zod_1.z.object({
237
- id: zod_1.z.string(),
238
213
  actionType: zod_1.z.string(),
239
214
  points: zod_1.z.number(),
240
215
  category: exports.reputationCategorySchema,
241
216
  description: zod_1.z.string(),
242
217
  cooldownInMinutes: zod_1.z.number(),
243
- isEnabled: zod_1.z.boolean(),
218
+ });
219
+ exports.reputationRulesResponseSchema = zod_1.z.object({
220
+ version: zod_1.z.number().int(),
221
+ rules: zod_1.z.array(exports.reputationRuleSchema),
244
222
  });
245
223
  exports.reputationLeaderboardUserSchema = zod_1.z.object({
246
224
  id: zod_1.z.string(),
@@ -260,10 +238,6 @@ exports.reputationInfluenceResultSchema = zod_1.z.object({
260
238
  weight: zod_1.z.number(),
261
239
  influence: exports.reputationInfluenceSchema,
262
240
  });
263
- exports.reverseReputationTransactionResultSchema = zod_1.z.object({
264
- original: exports.reputationTransactionSchema,
265
- reversal: exports.reputationTransactionSchema,
266
- });
267
241
  exports.awardReputationSchema = zod_1.z.object({
268
242
  userId: zod_1.z.string().trim().min(1),
269
243
  actionType: zod_1.z.string().trim().min(1),
@@ -276,22 +250,3 @@ exports.awardReputationSchema = zod_1.z.object({
276
250
  reason: zod_1.z.string().trim().max(500).optional(),
277
251
  metadata: zod_1.z.record(zod_1.z.unknown()).optional(),
278
252
  });
279
- exports.createReputationDisputeSchema = zod_1.z.object({
280
- transactionId: zod_1.z.string().trim().min(1),
281
- reason: zod_1.z.string().trim().min(1).max(1000),
282
- evidence: zod_1.z.array(zod_1.z.string().trim().min(1)).max(20).optional(),
283
- });
284
- exports.resolveReputationDisputeSchema = zod_1.z.object({
285
- status: zod_1.z.enum(['accepted', 'rejected']),
286
- });
287
- exports.upsertReputationRuleSchema = zod_1.z.object({
288
- actionType: zod_1.z.string().trim().min(1),
289
- points: zod_1.z.number(),
290
- category: exports.reputationCategorySchema,
291
- description: zod_1.z.string().trim().min(1).max(500),
292
- cooldownInMinutes: zod_1.z.number().int().min(0).default(0),
293
- isEnabled: zod_1.z.boolean().default(true),
294
- });
295
- exports.reverseReputationTransactionSchema = zod_1.z.object({
296
- reason: zod_1.z.string().trim().max(500).optional(),
297
- });
@@ -0,0 +1,304 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SIGN_IN_ERROR_CODES = exports.totpBackupCodesResponseSchema = exports.totpReauthRequestSchema = exports.totpConfirmRequestSchema = exports.totpEnrollResponseSchema = exports.signInMethodsSchema = exports.passwordSetRequestSchema = exports.emailReauthProofSchema = exports.reauthProofSchema = exports.reauthEmailStartRequestSchema = exports.REAUTH_ACTIONS = exports.emailSignInPendingSchema = exports.secondFactorRequiredSchema = exports.signUpRequestSchema = exports.secondFactorSignInRequestSchema = exports.passwordSignInRequestSchema = exports.emailSignInLinkResponseSchema = exports.emailSignInLinkRequestSchema = exports.emailSignInCollectRequestSchema = exports.emailSignInConfirmRequestSchema = exports.emailSignInCodeSchema = exports.emailSignInStartResponseSchema = exports.emailSignInStartRequestSchema = exports.secondFactorCodeSchema = exports.newPasswordSchema = exports.passwordInputSchema = exports.signInIdentifierSchema = exports.EMAIL_SIGNIN_LONG_CODE_LENGTH = exports.EMAIL_SIGNIN_LONG_CODE_ALPHABET = exports.TOTP_BACKUP_CODE_COUNT = exports.TOTP_PERIOD_SECONDS = exports.TOTP_DIGITS = exports.PASSWORD_MAX_LENGTH = exports.PASSWORD_MIN_LENGTH = exports.SIGNIN_SECOND_FACTOR_MAX_ATTEMPTS = exports.SIGNIN_SECOND_FACTOR_TTL_MS = exports.EMAIL_SIGNIN_LINK_TTL_MS = void 0;
4
+ exports.normalizeEmailSignInCode = normalizeEmailSignInCode;
5
+ exports.isSecondFactorRequired = isSecondFactorRequired;
6
+ /**
7
+ * Signing in: an email code or link, an optional password,
8
+ * and an optional authenticator app (TOTP) as a second factor.
9
+ *
10
+ * Every Oxy app runs these from its own account dialog (official apps and
11
+ * auth.oxy.so only; third parties sign in through OAuth on auth.oxy.so):
12
+ *
13
+ * 1. `POST /auth/signin/email/start` with a username or email. One email
14
+ * carries a 6-digit code AND a one-use link to auth.oxy.so; the answer is the
15
+ * same whether or not the account exists. The dialog keeps the
16
+ * `requestSecret` it is given: only it can collect the session.
17
+ * 2. Either the code (`POST /auth/signin/email/confirm`), or the link opened in
18
+ * the SAME browser (`POST /auth/signin/email/link` from auth.oxy.so, proving
19
+ * the browser's shared device) followed by the dialog's
20
+ * `POST /auth/signin/email/collect`.
21
+ * 3. Or a password (`POST /auth/signin/password`), when the account set one.
22
+ * 4. When the account has an authenticator, every first factor answers a
23
+ * one-use second-factor challenge instead of a session, and
24
+ * `POST /auth/signin/second-factor` takes the TOTP (or a backup code).
25
+ *
26
+ * `POST /auth/signup` creates an account from a username and an email confirmed
27
+ * with `POST /auth/email/verify/{start,confirm}` (purpose `signup`).
28
+ *
29
+ * Every body that ends in a session may carry `device`, the proof of the
30
+ * browser's shared device (ADR 0029 D2), so the account lands on it.
31
+ */
32
+ const zod_1 = require("zod");
33
+ const accountEmail_1 = require("./accountEmail");
34
+ const deviceSession_1 = require("./deviceSession");
35
+ /** How long the link in a sign-in email can be opened. */
36
+ exports.EMAIL_SIGNIN_LINK_TTL_MS = 15 * 60 * 1000;
37
+ /** How long a second-factor challenge can be answered. */
38
+ exports.SIGNIN_SECOND_FACTOR_TTL_MS = 5 * 60 * 1000;
39
+ /** Wrong second-factor codes one challenge accepts. */
40
+ exports.SIGNIN_SECOND_FACTOR_MAX_ATTEMPTS = 5;
41
+ /** Shortest password Oxy accepts when one is set. */
42
+ exports.PASSWORD_MIN_LENGTH = 10;
43
+ /** Longest password Oxy accepts (characters). */
44
+ exports.PASSWORD_MAX_LENGTH = 256;
45
+ /** Digits in an authenticator code. */
46
+ exports.TOTP_DIGITS = 6;
47
+ /** Seconds each authenticator code lasts. */
48
+ exports.TOTP_PERIOD_SECONDS = 30;
49
+ /** Backup codes issued when an authenticator is enabled or they are regenerated. */
50
+ exports.TOTP_BACKUP_CODE_COUNT = 10;
51
+ /**
52
+ * The long sign-in code: ten characters of Crockford base32 without the
53
+ * look-alikes (no 0, O, 1, I, L, U), shown as `XXXXX-XXXXX`. An account whose
54
+ * sign-in codes were guessed at too often today (the per-account ceiling) is
55
+ * sent this instead of 6 digits for the rest of the day, so its owner can still
56
+ * type a code while guessing one is infeasible.
57
+ */
58
+ exports.EMAIL_SIGNIN_LONG_CODE_ALPHABET = '23456789ABCDEFGHJKMNPQRSTVWXYZ';
59
+ exports.EMAIL_SIGNIN_LONG_CODE_LENGTH = 10;
60
+ /**
61
+ * Normalise a typed sign-in code: 6 digits stay as they are; a long code is
62
+ * upper-cased with its dash and spaces removed. Anything else is returned
63
+ * trimmed (and will simply be wrong).
64
+ */
65
+ function normalizeEmailSignInCode(code) {
66
+ const compact = code.trim().replace(/[\s-]/g, '').toUpperCase();
67
+ return compact;
68
+ }
69
+ const sixDigits = zod_1.z
70
+ .string()
71
+ .trim()
72
+ .regex(new RegExp(`^\\d{${accountEmail_1.EMAIL_CODE_LENGTH}}$`), `code must be ${accountEmail_1.EMAIL_CODE_LENGTH} digits`);
73
+ /**
74
+ * 32 random bytes, base64url: a request secret, a link token, a challenge id.
75
+ * The same shape as an email ticket.
76
+ */
77
+ const opaqueTokenSchema = accountEmail_1.emailTicketSchema;
78
+ /** A username or an email. */
79
+ exports.signInIdentifierSchema = zod_1.z.string().trim().min(1).max(254);
80
+ /** A password as typed. Its policy applies only when one is set. */
81
+ exports.passwordInputSchema = zod_1.z.string().min(1).max(exports.PASSWORD_MAX_LENGTH);
82
+ /** A new password. */
83
+ exports.newPasswordSchema = zod_1.z
84
+ .string()
85
+ .min(exports.PASSWORD_MIN_LENGTH, `password must be at least ${exports.PASSWORD_MIN_LENGTH} characters`)
86
+ .max(exports.PASSWORD_MAX_LENGTH, `password must be at most ${exports.PASSWORD_MAX_LENGTH} characters`);
87
+ /**
88
+ * An authenticator code (6 digits) or a backup code (10 characters, with or
89
+ * without its dash). Normalised server-side.
90
+ */
91
+ exports.secondFactorCodeSchema = zod_1.z.string().trim().min(exports.TOTP_DIGITS).max(16);
92
+ /** The device-session fields every body that ends in a session may carry. */
93
+ const sessionEnvelope = {
94
+ deviceName: zod_1.z.string().trim().min(1).max(120).optional(),
95
+ deviceFingerprint: zod_1.z.string().trim().min(1).max(256).optional(),
96
+ /** Proof of the browser's shared device; invalid → the session gets its own. */
97
+ device: deviceSession_1.deviceProofSchema.optional(),
98
+ };
99
+ /** `POST /auth/signin/email/start` */
100
+ exports.emailSignInStartRequestSchema = zod_1.z
101
+ .object({
102
+ identifier: exports.signInIdentifierSchema,
103
+ /** The requester's device: the email's link approves only in this browser. */
104
+ device: deviceSession_1.deviceProofSchema.optional(),
105
+ })
106
+ .strict();
107
+ exports.emailSignInStartResponseSchema = zod_1.z.object({
108
+ requestId: zod_1.z.string().min(1).max(64),
109
+ requestSecret: opaqueTokenSchema,
110
+ expiresAt: zod_1.z.number().int().positive(),
111
+ retryLater: zod_1.z.literal(true).optional(),
112
+ });
113
+ /**
114
+ * The code from a sign-in email: 6 digits, or — for an account whose codes
115
+ * were guessed at too often today — the 10-character long code
116
+ * (`EMAIL_SIGNIN_LONG_CODE_ALPHABET`, with or without its dash, any case). The
117
+ * UI must accept BOTH in one field: the email says which was sent, and the
118
+ * start response is the same either way (it says nothing about the account).
119
+ */
120
+ exports.emailSignInCodeSchema = zod_1.z
121
+ .string()
122
+ .trim()
123
+ .refine((value) => {
124
+ const code = normalizeEmailSignInCode(value);
125
+ return (new RegExp(`^\\d{${accountEmail_1.EMAIL_CODE_LENGTH}}$`).test(code) ||
126
+ new RegExp(`^[${exports.EMAIL_SIGNIN_LONG_CODE_ALPHABET}]{${exports.EMAIL_SIGNIN_LONG_CODE_LENGTH}}$`).test(code));
127
+ }, { message: `code must be ${accountEmail_1.EMAIL_CODE_LENGTH} digits or the ${exports.EMAIL_SIGNIN_LONG_CODE_LENGTH}-character code from the email` });
128
+ /** `POST /auth/signin/email/confirm` — the code from the email. */
129
+ exports.emailSignInConfirmRequestSchema = zod_1.z
130
+ .object({
131
+ requestId: zod_1.z.string().trim().min(1).max(64),
132
+ requestSecret: opaqueTokenSchema,
133
+ code: exports.emailSignInCodeSchema,
134
+ ...sessionEnvelope,
135
+ })
136
+ .strict();
137
+ /** `POST /auth/signin/email/collect` — the dialog asks whether its link was opened. */
138
+ exports.emailSignInCollectRequestSchema = zod_1.z
139
+ .object({
140
+ requestId: zod_1.z.string().trim().min(1).max(64),
141
+ requestSecret: opaqueTokenSchema,
142
+ ...sessionEnvelope,
143
+ })
144
+ .strict();
145
+ /**
146
+ * `POST /auth/signin/email/link` — auth.oxy.so, where the email's link lands.
147
+ * It proves auth.oxy.so's own credential for the browser's device; the request
148
+ * is approved only when that is the device that asked.
149
+ */
150
+ exports.emailSignInLinkRequestSchema = zod_1.z
151
+ .object({
152
+ token: opaqueTokenSchema,
153
+ device: deviceSession_1.deviceProofSchema,
154
+ })
155
+ .strict();
156
+ exports.emailSignInLinkResponseSchema = zod_1.z.object({
157
+ approved: zod_1.z.literal(true),
158
+ });
159
+ /** `POST /auth/signin/password` */
160
+ exports.passwordSignInRequestSchema = zod_1.z
161
+ .object({
162
+ identifier: exports.signInIdentifierSchema,
163
+ password: exports.passwordInputSchema,
164
+ ...sessionEnvelope,
165
+ })
166
+ .strict();
167
+ /** `POST /auth/signin/second-factor` */
168
+ exports.secondFactorSignInRequestSchema = zod_1.z
169
+ .object({
170
+ challengeId: opaqueTokenSchema,
171
+ code: exports.secondFactorCodeSchema,
172
+ ...sessionEnvelope,
173
+ })
174
+ .strict();
175
+ /** `POST /auth/signup` — the account, from a username and a confirmed email. */
176
+ exports.signUpRequestSchema = zod_1.z
177
+ .object({
178
+ username: zod_1.z.string().trim().min(1).max(60),
179
+ email: accountEmail_1.emailAddressSchema,
180
+ /** The ticket `POST /auth/email/verify/confirm` (purpose `signup`) returned. */
181
+ emailTicket: accountEmail_1.emailTicketSchema,
182
+ ...sessionEnvelope,
183
+ })
184
+ .strict();
185
+ exports.secondFactorRequiredSchema = zod_1.z.object({
186
+ secondFactorRequired: zod_1.z.literal(true),
187
+ challengeId: opaqueTokenSchema,
188
+ expiresAt: zod_1.z.number().int().positive(),
189
+ });
190
+ exports.emailSignInPendingSchema = zod_1.z.object({
191
+ status: zod_1.z.literal('pending'),
192
+ expiresAt: zod_1.z.number().int().positive(),
193
+ });
194
+ /** Narrow a {@link SignInStepResult}. */
195
+ function isSecondFactorRequired(result) {
196
+ return result.secondFactorRequired === true;
197
+ }
198
+ /**
199
+ * What a re-verification email code confirms. The code is bound to it: a code
200
+ * asked for one change never authorises another, and the email names it.
201
+ */
202
+ exports.REAUTH_ACTIONS = ['change_password', 'totp', 'link_commons', 'delete_account'];
203
+ /** `POST /users/me/reauth/email` */
204
+ exports.reauthEmailStartRequestSchema = zod_1.z.object({ action: zod_1.z.enum(exports.REAUTH_ACTIONS) }).strict();
205
+ /**
206
+ * A fresh proof that the person, not only their session, is asking: the
207
+ * current password, or a code just sent to the account's email
208
+ * (`POST /users/me/reauth/email`) — plus the authenticator code when the
209
+ * account has one. Carried inside the request it authorises.
210
+ */
211
+ exports.reauthProofSchema = zod_1.z
212
+ .object({
213
+ password: exports.passwordInputSchema.optional(),
214
+ emailCode: zod_1.z
215
+ .object({
216
+ verificationId: zod_1.z.string().trim().min(1).max(64),
217
+ code: sixDigits,
218
+ })
219
+ .strict()
220
+ .optional(),
221
+ /** Required when the account has an authenticator: its code or a backup code. */
222
+ totpCode: exports.secondFactorCodeSchema.optional(),
223
+ })
224
+ .strict()
225
+ .refine((proof) => proof.password !== undefined || proof.emailCode !== undefined, {
226
+ message: 'password or emailCode is required',
227
+ });
228
+ /** A proof by email code only (+ TOTP): deleting an account, linking Commons. */
229
+ exports.emailReauthProofSchema = zod_1.z
230
+ .object({
231
+ emailCode: zod_1.z
232
+ .object({
233
+ verificationId: zod_1.z.string().trim().min(1).max(64),
234
+ code: sixDigits,
235
+ })
236
+ .strict(),
237
+ totpCode: exports.secondFactorCodeSchema.optional(),
238
+ })
239
+ .strict();
240
+ /** `PUT /users/me/password` — set or change the password. */
241
+ exports.passwordSetRequestSchema = zod_1.z
242
+ .object({
243
+ newPassword: exports.newPasswordSchema,
244
+ reauth: exports.reauthProofSchema,
245
+ /** Sign every other session of the account out. */
246
+ revokeOtherSessions: zod_1.z.boolean().optional(),
247
+ })
248
+ .strict();
249
+ exports.signInMethodsSchema = zod_1.z.object({
250
+ hasEmail: zod_1.z.boolean(),
251
+ hasPassword: zod_1.z.boolean(),
252
+ totpEnabled: zod_1.z.boolean(),
253
+ backupCodesRemaining: zod_1.z.number().int().min(0),
254
+ });
255
+ exports.totpEnrollResponseSchema = zod_1.z.object({
256
+ secret: zod_1.z.string().regex(/^[A-Z2-7]+$/),
257
+ otpauthUri: zod_1.z.string().startsWith('otpauth://totp/'),
258
+ });
259
+ /** `POST /users/me/totp/confirm` — the first code from the authenticator turns it on. */
260
+ exports.totpConfirmRequestSchema = zod_1.z
261
+ .object({
262
+ code: zod_1.z.string().trim().regex(new RegExp(`^\\d{${exports.TOTP_DIGITS}}$`)),
263
+ reauth: exports.reauthProofSchema,
264
+ })
265
+ .strict();
266
+ /** `POST /users/me/totp/disable` and `POST /users/me/totp/backup-codes` */
267
+ exports.totpReauthRequestSchema = zod_1.z.object({ reauth: exports.reauthProofSchema }).strict();
268
+ exports.totpBackupCodesResponseSchema = zod_1.z.object({
269
+ backupCodes: zod_1.z.array(zod_1.z.string().regex(/^[a-z2-9]{5}-[a-z2-9]{5}$/)).length(exports.TOTP_BACKUP_CODE_COUNT),
270
+ });
271
+ /**
272
+ * Stable error codes (`error.code` in the API error body). Clients map these
273
+ * through their localization, never the English message.
274
+ */
275
+ exports.SIGN_IN_ERROR_CODES = {
276
+ /** Wrong identifier or password — never says which. */
277
+ invalidCredentials: 'SIGNIN_INVALID_CREDENTIALS',
278
+ /** The request is unknown, expired, spent, or its secret is wrong. */
279
+ requestInvalid: 'SIGNIN_REQUEST_INVALID',
280
+ /** The link is unknown, expired or already used. */
281
+ linkInvalid: 'SIGNIN_LINK_INVALID',
282
+ /** The link was opened in another browser: type the code in the app instead. */
283
+ linkOtherDevice: 'SIGNIN_LINK_OTHER_DEVICE',
284
+ /** The second-factor challenge or its code is wrong, expired or spent. */
285
+ secondFactorInvalid: 'SECOND_FACTOR_INVALID',
286
+ /** Too many failures for this account: wait and try again. */
287
+ locked: 'SIGNIN_LOCKED',
288
+ /** The step needs a fresh proof (`reauth`). */
289
+ reauthRequired: 'REAUTH_REQUIRED',
290
+ /** The fresh proof was wrong. */
291
+ reauthInvalid: 'REAUTH_INVALID',
292
+ /** The account has an authenticator: the proof must carry its code. */
293
+ totpRequired: 'TOTP_REQUIRED',
294
+ /** Enrolling or confirming an authenticator that is already on. */
295
+ totpAlreadyEnabled: 'TOTP_ALREADY_ENABLED',
296
+ /** Disabling, confirming or regenerating codes with no authenticator (or no pending one). */
297
+ totpNotEnabled: 'TOTP_NOT_ENABLED',
298
+ /** The code the authenticator showed at enrolment is wrong. */
299
+ totpCodeInvalid: 'TOTP_CODE_INVALID',
300
+ /** Only official Oxy apps and auth.oxy.so may sign people in here. */
301
+ originNotAllowed: 'SIGNIN_ORIGIN_NOT_ALLOWED',
302
+ /** The username is taken. */
303
+ usernameTaken: 'USERNAME_TAKEN',
304
+ };