keystone-design-bootstrap 1.0.102 → 1.0.103
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/package.json
CHANGED
|
@@ -204,7 +204,30 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
204
204
|
body: JSON.stringify({ phone: fullPhone, code }),
|
|
205
205
|
});
|
|
206
206
|
const result = await res.json().catch(() => ({}));
|
|
207
|
-
if (!res.ok
|
|
207
|
+
if (!res.ok) {
|
|
208
|
+
setError(result.error || 'That code is invalid or expired.');
|
|
209
|
+
captureEvent('portal_login_failed', { step: 'signin', reason: result.code || 'verification_failed' });
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Compatibility path: verify_code may directly authenticate and set the
|
|
214
|
+
// session cookie via the route handler (no passwordless_auth needed).
|
|
215
|
+
if (result.authenticated) {
|
|
216
|
+
await setPixelUserData({ email: result.email || undefined, phone: fullPhone });
|
|
217
|
+
firePixelEvent('Lead', undefined, result?.eventId);
|
|
218
|
+
captureEvent('portal_login_step_advanced', {
|
|
219
|
+
from_step: 'signin',
|
|
220
|
+
to_step: 'authenticated',
|
|
221
|
+
reason: 'verification_direct_auth',
|
|
222
|
+
});
|
|
223
|
+
captureEvent('portal_login_completed', { flow: 'signin' });
|
|
224
|
+
setResendAvailableAt(null);
|
|
225
|
+
setSecondsUntilResend(0);
|
|
226
|
+
if (onSuccess) onSuccess(); else router.refresh();
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (!result.verification_token) {
|
|
208
231
|
setError(result.error || 'That code is invalid or expired.');
|
|
209
232
|
captureEvent('portal_login_failed', { step: 'signin', reason: result.code || 'verification_failed' });
|
|
210
233
|
return;
|
|
@@ -51,6 +51,67 @@ function responseData(json: Record<string, unknown>): Record<string, unknown> {
|
|
|
51
51
|
return (json.data as Record<string, unknown>) || json;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
function readStringField(obj: Record<string, unknown>, key: string): string | null {
|
|
55
|
+
const value = obj[key];
|
|
56
|
+
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function readNestedStringField(
|
|
60
|
+
obj: Record<string, unknown>,
|
|
61
|
+
parentKey: string,
|
|
62
|
+
key: string
|
|
63
|
+
): string | null {
|
|
64
|
+
const parent = obj[parentKey];
|
|
65
|
+
if (!parent || typeof parent !== 'object') return null;
|
|
66
|
+
const value = (parent as Record<string, unknown>)[key];
|
|
67
|
+
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function extractAuthToken(data: Record<string, unknown>): string | null {
|
|
71
|
+
const direct =
|
|
72
|
+
readStringField(data, 'token') ||
|
|
73
|
+
readStringField(data, 'auth_token') ||
|
|
74
|
+
readStringField(data, 'access_token') ||
|
|
75
|
+
readStringField(data, 'session_token') ||
|
|
76
|
+
readStringField(data, 'jwt') ||
|
|
77
|
+
readNestedStringField(data, 'consumer', 'token') ||
|
|
78
|
+
readNestedStringField(data, 'session', 'token');
|
|
79
|
+
if (direct) return direct;
|
|
80
|
+
|
|
81
|
+
// Compatibility fallback: some backends rename token fields without notice.
|
|
82
|
+
// Walk a few levels and accept any non-empty string on keys containing "token"/"jwt".
|
|
83
|
+
const queue: Array<{ value: unknown; depth: number }> = [{ value: data, depth: 0 }];
|
|
84
|
+
const visited = new Set<object>();
|
|
85
|
+
while (queue.length > 0) {
|
|
86
|
+
const next = queue.shift();
|
|
87
|
+
if (!next) break;
|
|
88
|
+
const { value, depth } = next;
|
|
89
|
+
if (!value || typeof value !== 'object') continue;
|
|
90
|
+
if (visited.has(value as object)) continue;
|
|
91
|
+
visited.add(value as object);
|
|
92
|
+
|
|
93
|
+
const entries = Object.entries(value as Record<string, unknown>);
|
|
94
|
+
for (const [key, candidate] of entries) {
|
|
95
|
+
// Never treat verification tokens as auth/session JWTs.
|
|
96
|
+
if (/verification[_-]?token/i.test(key)) {
|
|
97
|
+
if (depth < 4 && candidate && typeof candidate === 'object') {
|
|
98
|
+
queue.push({ value: candidate, depth: depth + 1 });
|
|
99
|
+
}
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const isTokenLikeKey = /token|jwt/i.test(key);
|
|
103
|
+
if (isTokenLikeKey && typeof candidate === 'string' && candidate.length > 0) {
|
|
104
|
+
return candidate;
|
|
105
|
+
}
|
|
106
|
+
if (depth < 4 && candidate && typeof candidate === 'object') {
|
|
107
|
+
queue.push({ value: candidate, depth: depth + 1 });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
54
115
|
// POST /api/consumer/initiate
|
|
55
116
|
// Looks up an existing user by email or phone, creating a Contact if needed.
|
|
56
117
|
// Returns { exists, firstName, hasPassword }.
|
|
@@ -108,7 +169,8 @@ async function handleLogin(request: Request, NR: NextResponseLike): Promise<Resp
|
|
|
108
169
|
);
|
|
109
170
|
}
|
|
110
171
|
|
|
111
|
-
const
|
|
172
|
+
const data = responseData(json);
|
|
173
|
+
const token = extractAuthToken(data);
|
|
112
174
|
if (!token) return NR.json({ error: 'No token received.' }, { status: 500 });
|
|
113
175
|
|
|
114
176
|
const response = NR.json({});
|
|
@@ -144,15 +206,16 @@ async function handleSignup(request: Request, NR: NextResponseLike): Promise<Res
|
|
|
144
206
|
}),
|
|
145
207
|
});
|
|
146
208
|
|
|
147
|
-
const json = await res.json().catch(() => ({}))
|
|
209
|
+
const json = await res.json().catch(() => ({})) as Record<string, unknown>;
|
|
148
210
|
if (!res.ok) {
|
|
149
211
|
return NR.json({ error: json.error || 'Signup failed. Please try again.' }, { status: res.status });
|
|
150
212
|
}
|
|
151
213
|
|
|
152
|
-
const
|
|
214
|
+
const data = responseData(json);
|
|
215
|
+
const token = extractAuthToken(data);
|
|
153
216
|
if (!token) return NR.json({ error: 'No token received.' }, { status: 500 });
|
|
154
217
|
|
|
155
|
-
const response = NR.json({ claimed:
|
|
218
|
+
const response = NR.json({ claimed: data?.claimed ?? false });
|
|
156
219
|
response.cookies.set(CONSUMER_TOKEN_COOKIE, token, {
|
|
157
220
|
httpOnly: true,
|
|
158
221
|
secure: process.env.NODE_ENV === 'production',
|
|
@@ -219,6 +282,26 @@ async function handleVerifyCode(request: Request, NR: NextResponseLike): Promise
|
|
|
219
282
|
);
|
|
220
283
|
}
|
|
221
284
|
|
|
285
|
+
// Some surfaces return a direct session token on verify.
|
|
286
|
+
const directToken = extractAuthToken(data);
|
|
287
|
+
if (directToken) {
|
|
288
|
+
const response = NR.json({
|
|
289
|
+
authenticated: true,
|
|
290
|
+
first_name: data?.first_name ?? '',
|
|
291
|
+
last_name: data?.last_name ?? '',
|
|
292
|
+
email: data?.email ?? '',
|
|
293
|
+
eventId: data?.event_id ?? undefined,
|
|
294
|
+
});
|
|
295
|
+
response.cookies.set(CONSUMER_TOKEN_COOKIE, directToken, {
|
|
296
|
+
httpOnly: true,
|
|
297
|
+
secure: process.env.NODE_ENV === 'production',
|
|
298
|
+
sameSite: 'lax',
|
|
299
|
+
path: '/',
|
|
300
|
+
maxAge: COOKIE_MAX_AGE,
|
|
301
|
+
});
|
|
302
|
+
return response;
|
|
303
|
+
}
|
|
304
|
+
|
|
222
305
|
return NR.json({
|
|
223
306
|
verification_token: data?.verification_token,
|
|
224
307
|
first_name: data?.first_name ?? '',
|
|
@@ -260,7 +343,7 @@ async function handlePasswordlessAuth(request: Request, NR: NextResponseLike): P
|
|
|
260
343
|
);
|
|
261
344
|
}
|
|
262
345
|
|
|
263
|
-
const token = data
|
|
346
|
+
const token = extractAuthToken(data);
|
|
264
347
|
if (!token) return NR.json({ error: 'No token received.' }, { status: 500 });
|
|
265
348
|
|
|
266
349
|
// Surface the server-side Lead event_id so the browser can fire a single deduped
|