@wix/himalaya-cli 0.870.0 → 0.872.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/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/wix-gateway.d.ts +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/auth-providers/wix/src/device-auth.d.ts +7 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/auth-providers/wix/src/index.d.ts +1 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/auth-providers/wix/src/passkey-server.d.ts +16 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/auth-providers/wix/src/redaction.d.ts +20 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/auth-providers/wix/src/signed-instance-resolver.d.ts +3 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-auth/server.mjs +9 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-auth/worker.mjs +72 -17
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-auth.mjs +71 -17
- package/dist/cli.mjs +43 -7
- package/package.json +2 -2
|
@@ -9,6 +9,7 @@ export interface WixGatewayState {
|
|
|
9
9
|
seq: number;
|
|
10
10
|
ruleSeq: number;
|
|
11
11
|
}
|
|
12
|
+
export declare const SECRET_BODY_KEYS: Set<string>;
|
|
12
13
|
/** Scrub secrets from a request/response body before it is stored/streamed to the
|
|
13
14
|
* admin inspector. JSON bodies are redacted structurally by key; other bodies
|
|
14
15
|
* get form/bearer token scrubbing. Never throws. */
|
|
@@ -18,6 +18,13 @@ export interface DeviceAuthResult {
|
|
|
18
18
|
tokens?: DeviceAuthTokens;
|
|
19
19
|
error?: string;
|
|
20
20
|
}
|
|
21
|
+
/** A device-authorization request that failed before a QR could be shown. Typed
|
|
22
|
+
* so the caller can render it, rather than the raw `SyntaxError` an HTML gateway
|
|
23
|
+
* page used to produce out of `res.json()`. */
|
|
24
|
+
export declare class DeviceAuthError extends Error {
|
|
25
|
+
readonly status?: number;
|
|
26
|
+
constructor(message: string, status?: number);
|
|
27
|
+
}
|
|
21
28
|
export interface DeviceAuthConfig {
|
|
22
29
|
clientId: string;
|
|
23
30
|
baseURL?: string;
|
|
@@ -7,7 +7,7 @@ export { REDACTED, WIX_SENSITIVE_KEYS, isSensitiveKey, normalizeKey, redactHeade
|
|
|
7
7
|
export { SessionIDService, InMemorySessionIDStore, bsiShouldRotate, localDayIndex, BSI_ROTATE_INTERVAL_MS, type SessionIDRecord, type SessionIDStore, } from "./session-id.js";
|
|
8
8
|
export { policyFor as wixRequestPolicy, isFirstPartyWixHost, isSignedUploadTarget, type MobileRequestKind, type RequestPolicy, } from "./request-policy.js";
|
|
9
9
|
export { METASITE_HEADER, SIGNED_INSTANCE_EXP_HEADER, AUTH_PROXY_PREFIX, authProxyHeaders, authProxyPseudoInstance, isAuthProxyInstance, authProxyBusinessId, parseSignedInstanceExp, isSignedInstanceExpired, } from "./auth-proxy.js";
|
|
10
|
-
export { DeviceAuthClient, parseDeviceQr, type DeviceAuthorization, type DeviceAuthTokens, type DeviceAuthResult, type DeviceAuthConfig, type DeviceAuthTerminal, } from "./device-auth.js";
|
|
10
|
+
export { DeviceAuthClient, DeviceAuthError, parseDeviceQr, type DeviceAuthorization, type DeviceAuthTokens, type DeviceAuthResult, type DeviceAuthConfig, type DeviceAuthTerminal, } from "./device-auth.js";
|
|
11
11
|
export { buildRegistrationOptions, buildAuthenticationOptions, authenticatorDataCounter, assertSignCountAdvanced, PasskeyCloneError, type RegistrationOptions, type AuthenticationOptions, } from "./passkey-ceremony.js";
|
|
12
12
|
export { WixAuthModalities, classifyAuthErrorCode, type PushApprovalState, type AuthModalitiesConfig, } from "./auth-modalities.js";
|
|
13
13
|
export { shouldEscalateCaptcha, captchaResponseFields, CaptchaEscalation, CAPTCHA_ESCALATION_CODES, type CaptchaTier, } from "./captcha-escalation.js";
|
|
@@ -3,6 +3,12 @@ export interface StoredCredential {
|
|
|
3
3
|
credentialId: string;
|
|
4
4
|
publicKeySpki: string;
|
|
5
5
|
alg: number;
|
|
6
|
+
/** The authenticator's signature counter, for clone detection (WebAuthn §7.2
|
|
7
|
+
* step 17). `verifyAuthentication` does NOT read or advance this — step 17 is
|
|
8
|
+
* `assertSignCountAdvanced` in `passkey-ceremony.ts`, which the host calls with
|
|
9
|
+
* `authenticatorDataCounter(authenticatorData)` and then PERSISTS the returned
|
|
10
|
+
* value. It is split out because only the host owns the store; do not read a
|
|
11
|
+
* non-zero value here as proof the check ran. */
|
|
6
12
|
signCount: number;
|
|
7
13
|
}
|
|
8
14
|
export interface ClientData {
|
|
@@ -33,6 +39,16 @@ export interface AuthenticationInput {
|
|
|
33
39
|
stored: StoredCredential;
|
|
34
40
|
expectedChallenge: string;
|
|
35
41
|
expectedOrigin: string;
|
|
42
|
+
/** The relying-party id this assertion must be bound to — the SAME value the
|
|
43
|
+
* page passed as `rpId` in `buildAuthenticationOptions` (a registrable domain
|
|
44
|
+
* suffix of the origin, e.g. `"localhost"` or `"wix.com"`).
|
|
45
|
+
*
|
|
46
|
+
* REQUIRED, not optional, and deliberately so: a security check the caller can
|
|
47
|
+
* forget is a security check that is off in production. WebAuthn §7.2 step 15
|
|
48
|
+
* is the only thing binding an assertion to its relying party, and without it
|
|
49
|
+
* `expectedOrigin` alone leaves nothing checking what the *authenticator*
|
|
50
|
+
* signed over. */
|
|
51
|
+
expectedRpId: string;
|
|
36
52
|
}
|
|
37
53
|
/** Verify an assertion against a stored credential. Throws PasskeyError on any
|
|
38
54
|
* mismatch / bad signature; returns true on success. */
|
|
@@ -7,6 +7,26 @@ export declare const WIX_SENSITIVE_KEYS: readonly string[];
|
|
|
7
7
|
export declare function normalizeKey(key: string): string;
|
|
8
8
|
/** Whether a header/field/cookie name names a Wix secret. */
|
|
9
9
|
export declare function isSensitiveKey(key: string): boolean;
|
|
10
|
+
/** Whether a `name=` token seen in a FREE-TEXT body (form-encoded, query string,
|
|
11
|
+
* raw dump) names a Wix secret.
|
|
12
|
+
*
|
|
13
|
+
* Looser than `isSensitiveKey` on purpose, in exactly two ways, because a
|
|
14
|
+
* free-text scrubber sees names the structural paths never do:
|
|
15
|
+
*
|
|
16
|
+
* 1. **Internal separators.** The vocabulary is stored separator-stripped
|
|
17
|
+
* (`accesstoken`), so a literal alternation built from it can never match the
|
|
18
|
+
* snake_case OAuth spellings `access_token` / `refresh_token` / `id_token`
|
|
19
|
+
* — which is precisely the shape an OAuth form body uses. We therefore split
|
|
20
|
+
* the name on separators and normalize before comparing.
|
|
21
|
+
* 2. **Prefix/suffix tolerance.** A name whose leading segments are a secret
|
|
22
|
+
* name counts (`accessTokenV2`, `x-access-token`), matching the loose
|
|
23
|
+
* `\b<key>[-_]?[a-z0-9]*=` shape this replaced. Over-redaction is the safe
|
|
24
|
+
* direction for a log scrubber; under-redaction is a leak.
|
|
25
|
+
*
|
|
26
|
+
* Checking every segment-suffix (not just the whole name) reproduces the old
|
|
27
|
+
* `\b` anchoring: `x-access-token` matches on its `access-token` tail, while
|
|
28
|
+
* `myaccesstoken` — which has no word boundary before the secret — does not. */
|
|
29
|
+
export declare function isSensitiveFreeTextName(name: string): boolean;
|
|
10
30
|
/** Redact a header map (values only; keys preserved). Case-insensitive. */
|
|
11
31
|
export declare function redactHeaders(headers: Record<string, string | string[] | undefined>): Record<string, string>;
|
|
12
32
|
/** Redact a `Cookie:` / `Set-Cookie:` header string, keeping cookie names but
|
|
@@ -57,6 +57,9 @@ export declare class SignedInstanceResolver {
|
|
|
57
57
|
/** Resolve the signed instance for `businessId`, using (and populating) the
|
|
58
58
|
* caches. Concurrent calls for the same key share a single fetch. */
|
|
59
59
|
resolve(businessId: string, signedInstanceType?: SignedInstanceType, forceRefresh?: boolean): Promise<string | undefined>;
|
|
60
|
+
/** Whether `task` is still the in-flight fetch registered for `key` — false once
|
|
61
|
+
* `remove`/`reset` has evicted it, or a later resolve has replaced it. */
|
|
62
|
+
private isCurrent;
|
|
60
63
|
/** Evict everything for a business (e.g. on leaving / switching it). */
|
|
61
64
|
remove(businessId: string): void;
|
|
62
65
|
/** Wipe all caches (e.g. on sign-out). */
|
|
@@ -3,7 +3,7 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
|
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
|
|
5
5
|
// ../../stdlib/auth-providers/wix/src/passkey-server.ts
|
|
6
|
-
import { createHash, createPublicKey, verify as cryptoVerify, randomBytes } from "node:crypto";
|
|
6
|
+
import { createHash, createPublicKey, verify as cryptoVerify, randomBytes, timingSafeEqual } from "node:crypto";
|
|
7
7
|
function newChallenge() {
|
|
8
8
|
return randomBytes(32).toString("base64url");
|
|
9
9
|
}
|
|
@@ -34,11 +34,17 @@ function verifyRegistration(input) {
|
|
|
34
34
|
signCount: 0
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
|
+
var FLAG_USER_PRESENT = 1;
|
|
37
38
|
function verifyAuthentication(input) {
|
|
38
39
|
const client = parseClientData(input.clientDataJSON);
|
|
39
40
|
assertCeremony(client, "webauthn.get", input.expectedChallenge, input.expectedOrigin);
|
|
40
41
|
const authData = Buffer.from(input.authenticatorData, "base64url");
|
|
41
42
|
if (authData.length < 37) throw new PasskeyError("authenticatorData too short");
|
|
43
|
+
const expectedRpIdHash = createHash("sha256").update(input.expectedRpId).digest();
|
|
44
|
+
if (!timingSafeEqual(authData.subarray(0, 32), expectedRpIdHash)) {
|
|
45
|
+
throw new PasskeyError("rpIdHash mismatch");
|
|
46
|
+
}
|
|
47
|
+
if ((authData[32] & FLAG_USER_PRESENT) === 0) throw new PasskeyError("user not present");
|
|
42
48
|
const clientHash = createHash("sha256").update(Buffer.from(input.clientDataJSON, "base64url")).digest();
|
|
43
49
|
const signedData = Buffer.concat([authData, clientHash]);
|
|
44
50
|
let key;
|
|
@@ -53,7 +59,7 @@ function verifyAuthentication(input) {
|
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
// ../../stdlib/auth-providers/wix/src/jot-deeplink.ts
|
|
56
|
-
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
62
|
+
import { createHmac, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
57
63
|
var JOT_DLA_HOST = "invite.mobileappinvite.com";
|
|
58
64
|
function signJotPayload(payload, secret) {
|
|
59
65
|
return createHmac("sha256", secret).update(payload).digest("base64url");
|
|
@@ -62,7 +68,7 @@ function verifyJotSignature(payload, signature, secret) {
|
|
|
62
68
|
const expected = signJotPayload(payload, secret);
|
|
63
69
|
const a = Buffer.from(signature);
|
|
64
70
|
const b = Buffer.from(expected);
|
|
65
|
-
return a.length === b.length &&
|
|
71
|
+
return a.length === b.length && timingSafeEqual2(a, b);
|
|
66
72
|
}
|
|
67
73
|
function parseUniversalLink(url, appId) {
|
|
68
74
|
let u;
|
|
@@ -4,6 +4,21 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4
4
|
|
|
5
5
|
// ../../stdlib/auth-providers/wix/src/device-auth.ts
|
|
6
6
|
var DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
|
|
7
|
+
var DeviceAuthError = class extends Error {
|
|
8
|
+
constructor(message, status) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.status = status;
|
|
11
|
+
this.name = "DeviceAuthError";
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
async function readJsonSafely(res) {
|
|
15
|
+
try {
|
|
16
|
+
const value = await res.json();
|
|
17
|
+
return value && typeof value === "object" ? { json: value, parsed: true } : { json: {}, parsed: false };
|
|
18
|
+
} catch {
|
|
19
|
+
return { json: {}, parsed: false };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
7
22
|
function parseDeviceQr(scanned) {
|
|
8
23
|
const s = scanned.trim();
|
|
9
24
|
if (!s) return {};
|
|
@@ -52,7 +67,13 @@ var DeviceAuthClient = class {
|
|
|
52
67
|
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
|
|
53
68
|
body: `client_id=${encodeURIComponent(this.clientId)}&scope=${encodeURIComponent(scope)}`
|
|
54
69
|
});
|
|
55
|
-
const j = await res
|
|
70
|
+
const { json: j } = await readJsonSafely(res);
|
|
71
|
+
if (!res.ok || !j.device_code) {
|
|
72
|
+
throw new DeviceAuthError(
|
|
73
|
+
j.error_description ?? j.error ?? `Could not start device sign-in (HTTP ${res.status}).`,
|
|
74
|
+
res.status
|
|
75
|
+
);
|
|
76
|
+
}
|
|
56
77
|
return {
|
|
57
78
|
deviceCode: j.device_code,
|
|
58
79
|
userCode: j.user_code,
|
|
@@ -65,15 +86,21 @@ var DeviceAuthClient = class {
|
|
|
65
86
|
/** One poll step against the token endpoint. */
|
|
66
87
|
async pollOnce(auth) {
|
|
67
88
|
if (this.devMode) return { terminal: { state: "authorized", tokens: { accessToken: "dev-device-token" } } };
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
89
|
+
let res;
|
|
90
|
+
try {
|
|
91
|
+
res = await this.fetchImpl(`${this.baseURL}/oauth2/token`, {
|
|
92
|
+
method: "POST",
|
|
93
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
|
|
94
|
+
body: `grant_type=${encodeURIComponent(DEVICE_GRANT)}&device_code=${encodeURIComponent(auth.deviceCode)}&client_id=${encodeURIComponent(this.clientId)}`
|
|
95
|
+
});
|
|
96
|
+
} catch {
|
|
97
|
+
return {};
|
|
98
|
+
}
|
|
99
|
+
const { json: j, parsed } = await readJsonSafely(res);
|
|
74
100
|
if (j.access_token) {
|
|
75
101
|
return { terminal: { state: "authorized", tokens: { accessToken: j.access_token, refreshToken: j.refresh_token } } };
|
|
76
102
|
}
|
|
103
|
+
if (!parsed) return {};
|
|
77
104
|
switch (j.error) {
|
|
78
105
|
case "authorization_pending":
|
|
79
106
|
return {};
|
|
@@ -83,8 +110,11 @@ var DeviceAuthClient = class {
|
|
|
83
110
|
return { terminal: { state: "denied", error: "The sign-in request was denied." } };
|
|
84
111
|
case "expired_token":
|
|
85
112
|
return { terminal: { state: "expired", error: "The code expired. Try again." } };
|
|
113
|
+
// `message` is read too so Wix's own error envelope, which carries no
|
|
114
|
+
// `error`, still reaches the user with its actual reason rather than the
|
|
115
|
+
// generic fallback.
|
|
86
116
|
default:
|
|
87
|
-
return { terminal: { state: "error", error: j.error_description ?? j.error ?? "Device authorization failed." } };
|
|
117
|
+
return { terminal: { state: "error", error: j.error_description ?? j.error ?? j.message ?? "Device authorization failed." } };
|
|
88
118
|
}
|
|
89
119
|
}
|
|
90
120
|
/** Run the full poll loop until a terminal state or the code expires. Honors
|
|
@@ -152,7 +182,15 @@ var WixNativeAuthEngine = class {
|
|
|
152
182
|
} catch (e) {
|
|
153
183
|
throw new NativeAuthError("network", e.message);
|
|
154
184
|
}
|
|
155
|
-
|
|
185
|
+
if (!res.ok) {
|
|
186
|
+
throw new NativeAuthError("network", `Account status lookup failed (HTTP ${res.status}).`);
|
|
187
|
+
}
|
|
188
|
+
let json;
|
|
189
|
+
try {
|
|
190
|
+
json = await res.json();
|
|
191
|
+
} catch {
|
|
192
|
+
throw new NativeAuthError("network", "Account status response was not readable JSON.");
|
|
193
|
+
}
|
|
156
194
|
const status = json?.payload?.userAuthStatus ?? json?.userAuthStatus ?? {};
|
|
157
195
|
if (!status.userId) return "newUser";
|
|
158
196
|
return status.hasPassword ? "existingPassword" : "passwordless";
|
|
@@ -671,6 +709,14 @@ function normalizeKey(key) {
|
|
|
671
709
|
function isSensitiveKey(key) {
|
|
672
710
|
return SENSITIVE.has(normalizeKey(key));
|
|
673
711
|
}
|
|
712
|
+
function isSensitiveFreeTextName(name) {
|
|
713
|
+
const segments = name.split(/[^A-Za-z0-9]+/).filter(Boolean);
|
|
714
|
+
for (let i = 0; i < segments.length; i++) {
|
|
715
|
+
const tail = normalizeKey(segments.slice(i).join(""));
|
|
716
|
+
for (const k of WIX_SENSITIVE_KEYS) if (tail.startsWith(k)) return true;
|
|
717
|
+
}
|
|
718
|
+
return false;
|
|
719
|
+
}
|
|
674
720
|
function redactHeaders(headers) {
|
|
675
721
|
const out = {};
|
|
676
722
|
for (const [k, v] of Object.entries(headers)) {
|
|
@@ -699,10 +745,9 @@ function redactJson(value) {
|
|
|
699
745
|
}
|
|
700
746
|
return value;
|
|
701
747
|
}
|
|
702
|
-
var
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
);
|
|
748
|
+
var FORM_ASSIGNMENT_RE = /([A-Za-z0-9][A-Za-z0-9_.-]*)=([^&;\s]+)/g;
|
|
749
|
+
var JSON_FIELD_RE = /"([^"\\]+)"(\s*:\s*)"((?:\\.|[^"\\])*)"/g;
|
|
750
|
+
var JSON_FIELD_UNTERMINATED_RE = /"([^"\\]+)"(\s*:\s*)"(?:\\.|[^"\\])*$/;
|
|
706
751
|
var BEARER_RE = /\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/gi;
|
|
707
752
|
function redactBody(body, maxLen = 4096) {
|
|
708
753
|
if (!body) return body ?? "";
|
|
@@ -720,7 +765,7 @@ function redactBody(body, maxLen = 4096) {
|
|
|
720
765
|
return out.length > maxLen ? out.slice(0, maxLen) + "\u2026(truncated)" : out;
|
|
721
766
|
}
|
|
722
767
|
function scrubText(s) {
|
|
723
|
-
return s.replace(
|
|
768
|
+
return s.replace(FORM_ASSIGNMENT_RE, (m, name) => isSensitiveFreeTextName(name) ? `${name}=${REDACTED}` : m).replace(JSON_FIELD_RE, (m, name, sep) => isSensitiveFreeTextName(name) ? `"${name}"${sep}"${REDACTED}"` : m).replace(JSON_FIELD_UNTERMINATED_RE, (m, name, sep) => isSensitiveFreeTextName(name) ? `"${name}"${sep}"${REDACTED}` : m).replace(BEARER_RE, (_m, scheme) => `${scheme} ${REDACTED}`);
|
|
724
769
|
}
|
|
725
770
|
|
|
726
771
|
// ../../stdlib/auth-providers/wix/src/request-policy.ts
|
|
@@ -915,24 +960,32 @@ var SignedInstanceResolver = class {
|
|
|
915
960
|
this.pending.set(key, task);
|
|
916
961
|
try {
|
|
917
962
|
const record = await task;
|
|
918
|
-
if (record) {
|
|
963
|
+
if (record && this.isCurrent(key, task)) {
|
|
919
964
|
this.memoryCache.set(key, record);
|
|
920
965
|
if (!this.isExpired(record)) this.storage.save(businessId, signedInstanceType, record);
|
|
921
966
|
}
|
|
922
967
|
return record?.signedInstance;
|
|
923
968
|
} catch (error) {
|
|
924
969
|
const sie = error instanceof SignedInstanceError ? error : new SignedInstanceError("Signed-instance fetch failed", "fetchFailed", error);
|
|
925
|
-
this.
|
|
970
|
+
if (this.isCurrent(key, task)) {
|
|
971
|
+
this.failureCache.set(key, { error: sie, expiry: this.now() + this.failureCacheTtlMs });
|
|
972
|
+
}
|
|
926
973
|
this.report(sie);
|
|
927
974
|
throw sie;
|
|
928
975
|
} finally {
|
|
929
|
-
this.pending.delete(key);
|
|
976
|
+
if (this.isCurrent(key, task)) this.pending.delete(key);
|
|
930
977
|
}
|
|
931
978
|
}
|
|
979
|
+
/** Whether `task` is still the in-flight fetch registered for `key` — false once
|
|
980
|
+
* `remove`/`reset` has evicted it, or a later resolve has replaced it. */
|
|
981
|
+
isCurrent(key, task) {
|
|
982
|
+
return this.pending.get(key) === task;
|
|
983
|
+
}
|
|
932
984
|
/** Evict everything for a business (e.g. on leaving / switching it). */
|
|
933
985
|
remove(businessId) {
|
|
934
986
|
for (const k of [...this.memoryCache.keys()]) if (k.startsWith(`${businessId}:`)) this.memoryCache.delete(k);
|
|
935
987
|
for (const k of [...this.failureCache.keys()]) if (k.startsWith(`${businessId}:`)) this.failureCache.delete(k);
|
|
988
|
+
for (const k of [...this.pending.keys()]) if (k.startsWith(`${businessId}:`)) this.pending.delete(k);
|
|
936
989
|
this.storage.deleteBusiness(businessId);
|
|
937
990
|
}
|
|
938
991
|
/** Wipe all caches (e.g. on sign-out). */
|
|
@@ -958,6 +1011,7 @@ export {
|
|
|
958
1011
|
CAPTCHA_ESCALATION_CODES,
|
|
959
1012
|
CaptchaEscalation,
|
|
960
1013
|
DeviceAuthClient,
|
|
1014
|
+
DeviceAuthError,
|
|
961
1015
|
InMemorySessionIDStore,
|
|
962
1016
|
InMemorySignedInstanceStorage,
|
|
963
1017
|
METASITE_HEADER,
|
|
@@ -988,6 +1042,7 @@ export {
|
|
|
988
1042
|
dualDomainAuthorizeUrls,
|
|
989
1043
|
isAuthProxyInstance,
|
|
990
1044
|
isFirstPartyWixHost,
|
|
1045
|
+
isSensitiveFreeTextName,
|
|
991
1046
|
isSensitiveKey,
|
|
992
1047
|
isSignedInstanceExpired,
|
|
993
1048
|
isSignedUploadTarget,
|
|
@@ -70,7 +70,15 @@ var WixNativeAuthEngine = class {
|
|
|
70
70
|
} catch (e) {
|
|
71
71
|
throw new NativeAuthError("network", e.message);
|
|
72
72
|
}
|
|
73
|
-
|
|
73
|
+
if (!res.ok) {
|
|
74
|
+
throw new NativeAuthError("network", `Account status lookup failed (HTTP ${res.status}).`);
|
|
75
|
+
}
|
|
76
|
+
let json;
|
|
77
|
+
try {
|
|
78
|
+
json = await res.json();
|
|
79
|
+
} catch {
|
|
80
|
+
throw new NativeAuthError("network", "Account status response was not readable JSON.");
|
|
81
|
+
}
|
|
74
82
|
const status = json?.payload?.userAuthStatus ?? json?.userAuthStatus ?? {};
|
|
75
83
|
if (!status.userId) return "newUser";
|
|
76
84
|
return status.hasPassword ? "existingPassword" : "passwordless";
|
|
@@ -402,24 +410,32 @@ var SignedInstanceResolver = class {
|
|
|
402
410
|
this.pending.set(key, task);
|
|
403
411
|
try {
|
|
404
412
|
const record = await task;
|
|
405
|
-
if (record) {
|
|
413
|
+
if (record && this.isCurrent(key, task)) {
|
|
406
414
|
this.memoryCache.set(key, record);
|
|
407
415
|
if (!this.isExpired(record)) this.storage.save(businessId, signedInstanceType, record);
|
|
408
416
|
}
|
|
409
417
|
return record?.signedInstance;
|
|
410
418
|
} catch (error) {
|
|
411
419
|
const sie = error instanceof SignedInstanceError ? error : new SignedInstanceError("Signed-instance fetch failed", "fetchFailed", error);
|
|
412
|
-
this.
|
|
420
|
+
if (this.isCurrent(key, task)) {
|
|
421
|
+
this.failureCache.set(key, { error: sie, expiry: this.now() + this.failureCacheTtlMs });
|
|
422
|
+
}
|
|
413
423
|
this.report(sie);
|
|
414
424
|
throw sie;
|
|
415
425
|
} finally {
|
|
416
|
-
this.pending.delete(key);
|
|
426
|
+
if (this.isCurrent(key, task)) this.pending.delete(key);
|
|
417
427
|
}
|
|
418
428
|
}
|
|
429
|
+
/** Whether `task` is still the in-flight fetch registered for `key` — false once
|
|
430
|
+
* `remove`/`reset` has evicted it, or a later resolve has replaced it. */
|
|
431
|
+
isCurrent(key, task) {
|
|
432
|
+
return this.pending.get(key) === task;
|
|
433
|
+
}
|
|
419
434
|
/** Evict everything for a business (e.g. on leaving / switching it). */
|
|
420
435
|
remove(businessId) {
|
|
421
436
|
for (const k of [...this.memoryCache.keys()]) if (k.startsWith(`${businessId}:`)) this.memoryCache.delete(k);
|
|
422
437
|
for (const k of [...this.failureCache.keys()]) if (k.startsWith(`${businessId}:`)) this.failureCache.delete(k);
|
|
438
|
+
for (const k of [...this.pending.keys()]) if (k.startsWith(`${businessId}:`)) this.pending.delete(k);
|
|
423
439
|
this.storage.deleteBusiness(businessId);
|
|
424
440
|
}
|
|
425
441
|
/** Wipe all caches (e.g. on sign-out). */
|
|
@@ -477,6 +493,14 @@ function normalizeKey(key) {
|
|
|
477
493
|
function isSensitiveKey(key) {
|
|
478
494
|
return SENSITIVE.has(normalizeKey(key));
|
|
479
495
|
}
|
|
496
|
+
function isSensitiveFreeTextName(name) {
|
|
497
|
+
const segments = name.split(/[^A-Za-z0-9]+/).filter(Boolean);
|
|
498
|
+
for (let i = 0; i < segments.length; i++) {
|
|
499
|
+
const tail = normalizeKey(segments.slice(i).join(""));
|
|
500
|
+
for (const k of WIX_SENSITIVE_KEYS) if (tail.startsWith(k)) return true;
|
|
501
|
+
}
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
480
504
|
function redactHeaders(headers) {
|
|
481
505
|
const out = {};
|
|
482
506
|
for (const [k, v] of Object.entries(headers)) {
|
|
@@ -505,10 +529,9 @@ function redactJson(value) {
|
|
|
505
529
|
}
|
|
506
530
|
return value;
|
|
507
531
|
}
|
|
508
|
-
var
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
);
|
|
532
|
+
var FORM_ASSIGNMENT_RE = /([A-Za-z0-9][A-Za-z0-9_.-]*)=([^&;\s]+)/g;
|
|
533
|
+
var JSON_FIELD_RE = /"([^"\\]+)"(\s*:\s*)"((?:\\.|[^"\\])*)"/g;
|
|
534
|
+
var JSON_FIELD_UNTERMINATED_RE = /"([^"\\]+)"(\s*:\s*)"(?:\\.|[^"\\])*$/;
|
|
512
535
|
var BEARER_RE = /\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/gi;
|
|
513
536
|
function redactBody(body, maxLen = 4096) {
|
|
514
537
|
if (!body) return body ?? "";
|
|
@@ -526,7 +549,7 @@ function redactBody(body, maxLen = 4096) {
|
|
|
526
549
|
return out.length > maxLen ? out.slice(0, maxLen) + "\u2026(truncated)" : out;
|
|
527
550
|
}
|
|
528
551
|
function scrubText(s) {
|
|
529
|
-
return s.replace(
|
|
552
|
+
return s.replace(FORM_ASSIGNMENT_RE, (m, name) => isSensitiveFreeTextName(name) ? `${name}=${REDACTED}` : m).replace(JSON_FIELD_RE, (m, name, sep) => isSensitiveFreeTextName(name) ? `"${name}"${sep}"${REDACTED}"` : m).replace(JSON_FIELD_UNTERMINATED_RE, (m, name, sep) => isSensitiveFreeTextName(name) ? `"${name}"${sep}"${REDACTED}` : m).replace(BEARER_RE, (_m, scheme) => `${scheme} ${REDACTED}`);
|
|
530
553
|
}
|
|
531
554
|
|
|
532
555
|
// ../../stdlib/auth-providers/wix/src/session-id.ts
|
|
@@ -661,6 +684,21 @@ function pickHeader(headers, name) {
|
|
|
661
684
|
|
|
662
685
|
// ../../stdlib/auth-providers/wix/src/device-auth.ts
|
|
663
686
|
var DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
|
|
687
|
+
var DeviceAuthError = class extends Error {
|
|
688
|
+
constructor(message, status) {
|
|
689
|
+
super(message);
|
|
690
|
+
this.status = status;
|
|
691
|
+
this.name = "DeviceAuthError";
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
async function readJsonSafely(res) {
|
|
695
|
+
try {
|
|
696
|
+
const value = await res.json();
|
|
697
|
+
return value && typeof value === "object" ? { json: value, parsed: true } : { json: {}, parsed: false };
|
|
698
|
+
} catch {
|
|
699
|
+
return { json: {}, parsed: false };
|
|
700
|
+
}
|
|
701
|
+
}
|
|
664
702
|
function parseDeviceQr(scanned) {
|
|
665
703
|
const s = scanned.trim();
|
|
666
704
|
if (!s) return {};
|
|
@@ -709,7 +747,13 @@ var DeviceAuthClient = class {
|
|
|
709
747
|
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
|
|
710
748
|
body: `client_id=${encodeURIComponent(this.clientId)}&scope=${encodeURIComponent(scope)}`
|
|
711
749
|
});
|
|
712
|
-
const j = await res
|
|
750
|
+
const { json: j } = await readJsonSafely(res);
|
|
751
|
+
if (!res.ok || !j.device_code) {
|
|
752
|
+
throw new DeviceAuthError(
|
|
753
|
+
j.error_description ?? j.error ?? `Could not start device sign-in (HTTP ${res.status}).`,
|
|
754
|
+
res.status
|
|
755
|
+
);
|
|
756
|
+
}
|
|
713
757
|
return {
|
|
714
758
|
deviceCode: j.device_code,
|
|
715
759
|
userCode: j.user_code,
|
|
@@ -722,15 +766,21 @@ var DeviceAuthClient = class {
|
|
|
722
766
|
/** One poll step against the token endpoint. */
|
|
723
767
|
async pollOnce(auth) {
|
|
724
768
|
if (this.devMode) return { terminal: { state: "authorized", tokens: { accessToken: "dev-device-token" } } };
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
769
|
+
let res;
|
|
770
|
+
try {
|
|
771
|
+
res = await this.fetchImpl(`${this.baseURL}/oauth2/token`, {
|
|
772
|
+
method: "POST",
|
|
773
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
|
|
774
|
+
body: `grant_type=${encodeURIComponent(DEVICE_GRANT)}&device_code=${encodeURIComponent(auth.deviceCode)}&client_id=${encodeURIComponent(this.clientId)}`
|
|
775
|
+
});
|
|
776
|
+
} catch {
|
|
777
|
+
return {};
|
|
778
|
+
}
|
|
779
|
+
const { json: j, parsed } = await readJsonSafely(res);
|
|
731
780
|
if (j.access_token) {
|
|
732
781
|
return { terminal: { state: "authorized", tokens: { accessToken: j.access_token, refreshToken: j.refresh_token } } };
|
|
733
782
|
}
|
|
783
|
+
if (!parsed) return {};
|
|
734
784
|
switch (j.error) {
|
|
735
785
|
case "authorization_pending":
|
|
736
786
|
return {};
|
|
@@ -740,8 +790,11 @@ var DeviceAuthClient = class {
|
|
|
740
790
|
return { terminal: { state: "denied", error: "The sign-in request was denied." } };
|
|
741
791
|
case "expired_token":
|
|
742
792
|
return { terminal: { state: "expired", error: "The code expired. Try again." } };
|
|
793
|
+
// `message` is read too so Wix's own error envelope, which carries no
|
|
794
|
+
// `error`, still reaches the user with its actual reason rather than the
|
|
795
|
+
// generic fallback.
|
|
743
796
|
default:
|
|
744
|
-
return { terminal: { state: "error", error: j.error_description ?? j.error ?? "Device authorization failed." } };
|
|
797
|
+
return { terminal: { state: "error", error: j.error_description ?? j.error ?? j.message ?? "Device authorization failed." } };
|
|
745
798
|
}
|
|
746
799
|
}
|
|
747
800
|
/** Run the full poll loop until a terminal state or the code expires. Honors
|
|
@@ -1113,6 +1166,7 @@ export {
|
|
|
1113
1166
|
CLIENT_SITE_KEY_OPTIONS,
|
|
1114
1167
|
CaptchaEscalation,
|
|
1115
1168
|
DeviceAuthClient,
|
|
1169
|
+
DeviceAuthError,
|
|
1116
1170
|
InMemorySessionIDStore,
|
|
1117
1171
|
InMemorySignedInstanceStorage,
|
|
1118
1172
|
MEMBER_SITE_SETTINGS_URL,
|