@asteby/storefront-react 0.24.0 → 0.26.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/README.md +35 -2
- package/dist/account-server.cjs +128 -19
- package/dist/account-server.cjs.map +1 -1
- package/dist/account-server.d.cts +67 -4
- package/dist/account-server.d.ts +67 -4
- package/dist/account-server.js +125 -20
- package/dist/account-server.js.map +1 -1
- package/dist/account.cjs +261 -8
- package/dist/account.cjs.map +1 -1
- package/dist/account.css +99 -0
- package/dist/account.d.cts +132 -6
- package/dist/account.d.ts +132 -6
- package/dist/account.js +255 -9
- package/dist/account.js.map +1 -1
- package/dist/checkout.cjs +162 -8
- package/dist/checkout.cjs.map +1 -1
- package/dist/checkout.d.cts +104 -4
- package/dist/checkout.d.ts +104 -4
- package/dist/checkout.js +158 -10
- package/dist/checkout.js.map +1 -1
- package/dist/chunk-Z4C4LKXF.js +25 -0
- package/dist/chunk-Z4C4LKXF.js.map +1 -0
- package/dist/fit-key-B9sYQVwG.d.cts +49 -0
- package/dist/fit-key-B9sYQVwG.d.ts +49 -0
- package/dist/fitment.cjs +54 -9
- package/dist/fitment.cjs.map +1 -1
- package/dist/fitment.d.cts +20 -2
- package/dist/fitment.d.ts +20 -2
- package/dist/fitment.js +54 -10
- package/dist/fitment.js.map +1 -1
- package/dist/settings-types-jxFQFFqP.d.cts +77 -0
- package/dist/settings-types-jxFQFFqP.d.ts +77 -0
- package/dist/subjects-server.cjs +248 -0
- package/dist/subjects-server.cjs.map +1 -0
- package/dist/subjects-server.d.cts +130 -0
- package/dist/subjects-server.d.ts +130 -0
- package/dist/subjects-server.js +217 -0
- package/dist/subjects-server.js.map +1 -0
- package/dist/subjects.cjs +501 -0
- package/dist/subjects.cjs.map +1 -0
- package/dist/subjects.css +166 -0
- package/dist/subjects.d.cts +178 -0
- package/dist/subjects.d.ts +178 -0
- package/dist/subjects.js +465 -0
- package/dist/subjects.js.map +1 -0
- package/package.json +23 -2
- package/dist/types-D7pVK0GK.d.cts +0 -39
- package/dist/types-D7pVK0GK.d.ts +0 -39
package/dist/account-server.js
CHANGED
|
@@ -1,6 +1,111 @@
|
|
|
1
1
|
import { voucherOf } from './chunk-TXEYBDFG.js';
|
|
2
2
|
import { isStorefrontError, invoiceFileBytes, safeInvoiceFilename, repairPath } from '@asteby/storefront';
|
|
3
3
|
|
|
4
|
+
async function loadAccountSessions(customer, options = {}) {
|
|
5
|
+
try {
|
|
6
|
+
return await customer.customer.sessions.list(options.signal ? { signal: options.signal } : void 0);
|
|
7
|
+
} catch {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
async function loadAccountSettings(customer, options = {}) {
|
|
12
|
+
const o = options.signal ? { signal: options.signal } : void 0;
|
|
13
|
+
const [profile, capabilities, sessions] = await Promise.all([
|
|
14
|
+
customer.customer.get(o).catch(() => null),
|
|
15
|
+
customer.customer.capabilities(o).catch(() => null),
|
|
16
|
+
loadAccountSessions(customer, options)
|
|
17
|
+
]);
|
|
18
|
+
return { customer: profile, capabilities, sessions };
|
|
19
|
+
}
|
|
20
|
+
var fail = (intent, code, extra = {}) => ({
|
|
21
|
+
ok: false,
|
|
22
|
+
...intent ? { intent } : {},
|
|
23
|
+
error: { code, ...extra.field ? { field: extra.field } : {}, ...extra.retryAfter ? { retryAfter: extra.retryAfter } : {} }
|
|
24
|
+
});
|
|
25
|
+
function toSettingsFailure(err, intent, ctx = "other") {
|
|
26
|
+
if (!isStorefrontError(err)) return fail(intent, "unavailable");
|
|
27
|
+
const e = err;
|
|
28
|
+
if (e.code === "insufficient_scope") return fail(intent, "unsupported");
|
|
29
|
+
switch (e.status) {
|
|
30
|
+
case 401:
|
|
31
|
+
return fail(intent, "session_expired");
|
|
32
|
+
case 403:
|
|
33
|
+
return fail(intent, e.code === "reauth_required" ? "reauth_required" : "unsupported");
|
|
34
|
+
case 404:
|
|
35
|
+
return fail(intent, "unsupported");
|
|
36
|
+
case 409:
|
|
37
|
+
return fail(intent, e.field === "orders" ? "blocked_orders" : e.field === "pending_payments" ? "blocked_pending_payments" : "validation", e.field ? { field: e.field } : {});
|
|
38
|
+
case 429: {
|
|
39
|
+
const wait = typeof e.retryAfter === "number" && Number.isFinite(e.retryAfter) && e.retryAfter > 0 ? Math.min(3600, Math.ceil(e.retryAfter)) : void 0;
|
|
40
|
+
return fail(intent, "rate_limited", wait ? { retryAfter: wait } : {});
|
|
41
|
+
}
|
|
42
|
+
case 400:
|
|
43
|
+
case 422:
|
|
44
|
+
return ctx === "credential" ? fail(intent, "invalid_credential") : fail(intent, "validation", e.field ? { field: e.field } : {});
|
|
45
|
+
default:
|
|
46
|
+
return fail(intent, "unavailable");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function createAccountSettingsAction(options) {
|
|
50
|
+
const { storefront: sf, customerCookie } = options;
|
|
51
|
+
return async function settingsAction({ request }) {
|
|
52
|
+
if (request.headers.get("Sec-Fetch-Site")?.toLowerCase() === "cross-site") return fail(void 0, "validation");
|
|
53
|
+
const form = await request.formData().catch(() => null);
|
|
54
|
+
if (!form) return fail(void 0, "validation");
|
|
55
|
+
const text = (name) => {
|
|
56
|
+
const v = form.get(name);
|
|
57
|
+
return typeof v === "string" ? v : "";
|
|
58
|
+
};
|
|
59
|
+
const intent = text("intent");
|
|
60
|
+
const raw = await customerCookie.parse(request.headers.get("Cookie"));
|
|
61
|
+
const token = typeof raw === "string" && raw ? raw : null;
|
|
62
|
+
if (!token) return fail(intent, "session_expired");
|
|
63
|
+
const ip = options.clientIp?.(request);
|
|
64
|
+
const w = ip ? { clientIp: ip } : void 0;
|
|
65
|
+
const customer = sf.withCustomerToken(token);
|
|
66
|
+
try {
|
|
67
|
+
switch (intent) {
|
|
68
|
+
case "session-revoke": {
|
|
69
|
+
const id = text("session_id").trim();
|
|
70
|
+
if (!id || id.length > 64) return fail(intent, "validation", { field: "session_id" });
|
|
71
|
+
if (id.toLowerCase() === "others") return fail(intent, "validation", { field: "session_id" });
|
|
72
|
+
await customer.customer.sessions.revoke(id, w);
|
|
73
|
+
return { ok: true, intent };
|
|
74
|
+
}
|
|
75
|
+
case "session-revoke-others": {
|
|
76
|
+
await customer.customer.sessions.revokeOthers(w);
|
|
77
|
+
return { ok: true, intent };
|
|
78
|
+
}
|
|
79
|
+
case "locale": {
|
|
80
|
+
const locale = text("locale").trim();
|
|
81
|
+
if (locale.length > 35) return fail(intent, "validation", { field: "locale" });
|
|
82
|
+
await customer.customer.update({ locale: locale || null }, w);
|
|
83
|
+
return { ok: true, intent, saved: true };
|
|
84
|
+
}
|
|
85
|
+
case "notifications": {
|
|
86
|
+
const shown = form.getAll("pref_shown").filter((v) => typeof v === "string");
|
|
87
|
+
const checked = new Set(form.getAll("pref").filter((v) => typeof v === "string"));
|
|
88
|
+
if (shown.length === 0 || shown.length > 30) return fail(intent, "validation", { field: "preferences" });
|
|
89
|
+
const notifications = {};
|
|
90
|
+
for (const pair of shown) {
|
|
91
|
+
const m = /^([a-z][a-z0-9_]{0,31}):([a-z][a-z0-9_]{0,31})$/.exec(pair);
|
|
92
|
+
if (!m) return fail(intent, "validation", { field: "preferences" });
|
|
93
|
+
(notifications[m[1]] ??= {})[m[2]] = checked.has(pair);
|
|
94
|
+
}
|
|
95
|
+
await customer.customer.update({ preferences: { notifications } }, w);
|
|
96
|
+
return { ok: true, intent, saved: true };
|
|
97
|
+
}
|
|
98
|
+
default:
|
|
99
|
+
return fail(void 0, "validation");
|
|
100
|
+
}
|
|
101
|
+
} catch (err) {
|
|
102
|
+
if (intent === "session-revoke" && isStorefrontError(err) && err.status === 404) return { ok: true, intent };
|
|
103
|
+
return toSettingsFailure(err, intent);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/account/server.ts
|
|
4
109
|
var SAFE_AUTH_CAPABILITIES = Object.freeze({
|
|
5
110
|
password: true,
|
|
6
111
|
email_verification: false,
|
|
@@ -60,32 +165,32 @@ function safeNextPath(raw, fallback = "/account") {
|
|
|
60
165
|
if (!v.startsWith("/") || v.startsWith("//") || v.startsWith("/\\") || /[\u0000-\u001f\\]/.test(v)) return fallback;
|
|
61
166
|
return v;
|
|
62
167
|
}
|
|
63
|
-
var
|
|
168
|
+
var fail2 = (code, extra = {}, field) => ({
|
|
64
169
|
ok: false,
|
|
65
170
|
...extra,
|
|
66
171
|
error: { code, ...field ? { field } : {} }
|
|
67
172
|
});
|
|
68
173
|
function toFailure(err, ctx, extra) {
|
|
69
|
-
if (!isStorefrontError(err)) return
|
|
174
|
+
if (!isStorefrontError(err)) return fail2("unavailable", extra);
|
|
70
175
|
if (err.code === "insufficient_scope") {
|
|
71
176
|
logMisconfigured(err.requiredScopes);
|
|
72
|
-
return
|
|
177
|
+
return fail2("store_misconfigured", extra);
|
|
73
178
|
}
|
|
74
179
|
switch (err.status) {
|
|
75
180
|
case 401:
|
|
76
|
-
return
|
|
181
|
+
return fail2(ctx === "credentials" ? "invalid_credentials" : ctx === "code" ? "invalid_code" : "unavailable", extra, ctx === "code" ? "code" : void 0);
|
|
77
182
|
case 409:
|
|
78
|
-
return
|
|
183
|
+
return fail2("email_taken", extra, "email");
|
|
79
184
|
case 429:
|
|
80
|
-
return
|
|
185
|
+
return fail2("rate_limited", extra);
|
|
81
186
|
case 400:
|
|
82
187
|
case 422:
|
|
83
|
-
return
|
|
188
|
+
return fail2("validation", extra, err.field);
|
|
84
189
|
case 403:
|
|
85
190
|
case 404:
|
|
86
|
-
return
|
|
191
|
+
return fail2("unsupported", extra);
|
|
87
192
|
default:
|
|
88
|
-
return
|
|
193
|
+
return fail2("unavailable", extra);
|
|
89
194
|
}
|
|
90
195
|
}
|
|
91
196
|
function createAuthAction(options) {
|
|
@@ -99,7 +204,7 @@ function createAuthAction(options) {
|
|
|
99
204
|
}
|
|
100
205
|
return async function authAction({ request }) {
|
|
101
206
|
const form = await request.formData().catch(() => null);
|
|
102
|
-
if (!form) return
|
|
207
|
+
if (!form) return fail2("validation");
|
|
103
208
|
const text = (name) => {
|
|
104
209
|
const v = form.get(name);
|
|
105
210
|
return typeof v === "string" ? v : "";
|
|
@@ -112,22 +217,22 @@ function createAuthAction(options) {
|
|
|
112
217
|
const ip = options.clientIp?.(request);
|
|
113
218
|
const w = ip ? { clientIp: ip } : void 0;
|
|
114
219
|
const carry = { email };
|
|
115
|
-
if (!email) return
|
|
220
|
+
if (!email) return fail2("validation", carry, "email");
|
|
116
221
|
const caps = await loadCaps(sf);
|
|
117
222
|
const passwordOn = caps.password;
|
|
118
223
|
const misconfigured = caps.unavailable_reason === "missing_scope";
|
|
119
|
-
const off = () =>
|
|
224
|
+
const off = () => fail2(misconfigured ? "store_misconfigured" : "unsupported", carry);
|
|
120
225
|
const otpOn = caps.otp_channels.includes("email");
|
|
121
226
|
try {
|
|
122
227
|
switch (intent) {
|
|
123
228
|
case "login": {
|
|
124
229
|
if (!passwordOn) return off();
|
|
125
|
-
if (!password) return
|
|
230
|
+
if (!password) return fail2("validation", carry, "password");
|
|
126
231
|
return await signedIn(await sf.customer.loginWithPassword({ email, password }, w), next);
|
|
127
232
|
}
|
|
128
233
|
case "register": {
|
|
129
234
|
if (!passwordOn) return off();
|
|
130
|
-
if (!password) return
|
|
235
|
+
if (!password) return fail2("validation", carry, "password");
|
|
131
236
|
const res = await sf.customer.register(
|
|
132
237
|
{ email, password, ...text("first_name").trim() ? { first_name: text("first_name").trim() } : {}, ...text("last_name").trim() ? { last_name: text("last_name").trim() } : {} },
|
|
133
238
|
w
|
|
@@ -135,7 +240,7 @@ function createAuthAction(options) {
|
|
|
135
240
|
return res.status === "created" ? await signedIn(res.session, next) : { ok: true, step: "verify", email };
|
|
136
241
|
}
|
|
137
242
|
case "verify": {
|
|
138
|
-
if (!code) return
|
|
243
|
+
if (!code) return fail2("validation", carry, "code");
|
|
139
244
|
return await signedIn(await sf.customer.verifyRegistration({ email, code }, w), next);
|
|
140
245
|
}
|
|
141
246
|
case "otp-request": {
|
|
@@ -145,7 +250,7 @@ function createAuthAction(options) {
|
|
|
145
250
|
}
|
|
146
251
|
case "otp-login": {
|
|
147
252
|
if (!otpOn) return off();
|
|
148
|
-
if (!code) return
|
|
253
|
+
if (!code) return fail2("validation", carry, "code");
|
|
149
254
|
return await signedIn(await sf.customer.createSession({ email, code }, w), next);
|
|
150
255
|
}
|
|
151
256
|
case "forgot": {
|
|
@@ -159,13 +264,13 @@ function createAuthAction(options) {
|
|
|
159
264
|
}
|
|
160
265
|
case "reset": {
|
|
161
266
|
if (!passwordOn) return off();
|
|
162
|
-
if (!code) return
|
|
163
|
-
if (!password) return
|
|
267
|
+
if (!code) return fail2("validation", carry, "code");
|
|
268
|
+
if (!password) return fail2("validation", carry, "password");
|
|
164
269
|
await sf.customer.resetPassword({ email, code, password }, w);
|
|
165
270
|
return await signedIn(await sf.customer.loginWithPassword({ email, password }, w), next);
|
|
166
271
|
}
|
|
167
272
|
default:
|
|
168
|
-
return
|
|
273
|
+
return fail2("validation", carry);
|
|
169
274
|
}
|
|
170
275
|
} catch (err) {
|
|
171
276
|
const step = intent === "verify" ? "verify" : intent === "otp-login" ? "otp" : intent === "reset" || intent === "forgot" ? "reset" : void 0;
|
|
@@ -245,6 +350,6 @@ async function pendingVoucherResponse({ storefront, id, download = false, shopNa
|
|
|
245
350
|
}
|
|
246
351
|
}
|
|
247
352
|
|
|
248
|
-
export { MAX_INVOICE_FILE_BYTES, SAFE_AUTH_CAPABILITIES, createAuthAction, invoiceFileResponse, loadAuthCapabilities, pendingVoucherResponse, safeNextPath };
|
|
353
|
+
export { MAX_INVOICE_FILE_BYTES, SAFE_AUTH_CAPABILITIES, createAccountSettingsAction, createAuthAction, invoiceFileResponse, loadAccountSessions, loadAccountSettings, loadAuthCapabilities, pendingVoucherResponse, safeNextPath, toSettingsFailure };
|
|
249
354
|
//# sourceMappingURL=account-server.js.map
|
|
250
355
|
//# sourceMappingURL=account-server.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/account/server.ts"],"names":[],"mappings":"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","file":"account-server.js","sourcesContent":["/**\n * Server side of the account screen (`@asteby/storefront-react/account/server`): `loadAuthCapabilities` for the loader\n * and `createAuthAction` for the action. No React, no router: Web `Request`/`Response` only, so any framework mounts it.\n *\n * The browser never talks to OPS. The action signs the buyer in with the store's `sk_` client and keeps the session\n * token in the store's own cookie, which is passed in (`customerCookie`), so name, flags and signing stay the store's.\n */\nimport { invoiceFileBytes, isStorefrontError, repairPath, safeInvoiceFilename, type CustomerCapabilities, type CustomerSession, type StorefrontClient } from '@asteby/storefront'\nimport { voucherOf } from './voucher.js'\nimport type { AuthActionData, AuthErrorCode, AuthIntent } from './types.js'\n\nexport type { AuthActionData, AuthErrorCode, AuthIntent, AuthStep } from './types.js'\n\n/** What is assumed when the store cannot be asked: password sign-in works, nothing else is offered. */\nexport const SAFE_AUTH_CAPABILITIES: Readonly<CustomerCapabilities> = Object.freeze({\n password: true,\n email_verification: false,\n otp_channels: Object.freeze([]) as unknown as CustomerCapabilities['otp_channels'],\n})\n\nconst FAILURE_TTL_MS = 5_000\nconst LOG_EVERY_MS = 60_000\nlet lastLogAt = -Infinity\n\n/** Server log (never the buyer's data): says what is missing and where it is repaired. At most once a minute. */\nfunction logMisconfigured(scopes: readonly string[]): void {\n const t = Date.now()\n if (t - lastLogAt < LOG_EVERY_MS) return\n lastLogAt = t\n console.error(\n `[@asteby/storefront-react] La credencial de la tienda no tiene permisos${scopes.length ? ` (${scopes.join(', ')})` : ''}; ` +\n `las cuentas de comprador no funcionan hasta repararlo en ${repairPath()}.`,\n )\n}\nconst cache = new WeakMap<object, { at: number; ok: boolean; value: CustomerCapabilities }>()\n\nexport type LoadAuthCapabilitiesOptions = {\n /** How long a good answer is reused per client (default 60 s). `0` disables the cache. */\n ttlMs?: number\n signal?: AbortSignal\n /** Clock, for tests. */\n now?: () => number\n}\n\n/**\n * Asks the store which ways of signing in it offers, with a short cache per client. It NEVER throws: when OPS does not\n * answer (older server, outage, token without scope) it returns `SAFE_AUTH_CAPABILITIES` and retries after a few\n * seconds, so the account screen always renders. Use it as the loader: `loader = () => loadAuthCapabilities(storefront)`.\n */\nexport async function loadAuthCapabilities(sf: StorefrontClient, options: LoadAuthCapabilitiesOptions = {}): Promise<CustomerCapabilities> {\n const now = options.now ?? Date.now\n const ttl = options.ttlMs ?? 60_000\n const hit = cache.get(sf)\n if (hit && now() - hit.at < (hit.ok ? ttl : FAILURE_TTL_MS)) return hit.value\n try {\n const got = await sf.customer.capabilities(options.signal ? { signal: options.signal } : undefined)\n const missing = Array.isArray(got?.missing_scopes) ? got.missing_scopes.filter((x): x is string => typeof x === 'string') : []\n const value: CustomerCapabilities = {\n // `password: false` (including for a missing scope) is respected: the form never offers what the token cannot do.\n password: got?.password !== false,\n email_verification: got?.email_verification === true,\n otp_channels: Array.isArray(got?.otp_channels) ? got.otp_channels : [],\n ...(typeof got?.unavailable_reason === 'string' ? { unavailable_reason: got.unavailable_reason } : {}),\n ...(missing.length ? { missing_scopes: missing } : {}),\n }\n if (value.unavailable_reason === 'missing_scope') logMisconfigured(missing)\n cache.set(sf, { at: now(), ok: true, value })\n return value\n } catch (err) {\n if (isStorefrontError(err) && err.code === 'insufficient_scope') {\n // The token cannot even read capabilities: do not offer password sign-in it could not complete.\n const value: CustomerCapabilities = {\n password: false,\n email_verification: false,\n otp_channels: [],\n unavailable_reason: 'missing_scope',\n missing_scopes: err.requiredScopes,\n }\n logMisconfigured(err.requiredScopes)\n cache.set(sf, { at: now(), ok: false, value })\n return value\n }\n cache.set(sf, { at: now(), ok: false, value: SAFE_AUTH_CAPABILITIES })\n return SAFE_AUTH_CAPABILITIES\n }\n}\n\n/** The part of a cookie the action needs. React Router's `createCookie(...)` and Remix's fit as they are. */\nexport type AuthCookie = {\n serialize(value: string, options?: { expires?: Date; maxAge?: number }): string | Promise<string>\n}\n\n/** Allows only a path inside the site (`/account`, `/orders?x=1`); anything else is `fallback`. Blocks `//host` and `/\\host`. */\nexport function safeNextPath(raw: string | null | undefined, fallback = '/account'): string {\n if (typeof raw !== 'string') return fallback\n const v = raw.trim()\n if (!v.startsWith('/') || v.startsWith('//') || v.startsWith('/\\\\') || /[\\u0000-\\u001f\\\\]/.test(v)) return fallback\n return v\n}\n\nexport type CreateAuthActionOptions = {\n storefront: StorefrontClient\n /** The store's cookie for the buyer session token (`createCookie('sf_customer', ...)`). */\n customerCookie: AuthCookie\n /** Validates the `next` field before redirecting (default `safeNextPath`). Return a same-site path. */\n safeNext?: (raw: string | null) => string\n /** Where to go after signing in when there is no `next` (default `/account`). Only used by the default `safeNext`. */\n defaultNext?: string\n /** Shopper IP for the per-shopper rate limit (see `clientIp` of the client). */\n clientIp?: (request: Request) => string | null | undefined\n /** Capabilities source, for tests or a shared cache (default `loadAuthCapabilities`). */\n capabilities?: (sf: StorefrontClient) => Promise<CustomerCapabilities>\n}\n\nexport type AuthActionArgs = { request: Request }\n\nconst fail = (code: AuthErrorCode, extra: Partial<AuthActionData> = {}, field?: string): AuthActionData => ({\n ok: false,\n ...extra,\n error: { code, ...(field ? { field } : {}) },\n})\n\n/** Maps what the SDK threw to the error the form shows. `ctx` tells what a 401 means on this step. */\nfunction toFailure(err: unknown, ctx: 'credentials' | 'code' | 'other', extra: Partial<AuthActionData>): AuthActionData {\n if (!isStorefrontError(err)) return fail('unavailable', extra)\n if (err.code === 'insufficient_scope') {\n logMisconfigured(err.requiredScopes)\n return fail('store_misconfigured', extra)\n }\n switch (err.status) {\n case 401:\n return fail(ctx === 'credentials' ? 'invalid_credentials' : ctx === 'code' ? 'invalid_code' : 'unavailable', extra, ctx === 'code' ? 'code' : undefined)\n case 409:\n return fail('email_taken', extra, 'email')\n case 429:\n return fail('rate_limited', extra)\n case 400:\n case 422:\n return fail('validation', extra, err.field)\n case 403:\n case 404:\n return fail('unsupported', extra)\n default:\n return fail('unavailable', extra)\n }\n}\n\n/**\n * The account action: one `POST` form with an `intent` field.\n *\n * | intent | fields | result |\n * | ------------- | ---------------------------------------- | -------------------------------------------------------- |\n * | `login` | email, password | signs in -> redirect |\n * | `register` | email, password, first_name?, last_name? | `step: 'verify'` or, without verification, redirect |\n * | `verify` | email, code | account ready -> redirect |\n * | `otp-request` | email | `step: 'otp'` (code sent) |\n * | `otp-login` | email, code | signs in -> redirect |\n * | `forgot` | email | `step: 'reset'` (always, so it never says who has an account) |\n * | `reset` | email, code, password | new password and signed in -> redirect |\n *\n * Success is a `303` to `next` (validated by `safeNext`) with `Set-Cookie` holding the session token; anything else is\n * an `AuthActionData` to render with `<AccountAuth data={...}>`. Passwords and codes are never echoed back.\n */\nexport function createAuthAction(options: CreateAuthActionOptions) {\n const { storefront: sf, customerCookie } = options\n const safeNext = options.safeNext ?? ((raw: string | null) => safeNextPath(raw, options.defaultNext ?? '/account'))\n const loadCaps = options.capabilities ?? ((client: StorefrontClient) => loadAuthCapabilities(client))\n\n async function signedIn(session: CustomerSession, next: string): Promise<Response> {\n const expires = new Date(session.expires_at)\n const cookie = await customerCookie.serialize(session.token, Number.isNaN(expires.getTime()) ? undefined : { expires })\n return new Response(null, { status: 303, headers: { Location: next, 'Set-Cookie': cookie } })\n }\n\n return async function authAction({ request }: AuthActionArgs): Promise<Response | AuthActionData> {\n const form = await request.formData().catch(() => null)\n if (!form) return fail('validation')\n const text = (name: string) => {\n const v = form.get(name)\n return typeof v === 'string' ? v : ''\n }\n const intent = text('intent') as AuthIntent\n const email = text('email').trim()\n const password = text('password')\n const code = text('code').replace(/\\s+/g, '')\n const next = safeNext(text('next') || null)\n const ip = options.clientIp?.(request)\n const w = ip ? { clientIp: ip } : undefined\n const carry: Partial<AuthActionData> = { email }\n if (!email) return fail('validation', carry, 'email')\n\n const caps = await loadCaps(sf)\n const passwordOn = caps.password\n // Off because the store's token lacks a scope (not a merchant choice): say so instead of \"not available\".\n const misconfigured = caps.unavailable_reason === 'missing_scope'\n const off = (): AuthActionData => fail(misconfigured ? 'store_misconfigured' : 'unsupported', carry)\n const otpOn = caps.otp_channels.includes('email')\n\n try {\n switch (intent) {\n case 'login': {\n if (!passwordOn) return off()\n if (!password) return fail('validation', carry, 'password')\n return await signedIn(await sf.customer.loginWithPassword({ email, password }, w), next)\n }\n case 'register': {\n if (!passwordOn) return off()\n if (!password) return fail('validation', carry, 'password')\n const res = await sf.customer.register(\n { email, password, ...(text('first_name').trim() ? { first_name: text('first_name').trim() } : {}), ...(text('last_name').trim() ? { last_name: text('last_name').trim() } : {}) },\n w,\n )\n return res.status === 'created' ? await signedIn(res.session, next) : { ok: true, step: 'verify', email }\n }\n case 'verify': {\n if (!code) return fail('validation', carry, 'code')\n return await signedIn(await sf.customer.verifyRegistration({ email, code }, w), next)\n }\n case 'otp-request': {\n if (!otpOn) return off()\n await sf.customer.requestOtp({ email }, w)\n return { ok: true, step: 'otp', email }\n }\n case 'otp-login': {\n if (!otpOn) return off()\n if (!code) return fail('validation', carry, 'code')\n return await signedIn(await sf.customer.createSession({ email, code }, w), next)\n }\n case 'forgot': {\n if (!passwordOn || !otpOn) return off()\n try {\n await sf.customer.requestOtp({ email }, w)\n } catch (err) {\n // Only throttling and a bad email are told; anything else looks the same as success (no account enumeration).\n if (isStorefrontError(err) && (err.status === 429 || err.status === 400)) throw err\n }\n return { ok: true, step: 'reset', email }\n }\n case 'reset': {\n if (!passwordOn) return off()\n if (!code) return fail('validation', carry, 'code')\n if (!password) return fail('validation', carry, 'password')\n await sf.customer.resetPassword({ email, code, password }, w)\n return await signedIn(await sf.customer.loginWithPassword({ email, password }, w), next)\n }\n default:\n return fail('validation', carry)\n }\n } catch (err) {\n const step = intent === 'verify' ? 'verify' : intent === 'otp-login' ? 'otp' : intent === 'reset' || intent === 'forgot' ? 'reset' : undefined\n const ctx = intent === 'login' ? 'credentials' : step ? 'code' : 'other'\n return toFailure(err, ctx, step && intent !== 'forgot' ? { ...carry, step } : carry)\n }\n }\n}\n\nexport type AuthAction = ReturnType<typeof createAuthAction>\n\n// ---------------------------------------------------------------------------\n// Resource routes of the order screens: the invoice file and the printable voucher\n// ---------------------------------------------------------------------------\n\n/** Headers every answer of a buyer route carries, error or not: never cached, never sniffed, never rendered as a page. */\nconst LOCKED_HEADERS = {\n 'Cache-Control': 'private, no-store',\n 'X-Content-Type-Options': 'nosniff',\n 'Referrer-Policy': 'no-referrer',\n 'X-Robots-Tag': 'noindex',\n} as const\nconst TEXT_HEADERS = { ...LOCKED_HEADERS, 'Content-Type': 'text/plain; charset=utf-8', 'Content-Security-Policy': \"default-src 'none'; sandbox\" } as const\nconst textResponse = (status: number, body: string, extra: Record<string, string> = {}) => new Response(body, { status, headers: { ...TEXT_HEADERS, ...extra } })\n\n/** Largest invoice file served (a CFDI PDF is well under 1 MB): anything bigger is refused before it is decoded. */\nexport const MAX_INVOICE_FILE_BYTES = 10 * 1024 * 1024\n\n/** Statuses a buyer-facing route answers with; anything unexpected is a plain 502 (never the upstream message). */\nfunction failureResponse(err: unknown): Response {\n if (isStorefrontError(err)) {\n if (err.status === 401) return textResponse(401, 'Sesion vencida')\n if (err.status === 404) return textResponse(404, 'No encontrado')\n if (err.status === 429) {\n const wait = typeof err.retryAfter === 'number' && Number.isFinite(err.retryAfter) && err.retryAfter > 0 ? Math.min(3600, Math.ceil(err.retryAfter)) : null\n return textResponse(429, 'Demasiados intentos', wait ? { 'Retry-After': String(wait) } : {})\n }\n }\n return textResponse(502, 'No pudimos obtener el archivo')\n}\n\nconst FILE_TYPES: Record<string, string> = { pdf: 'application/pdf', xml: 'application/xml' }\n\nexport type InvoiceFileResponseOptions = {\n /** Client WITH the buyer's session (`storefront.withCustomerToken(token)`). */\n storefront: StorefrontClient\n orderId: string\n invoiceId: string\n /** From the URL (`pdf` | `xml`); anything else is a 404. */\n kind: string | undefined\n signal?: AbortSignal\n /** Largest file served, in bytes (default `MAX_INVOICE_FILE_BYTES`, 10 MB); a bigger one is a generic 502. */\n maxBytes?: number\n}\n\n/**\n * Serves one file of an invoice or credit note to the signed-in buyer: the route handler of the store\n * (`account.order.$id.invoice.$doc.$kind.ts`) is `return invoiceFileResponse({ storefront, orderId, invoiceId, kind })`.\n * The buyer's token and any PAC URL stay on the server; the browser gets bytes with `Content-Disposition: attachment`,\n * `Cache-Control: private, no-store`, `nosniff` and a sandboxing CSP. The content type is taken from `kind`, never\n * from what the API says, so a file cannot make the browser render HTML under your origin. 401/404/429 pass through;\n * anything else is a generic 502 with no upstream detail.\n */\nexport async function invoiceFileResponse({ storefront, orderId, invoiceId, kind, signal, maxBytes = MAX_INVOICE_FILE_BYTES }: InvoiceFileResponseOptions): Promise<Response> {\n if (kind !== 'pdf' && kind !== 'xml') return textResponse(404, 'No encontrado')\n if (!orderId || !invoiceId) return textResponse(404, 'No encontrado')\n try {\n const file = await storefront.customer.invoiceFile(orderId, invoiceId, kind, signal ? { signal } : undefined)\n if (!file) return textResponse(404, 'No encontrado')\n // Checked on the encoded text (4 chars per 3 bytes, whitespace aside) so a huge answer is never decoded.\n if (file.content_base64.length > Math.ceil(maxBytes / 3) * 4 * 1.1 + 64) return textResponse(502, 'No pudimos obtener el archivo')\n const bytes = invoiceFileBytes(file)\n if (bytes.byteLength > maxBytes) return textResponse(502, 'No pudimos obtener el archivo')\n const name = safeInvoiceFilename(file.filename, kind === 'pdf' ? 'factura.pdf' : 'factura.xml')\n const filename = name.toLowerCase().endsWith(`.${kind}`) ? name : `${name}.${kind}`\n return new Response(bytes as BodyInit, {\n status: 200,\n headers: {\n 'Content-Type': FILE_TYPES[kind]!,\n 'Content-Length': String(bytes.byteLength),\n 'Content-Disposition': `attachment; filename=\"${filename}\"`,\n ...LOCKED_HEADERS,\n 'Content-Security-Policy': \"default-src 'none'; sandbox\",\n },\n })\n } catch (err) {\n return failureResponse(err)\n }\n}\n\nexport type PendingVoucherResponseOptions = {\n /** Client WITH the buyer's session. */\n storefront: StorefrontClient\n /** Checkout id (`PendingPayment.id`). */\n id: string\n /** `?descargar=1`: deliver it as a file instead of showing it. */\n download?: boolean\n shopName?: string\n locale?: string\n timeZone?: string\n signal?: AbortSignal\n}\n\n/**\n * The printable A4 voucher of a pending payment of the signed-in buyer. Served only when `id` is in HIS list (the list\n * is scoped by buyer in OPS): any other id is a 404. The document is self-contained, so its CSP allows nothing\n * external, only its own print script (with a per-response nonce) and inline styles.\n */\nexport async function pendingVoucherResponse({ storefront, id, download = false, shopName, locale, timeZone, signal }: PendingVoucherResponseOptions): Promise<Response> {\n try {\n const page = await storefront.customer.pendingPayments({ first: 100 }, signal ? { signal } : undefined)\n const item = page.nodes.find((p) => p.id === id)\n const voucher = item ? voucherOf(item) : null\n if (!item || !voucher) return textResponse(404, 'Ficha no encontrada')\n const nonce = btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(16))))\n const { renderVoucherDocument } = await import('./voucher-document.js')\n const html = renderVoucherDocument(voucher, { ...(shopName ? { shopName } : {}), ...(locale ? { locale } : {}), ...(timeZone ? { timeZone } : {}), printButton: !download, nonce })\n const safeId = id.replace(/[^A-Za-z0-9-]/g, '').slice(0, 8) || 'ficha'\n return new Response(html, {\n status: 200,\n headers: {\n 'Content-Type': 'text/html; charset=utf-8',\n ...LOCKED_HEADERS,\n // The download carries no script, so its policy allows none.\n 'Content-Security-Policy': `default-src 'none'; style-src 'unsafe-inline'; img-src data:; script-src ${download ? \"'none'\" : `'nonce-${nonce}'`}; base-uri 'none'; form-action 'none'; frame-ancestors 'self'`,\n ...(download ? { 'Content-Disposition': `attachment; filename=\"ficha-de-pago-${safeId}.html\"` } : {}),\n },\n })\n } catch (err) {\n return failureResponse(err)\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/account/settings-server.ts","../src/account/server.ts"],"names":["isStorefrontError","fail"],"mappings":"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","file":"account-server.js","sourcesContent":["/**\n * Server side of the account settings (`@asteby/storefront-react/account/server`): `loadAccountSessions` for the loader\n * and `createAccountSettingsAction` for the action. No React, no router: Web `Request`/`Response` only.\n *\n * The browser never talks to OPS and never sees the buyer's token: the action reads it from the store's own cookie\n * (passed in), builds a client with it (`withCustomerToken`) and calls the contract. Answers carry codes\n * (`SettingsErrorCode`), never a server message, a value of the buyer or a credential; nothing is logged here.\n */\nimport { isStorefrontError, type Customer, type CustomerCapabilities, type CustomerSessionInfo, type StorefrontClient, type StorefrontError } from '@asteby/storefront'\nimport type { AuthCookie } from './server.js'\nimport type { AccountSettingsActionData, SettingsErrorCode, SettingsIntent } from './settings-types.js'\n\nexport type { AccountSettingsActionData, SettingsErrorCode, SettingsIntent } from './settings-types.js'\n\n/** The store's cookie for the buyer session token. React Router's `createCookie(...)` and Remix's fit as they are. */\nexport type SettingsCookie = AuthCookie & {\n parse(header: string | null): unknown | Promise<unknown>\n}\n\n/**\n * The buyer's sessions for the loader. NEVER throws: a store whose server is older than contract 1.9, an outage or an\n * expired session is `null`, and `<SessionsList sessions={null}>` degrades while the rest of the page renders.\n */\nexport async function loadAccountSessions(customer: StorefrontClient, options: { signal?: AbortSignal } = {}): Promise<CustomerSessionInfo[] | null> {\n try {\n return await customer.customer.sessions.list(options.signal ? { signal: options.signal } : undefined)\n } catch {\n return null\n }\n}\n\n/** Everything the settings page reads, each part on its own: a part that cannot be read is `null` and its section degrades. */\nexport type AccountSettingsData = {\n customer: Customer | null\n capabilities: CustomerCapabilities | null\n sessions: CustomerSessionInfo[] | null\n}\n\n/**\n * The loader of the whole settings page. NEVER throws and never fails as a whole: the three reads run together and a\n * failed one (older server, outage, expired session) is `null` for that part only. `customer` is `null` also when the\n * buyer's session is dead: the page should then send them to sign in.\n */\nexport async function loadAccountSettings(customer: StorefrontClient, options: { signal?: AbortSignal } = {}): Promise<AccountSettingsData> {\n const o = options.signal ? { signal: options.signal } : undefined\n const [profile, capabilities, sessions] = await Promise.all([\n customer.customer.get(o).catch(() => null),\n customer.customer.capabilities(o).catch(() => null),\n loadAccountSessions(customer, options),\n ])\n return { customer: profile, capabilities, sessions }\n}\n\nexport type CreateAccountSettingsActionOptions = {\n /** The store's own client (`sk_` token, no buyer). */\n storefront: StorefrontClient\n /** The store's cookie holding the buyer session token. */\n customerCookie: SettingsCookie\n /** Shopper IP for the per-shopper rate limit (see `clientIp` of the client). */\n clientIp?: (request: Request) => string | null | undefined\n}\n\nexport type AccountSettingsActionArgs = { request: Request }\n\nexport const fail = (intent: SettingsIntent | undefined, code: SettingsErrorCode, extra: { field?: string; retryAfter?: number } = {}): AccountSettingsActionData => ({\n ok: false,\n ...(intent ? { intent } : {}),\n error: { code, ...(extra.field ? { field: extra.field } : {}), ...(extra.retryAfter ? { retryAfter: extra.retryAfter } : {}) },\n})\n\n/** Maps what the SDK threw to the code the form shows. `ctx: 'credential'` is a call that checks the buyer's password or code. */\nexport function toSettingsFailure(err: unknown, intent: SettingsIntent | undefined, ctx: 'credential' | 'other' = 'other'): AccountSettingsActionData {\n if (!isStorefrontError(err)) return fail(intent, 'unavailable')\n const e: StorefrontError = err\n if (e.code === 'insufficient_scope') return fail(intent, 'unsupported')\n switch (e.status) {\n case 401:\n return fail(intent, 'session_expired')\n case 403:\n return fail(intent, e.code === 'reauth_required' ? 'reauth_required' : 'unsupported')\n case 404:\n return fail(intent, 'unsupported')\n case 409:\n return fail(intent, e.field === 'orders' ? 'blocked_orders' : e.field === 'pending_payments' ? 'blocked_pending_payments' : 'validation', e.field ? { field: e.field } : {})\n case 429: {\n const wait = typeof e.retryAfter === 'number' && Number.isFinite(e.retryAfter) && e.retryAfter > 0 ? Math.min(3600, Math.ceil(e.retryAfter)) : undefined\n return fail(intent, 'rate_limited', wait ? { retryAfter: wait } : {})\n }\n case 400:\n case 422:\n return ctx === 'credential' ? fail(intent, 'invalid_credential') : fail(intent, 'validation', e.field ? { field: e.field } : {})\n default:\n return fail(intent, 'unavailable')\n }\n}\n\n/**\n * The settings action: one `POST` form with an `intent` field.\n *\n * | intent | fields | result |\n * | ----------------------- | ------------ | ---------------------------------------- |\n * | `session-revoke` | session_id | closes that session (idempotent) |\n * | `session-revoke-others` | - | closes every session but this one |\n * | `locale` | locale | saves the language (`''` = the store's) |\n * | `notifications` | pref, pref_shown | saves the checked / unchecked boxes |\n *\n * It answers `AccountSettingsActionData` to render with the section that sent it. A buyer without a session is\n * `session_expired` (the store redirects to sign-in).\n */\nexport function createAccountSettingsAction(options: CreateAccountSettingsActionOptions) {\n const { storefront: sf, customerCookie } = options\n\n return async function settingsAction({ request }: AccountSettingsActionArgs): Promise<Response | AccountSettingsActionData> {\n // Every intent changes the account (or spends a sign-in code), and the buyer's cookie rides along: a form that\n // another site submits is refused before anything is read. `Sec-Fetch-Site` is set by the browser and cannot be\n // forged by a page; an older browser that omits it falls back to the cookie's SameSite.\n if (request.headers.get('Sec-Fetch-Site')?.toLowerCase() === 'cross-site') return fail(undefined, 'validation')\n const form = await request.formData().catch(() => null)\n if (!form) return fail(undefined, 'validation')\n const text = (name: string) => {\n const v = form.get(name)\n return typeof v === 'string' ? v : ''\n }\n const intent = text('intent') as SettingsIntent\n const raw = await customerCookie.parse(request.headers.get('Cookie'))\n const token = typeof raw === 'string' && raw ? raw : null\n if (!token) return fail(intent, 'session_expired')\n const ip = options.clientIp?.(request)\n const w = ip ? { clientIp: ip } : undefined\n const customer = sf.withCustomerToken(token)\n\n try {\n switch (intent) {\n case 'session-revoke': {\n const id = text('session_id').trim()\n if (!id || id.length > 64) return fail(intent, 'validation', { field: 'session_id' })\n // `others` is its own intent: never let the field spell it, so one button cannot do the other's work.\n if (id.toLowerCase() === 'others') return fail(intent, 'validation', { field: 'session_id' })\n await customer.customer.sessions.revoke(id, w)\n return { ok: true, intent }\n }\n case 'session-revoke-others': {\n await customer.customer.sessions.revokeOthers(w)\n return { ok: true, intent }\n }\n case 'locale': {\n const locale = text('locale').trim()\n if (locale.length > 35) return fail(intent, 'validation', { field: 'locale' })\n await customer.customer.update({ locale: locale || null }, w)\n return { ok: true, intent, saved: true }\n }\n case 'notifications': {\n const shown = form.getAll('pref_shown').filter((v): v is string => typeof v === 'string')\n const checked = new Set(form.getAll('pref').filter((v): v is string => typeof v === 'string'))\n if (shown.length === 0 || shown.length > 30) return fail(intent, 'validation', { field: 'preferences' })\n const notifications: Record<string, Record<string, boolean>> = {}\n for (const pair of shown) {\n const m = /^([a-z][a-z0-9_]{0,31}):([a-z][a-z0-9_]{0,31})$/.exec(pair)\n if (!m) return fail(intent, 'validation', { field: 'preferences' })\n ;(notifications[m[1]!] ??= {})[m[2]!] = checked.has(pair)\n }\n await customer.customer.update({ preferences: { notifications } }, w)\n return { ok: true, intent, saved: true }\n }\n default:\n return fail(undefined, 'validation')\n }\n } catch (err) {\n // A session that is already gone (404) is what the buyer wanted; a stranger's looks the same, by design.\n if (intent === 'session-revoke' && isStorefrontError(err) && err.status === 404) return { ok: true, intent }\n return toSettingsFailure(err, intent)\n }\n }\n}\n\nexport type AccountSettingsAction = ReturnType<typeof createAccountSettingsAction>\n","/**\n * Server side of the account screen (`@asteby/storefront-react/account/server`): `loadAuthCapabilities` for the loader\n * and `createAuthAction` for the action. No React, no router: Web `Request`/`Response` only, so any framework mounts it.\n *\n * The browser never talks to OPS. The action signs the buyer in with the store's `sk_` client and keeps the session\n * token in the store's own cookie, which is passed in (`customerCookie`), so name, flags and signing stay the store's.\n */\nimport { invoiceFileBytes, isStorefrontError, repairPath, safeInvoiceFilename, type CustomerCapabilities, type CustomerSession, type StorefrontClient } from '@asteby/storefront'\nimport { voucherOf } from './voucher.js'\nimport type { AuthActionData, AuthErrorCode, AuthIntent } from './types.js'\n\nexport type { AuthActionData, AuthErrorCode, AuthIntent, AuthStep } from './types.js'\n\n/** What is assumed when the store cannot be asked: password sign-in works, nothing else is offered. */\nexport const SAFE_AUTH_CAPABILITIES: Readonly<CustomerCapabilities> = Object.freeze({\n password: true,\n email_verification: false,\n otp_channels: Object.freeze([]) as unknown as CustomerCapabilities['otp_channels'],\n})\n\nconst FAILURE_TTL_MS = 5_000\nconst LOG_EVERY_MS = 60_000\nlet lastLogAt = -Infinity\n\n/** Server log (never the buyer's data): says what is missing and where it is repaired. At most once a minute. */\nfunction logMisconfigured(scopes: readonly string[]): void {\n const t = Date.now()\n if (t - lastLogAt < LOG_EVERY_MS) return\n lastLogAt = t\n console.error(\n `[@asteby/storefront-react] La credencial de la tienda no tiene permisos${scopes.length ? ` (${scopes.join(', ')})` : ''}; ` +\n `las cuentas de comprador no funcionan hasta repararlo en ${repairPath()}.`,\n )\n}\nconst cache = new WeakMap<object, { at: number; ok: boolean; value: CustomerCapabilities }>()\n\nexport type LoadAuthCapabilitiesOptions = {\n /** How long a good answer is reused per client (default 60 s). `0` disables the cache. */\n ttlMs?: number\n signal?: AbortSignal\n /** Clock, for tests. */\n now?: () => number\n}\n\n/**\n * Asks the store which ways of signing in it offers, with a short cache per client. It NEVER throws: when OPS does not\n * answer (older server, outage, token without scope) it returns `SAFE_AUTH_CAPABILITIES` and retries after a few\n * seconds, so the account screen always renders. Use it as the loader: `loader = () => loadAuthCapabilities(storefront)`.\n */\nexport async function loadAuthCapabilities(sf: StorefrontClient, options: LoadAuthCapabilitiesOptions = {}): Promise<CustomerCapabilities> {\n const now = options.now ?? Date.now\n const ttl = options.ttlMs ?? 60_000\n const hit = cache.get(sf)\n if (hit && now() - hit.at < (hit.ok ? ttl : FAILURE_TTL_MS)) return hit.value\n try {\n const got = await sf.customer.capabilities(options.signal ? { signal: options.signal } : undefined)\n const missing = Array.isArray(got?.missing_scopes) ? got.missing_scopes.filter((x): x is string => typeof x === 'string') : []\n const value: CustomerCapabilities = {\n // `password: false` (including for a missing scope) is respected: the form never offers what the token cannot do.\n password: got?.password !== false,\n email_verification: got?.email_verification === true,\n otp_channels: Array.isArray(got?.otp_channels) ? got.otp_channels : [],\n ...(typeof got?.unavailable_reason === 'string' ? { unavailable_reason: got.unavailable_reason } : {}),\n ...(missing.length ? { missing_scopes: missing } : {}),\n }\n if (value.unavailable_reason === 'missing_scope') logMisconfigured(missing)\n cache.set(sf, { at: now(), ok: true, value })\n return value\n } catch (err) {\n if (isStorefrontError(err) && err.code === 'insufficient_scope') {\n // The token cannot even read capabilities: do not offer password sign-in it could not complete.\n const value: CustomerCapabilities = {\n password: false,\n email_verification: false,\n otp_channels: [],\n unavailable_reason: 'missing_scope',\n missing_scopes: err.requiredScopes,\n }\n logMisconfigured(err.requiredScopes)\n cache.set(sf, { at: now(), ok: false, value })\n return value\n }\n cache.set(sf, { at: now(), ok: false, value: SAFE_AUTH_CAPABILITIES })\n return SAFE_AUTH_CAPABILITIES\n }\n}\n\n/** The part of a cookie the action needs. React Router's `createCookie(...)` and Remix's fit as they are. */\nexport type AuthCookie = {\n serialize(value: string, options?: { expires?: Date; maxAge?: number }): string | Promise<string>\n}\n\n/** Allows only a path inside the site (`/account`, `/orders?x=1`); anything else is `fallback`. Blocks `//host` and `/\\host`. */\nexport function safeNextPath(raw: string | null | undefined, fallback = '/account'): string {\n if (typeof raw !== 'string') return fallback\n const v = raw.trim()\n if (!v.startsWith('/') || v.startsWith('//') || v.startsWith('/\\\\') || /[\\u0000-\\u001f\\\\]/.test(v)) return fallback\n return v\n}\n\nexport type CreateAuthActionOptions = {\n storefront: StorefrontClient\n /** The store's cookie for the buyer session token (`createCookie('sf_customer', ...)`). */\n customerCookie: AuthCookie\n /** Validates the `next` field before redirecting (default `safeNextPath`). Return a same-site path. */\n safeNext?: (raw: string | null) => string\n /** Where to go after signing in when there is no `next` (default `/account`). Only used by the default `safeNext`. */\n defaultNext?: string\n /** Shopper IP for the per-shopper rate limit (see `clientIp` of the client). */\n clientIp?: (request: Request) => string | null | undefined\n /** Capabilities source, for tests or a shared cache (default `loadAuthCapabilities`). */\n capabilities?: (sf: StorefrontClient) => Promise<CustomerCapabilities>\n}\n\nexport type AuthActionArgs = { request: Request }\n\nconst fail = (code: AuthErrorCode, extra: Partial<AuthActionData> = {}, field?: string): AuthActionData => ({\n ok: false,\n ...extra,\n error: { code, ...(field ? { field } : {}) },\n})\n\n/** Maps what the SDK threw to the error the form shows. `ctx` tells what a 401 means on this step. */\nfunction toFailure(err: unknown, ctx: 'credentials' | 'code' | 'other', extra: Partial<AuthActionData>): AuthActionData {\n if (!isStorefrontError(err)) return fail('unavailable', extra)\n if (err.code === 'insufficient_scope') {\n logMisconfigured(err.requiredScopes)\n return fail('store_misconfigured', extra)\n }\n switch (err.status) {\n case 401:\n return fail(ctx === 'credentials' ? 'invalid_credentials' : ctx === 'code' ? 'invalid_code' : 'unavailable', extra, ctx === 'code' ? 'code' : undefined)\n case 409:\n return fail('email_taken', extra, 'email')\n case 429:\n return fail('rate_limited', extra)\n case 400:\n case 422:\n return fail('validation', extra, err.field)\n case 403:\n case 404:\n return fail('unsupported', extra)\n default:\n return fail('unavailable', extra)\n }\n}\n\n/**\n * The account action: one `POST` form with an `intent` field.\n *\n * | intent | fields | result |\n * | ------------- | ---------------------------------------- | -------------------------------------------------------- |\n * | `login` | email, password | signs in -> redirect |\n * | `register` | email, password, first_name?, last_name? | `step: 'verify'` or, without verification, redirect |\n * | `verify` | email, code | account ready -> redirect |\n * | `otp-request` | email | `step: 'otp'` (code sent) |\n * | `otp-login` | email, code | signs in -> redirect |\n * | `forgot` | email | `step: 'reset'` (always, so it never says who has an account) |\n * | `reset` | email, code, password | new password and signed in -> redirect |\n *\n * Success is a `303` to `next` (validated by `safeNext`) with `Set-Cookie` holding the session token; anything else is\n * an `AuthActionData` to render with `<AccountAuth data={...}>`. Passwords and codes are never echoed back.\n */\nexport function createAuthAction(options: CreateAuthActionOptions) {\n const { storefront: sf, customerCookie } = options\n const safeNext = options.safeNext ?? ((raw: string | null) => safeNextPath(raw, options.defaultNext ?? '/account'))\n const loadCaps = options.capabilities ?? ((client: StorefrontClient) => loadAuthCapabilities(client))\n\n async function signedIn(session: CustomerSession, next: string): Promise<Response> {\n const expires = new Date(session.expires_at)\n const cookie = await customerCookie.serialize(session.token, Number.isNaN(expires.getTime()) ? undefined : { expires })\n return new Response(null, { status: 303, headers: { Location: next, 'Set-Cookie': cookie } })\n }\n\n return async function authAction({ request }: AuthActionArgs): Promise<Response | AuthActionData> {\n const form = await request.formData().catch(() => null)\n if (!form) return fail('validation')\n const text = (name: string) => {\n const v = form.get(name)\n return typeof v === 'string' ? v : ''\n }\n const intent = text('intent') as AuthIntent\n const email = text('email').trim()\n const password = text('password')\n const code = text('code').replace(/\\s+/g, '')\n const next = safeNext(text('next') || null)\n const ip = options.clientIp?.(request)\n const w = ip ? { clientIp: ip } : undefined\n const carry: Partial<AuthActionData> = { email }\n if (!email) return fail('validation', carry, 'email')\n\n const caps = await loadCaps(sf)\n const passwordOn = caps.password\n // Off because the store's token lacks a scope (not a merchant choice): say so instead of \"not available\".\n const misconfigured = caps.unavailable_reason === 'missing_scope'\n const off = (): AuthActionData => fail(misconfigured ? 'store_misconfigured' : 'unsupported', carry)\n const otpOn = caps.otp_channels.includes('email')\n\n try {\n switch (intent) {\n case 'login': {\n if (!passwordOn) return off()\n if (!password) return fail('validation', carry, 'password')\n return await signedIn(await sf.customer.loginWithPassword({ email, password }, w), next)\n }\n case 'register': {\n if (!passwordOn) return off()\n if (!password) return fail('validation', carry, 'password')\n const res = await sf.customer.register(\n { email, password, ...(text('first_name').trim() ? { first_name: text('first_name').trim() } : {}), ...(text('last_name').trim() ? { last_name: text('last_name').trim() } : {}) },\n w,\n )\n return res.status === 'created' ? await signedIn(res.session, next) : { ok: true, step: 'verify', email }\n }\n case 'verify': {\n if (!code) return fail('validation', carry, 'code')\n return await signedIn(await sf.customer.verifyRegistration({ email, code }, w), next)\n }\n case 'otp-request': {\n if (!otpOn) return off()\n await sf.customer.requestOtp({ email }, w)\n return { ok: true, step: 'otp', email }\n }\n case 'otp-login': {\n if (!otpOn) return off()\n if (!code) return fail('validation', carry, 'code')\n return await signedIn(await sf.customer.createSession({ email, code }, w), next)\n }\n case 'forgot': {\n if (!passwordOn || !otpOn) return off()\n try {\n await sf.customer.requestOtp({ email }, w)\n } catch (err) {\n // Only throttling and a bad email are told; anything else looks the same as success (no account enumeration).\n if (isStorefrontError(err) && (err.status === 429 || err.status === 400)) throw err\n }\n return { ok: true, step: 'reset', email }\n }\n case 'reset': {\n if (!passwordOn) return off()\n if (!code) return fail('validation', carry, 'code')\n if (!password) return fail('validation', carry, 'password')\n await sf.customer.resetPassword({ email, code, password }, w)\n return await signedIn(await sf.customer.loginWithPassword({ email, password }, w), next)\n }\n default:\n return fail('validation', carry)\n }\n } catch (err) {\n const step = intent === 'verify' ? 'verify' : intent === 'otp-login' ? 'otp' : intent === 'reset' || intent === 'forgot' ? 'reset' : undefined\n const ctx = intent === 'login' ? 'credentials' : step ? 'code' : 'other'\n return toFailure(err, ctx, step && intent !== 'forgot' ? { ...carry, step } : carry)\n }\n }\n}\n\nexport type AuthAction = ReturnType<typeof createAuthAction>\n\n// ---------------------------------------------------------------------------\n// Resource routes of the order screens: the invoice file and the printable voucher\n// ---------------------------------------------------------------------------\n\n/** Headers every answer of a buyer route carries, error or not: never cached, never sniffed, never rendered as a page. */\nconst LOCKED_HEADERS = {\n 'Cache-Control': 'private, no-store',\n 'X-Content-Type-Options': 'nosniff',\n 'Referrer-Policy': 'no-referrer',\n 'X-Robots-Tag': 'noindex',\n} as const\nconst TEXT_HEADERS = { ...LOCKED_HEADERS, 'Content-Type': 'text/plain; charset=utf-8', 'Content-Security-Policy': \"default-src 'none'; sandbox\" } as const\nconst textResponse = (status: number, body: string, extra: Record<string, string> = {}) => new Response(body, { status, headers: { ...TEXT_HEADERS, ...extra } })\n\n/** Largest invoice file served (a CFDI PDF is well under 1 MB): anything bigger is refused before it is decoded. */\nexport const MAX_INVOICE_FILE_BYTES = 10 * 1024 * 1024\n\n/** Statuses a buyer-facing route answers with; anything unexpected is a plain 502 (never the upstream message). */\nfunction failureResponse(err: unknown): Response {\n if (isStorefrontError(err)) {\n if (err.status === 401) return textResponse(401, 'Sesion vencida')\n if (err.status === 404) return textResponse(404, 'No encontrado')\n if (err.status === 429) {\n const wait = typeof err.retryAfter === 'number' && Number.isFinite(err.retryAfter) && err.retryAfter > 0 ? Math.min(3600, Math.ceil(err.retryAfter)) : null\n return textResponse(429, 'Demasiados intentos', wait ? { 'Retry-After': String(wait) } : {})\n }\n }\n return textResponse(502, 'No pudimos obtener el archivo')\n}\n\nconst FILE_TYPES: Record<string, string> = { pdf: 'application/pdf', xml: 'application/xml' }\n\nexport type InvoiceFileResponseOptions = {\n /** Client WITH the buyer's session (`storefront.withCustomerToken(token)`). */\n storefront: StorefrontClient\n orderId: string\n invoiceId: string\n /** From the URL (`pdf` | `xml`); anything else is a 404. */\n kind: string | undefined\n signal?: AbortSignal\n /** Largest file served, in bytes (default `MAX_INVOICE_FILE_BYTES`, 10 MB); a bigger one is a generic 502. */\n maxBytes?: number\n}\n\n/**\n * Serves one file of an invoice or credit note to the signed-in buyer: the route handler of the store\n * (`account.order.$id.invoice.$doc.$kind.ts`) is `return invoiceFileResponse({ storefront, orderId, invoiceId, kind })`.\n * The buyer's token and any PAC URL stay on the server; the browser gets bytes with `Content-Disposition: attachment`,\n * `Cache-Control: private, no-store`, `nosniff` and a sandboxing CSP. The content type is taken from `kind`, never\n * from what the API says, so a file cannot make the browser render HTML under your origin. 401/404/429 pass through;\n * anything else is a generic 502 with no upstream detail.\n */\nexport async function invoiceFileResponse({ storefront, orderId, invoiceId, kind, signal, maxBytes = MAX_INVOICE_FILE_BYTES }: InvoiceFileResponseOptions): Promise<Response> {\n if (kind !== 'pdf' && kind !== 'xml') return textResponse(404, 'No encontrado')\n if (!orderId || !invoiceId) return textResponse(404, 'No encontrado')\n try {\n const file = await storefront.customer.invoiceFile(orderId, invoiceId, kind, signal ? { signal } : undefined)\n if (!file) return textResponse(404, 'No encontrado')\n // Checked on the encoded text (4 chars per 3 bytes, whitespace aside) so a huge answer is never decoded.\n if (file.content_base64.length > Math.ceil(maxBytes / 3) * 4 * 1.1 + 64) return textResponse(502, 'No pudimos obtener el archivo')\n const bytes = invoiceFileBytes(file)\n if (bytes.byteLength > maxBytes) return textResponse(502, 'No pudimos obtener el archivo')\n const name = safeInvoiceFilename(file.filename, kind === 'pdf' ? 'factura.pdf' : 'factura.xml')\n const filename = name.toLowerCase().endsWith(`.${kind}`) ? name : `${name}.${kind}`\n return new Response(bytes as BodyInit, {\n status: 200,\n headers: {\n 'Content-Type': FILE_TYPES[kind]!,\n 'Content-Length': String(bytes.byteLength),\n 'Content-Disposition': `attachment; filename=\"${filename}\"`,\n ...LOCKED_HEADERS,\n 'Content-Security-Policy': \"default-src 'none'; sandbox\",\n },\n })\n } catch (err) {\n return failureResponse(err)\n }\n}\n\nexport type PendingVoucherResponseOptions = {\n /** Client WITH the buyer's session. */\n storefront: StorefrontClient\n /** Checkout id (`PendingPayment.id`). */\n id: string\n /** `?descargar=1`: deliver it as a file instead of showing it. */\n download?: boolean\n shopName?: string\n locale?: string\n timeZone?: string\n signal?: AbortSignal\n}\n\n/**\n * The printable A4 voucher of a pending payment of the signed-in buyer. Served only when `id` is in HIS list (the list\n * is scoped by buyer in OPS): any other id is a 404. The document is self-contained, so its CSP allows nothing\n * external, only its own print script (with a per-response nonce) and inline styles.\n */\nexport async function pendingVoucherResponse({ storefront, id, download = false, shopName, locale, timeZone, signal }: PendingVoucherResponseOptions): Promise<Response> {\n try {\n const page = await storefront.customer.pendingPayments({ first: 100 }, signal ? { signal } : undefined)\n const item = page.nodes.find((p) => p.id === id)\n const voucher = item ? voucherOf(item) : null\n if (!item || !voucher) return textResponse(404, 'Ficha no encontrada')\n const nonce = btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(16))))\n const { renderVoucherDocument } = await import('./voucher-document.js')\n const html = renderVoucherDocument(voucher, { ...(shopName ? { shopName } : {}), ...(locale ? { locale } : {}), ...(timeZone ? { timeZone } : {}), printButton: !download, nonce })\n const safeId = id.replace(/[^A-Za-z0-9-]/g, '').slice(0, 8) || 'ficha'\n return new Response(html, {\n status: 200,\n headers: {\n 'Content-Type': 'text/html; charset=utf-8',\n ...LOCKED_HEADERS,\n // The download carries no script, so its policy allows none.\n 'Content-Security-Policy': `default-src 'none'; style-src 'unsafe-inline'; img-src data:; script-src ${download ? \"'none'\" : `'nonce-${nonce}'`}; base-uri 'none'; form-action 'none'; frame-ancestors 'self'`,\n ...(download ? { 'Content-Disposition': `attachment; filename=\"ficha-de-pago-${safeId}.html\"` } : {}),\n },\n })\n } catch (err) {\n return failureResponse(err)\n }\n}\n\n// Account settings (sessions, preferences, export, deletion): see ./settings-server.ts\nexport { createAccountSettingsAction, loadAccountSessions, loadAccountSettings, toSettingsFailure } from './settings-server.js'\nexport type { AccountSettingsAction, AccountSettingsData, AccountSettingsActionArgs, CreateAccountSettingsActionOptions, SettingsCookie } from './settings-server.js'\nexport type { AccountSettingsActionData, SettingsErrorCode, SettingsIntent } from './settings-types.js'\n"]}
|