@jskit-ai/auth-web 0.1.8 → 0.1.10

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.
@@ -1,935 +0,0 @@
1
- import { computed, onMounted, ref } from "vue";
2
- import { useQueryClient } from "@tanstack/vue-query";
3
- import { mdiGoogle } from "@mdi/js";
4
- import { validateOperationSection } from "@jskit-ai/http-runtime/shared/validators/operationValidation";
5
- import {
6
- OAUTH_QUERY_PARAM_PROVIDER,
7
- OAUTH_QUERY_PARAM_RETURN_TO
8
- } from "@jskit-ai/auth-core/shared/oauthCallbackParams";
9
- import { authRegisterCommand } from "@jskit-ai/auth-core/shared/commands/authRegisterCommand";
10
- import { authLoginPasswordCommand } from "@jskit-ai/auth-core/shared/commands/authLoginPasswordCommand";
11
- import { authLoginOtpRequestCommand } from "@jskit-ai/auth-core/shared/commands/authLoginOtpRequestCommand";
12
- import { authLoginOtpVerifyCommand } from "@jskit-ai/auth-core/shared/commands/authLoginOtpVerifyCommand";
13
- import { authLoginOAuthStartCommand } from "@jskit-ai/auth-core/shared/commands/authLoginOAuthStartCommand";
14
- import { authLoginOAuthCompleteCommand } from "@jskit-ai/auth-core/shared/commands/authLoginOAuthCompleteCommand";
15
- import { authPasswordResetRequestCommand } from "@jskit-ai/auth-core/shared/commands/authPasswordResetRequestCommand";
16
- import { AUTH_PATHS, buildAuthOauthStartPath } from "@jskit-ai/auth-core/shared/authPaths";
17
- import { useShellWebErrorRuntime } from "@jskit-ai/shell-web/client/error";
18
- import { useWebPlacementContext } from "@jskit-ai/shell-web/client/placement";
19
- import { authHttpRequest } from "../runtime/authHttpClient.js";
20
- import {
21
- normalizeAuthReturnToPath,
22
- resolveAllowedReturnToOriginsFromPlacementContext
23
- } from "../lib/returnToPath.js";
24
-
25
- const REMEMBERED_ACCOUNT_STORAGE_KEY = "auth.rememberedAccount";
26
-
27
- function normalizeEmailAddress(value) {
28
- return String(value || "")
29
- .trim()
30
- .toLowerCase();
31
- }
32
-
33
- function maskEmail(emailAddress) {
34
- const normalized = normalizeEmailAddress(emailAddress);
35
- const separatorIndex = normalized.indexOf("@");
36
- if (separatorIndex <= 0) {
37
- return normalized;
38
- }
39
-
40
- const localPart = normalized.slice(0, separatorIndex);
41
- const domainPart = normalized.slice(separatorIndex + 1);
42
- const visiblePrefix = localPart.slice(0, 1);
43
- return `${visiblePrefix}***@${domainPart}`;
44
- }
45
-
46
- function resolveLocalStorage() {
47
- if (typeof window === "undefined" || !window.localStorage) {
48
- return null;
49
- }
50
-
51
- try {
52
- const probeKey = "__auth_hint_probe__";
53
- window.localStorage.setItem(probeKey, "1");
54
- window.localStorage.removeItem(probeKey);
55
- return window.localStorage;
56
- } catch {
57
- return null;
58
- }
59
- }
60
-
61
- function createRememberedAccountHint({ email: accountEmail, displayName, maskedEmail, lastUsedAt } = {}) {
62
- const normalizedEmail = normalizeEmailAddress(accountEmail);
63
- if (!normalizedEmail) {
64
- return null;
65
- }
66
-
67
- const normalizedDisplayName = String(displayName || "").trim() || normalizedEmail.split("@")[0] || "User";
68
- const normalizedMaskedEmail =
69
- normalizedEmail.includes("@") && !maskedEmail ? maskEmail(normalizedEmail) : String(maskedEmail || "").trim();
70
-
71
- return {
72
- email: normalizedEmail,
73
- displayName: normalizedDisplayName,
74
- maskedEmail: normalizedMaskedEmail,
75
- lastUsedAt: String(lastUsedAt || new Date().toISOString())
76
- };
77
- }
78
-
79
- function readRememberedAccountHint() {
80
- const storage = resolveLocalStorage();
81
- if (!storage) {
82
- return null;
83
- }
84
-
85
- try {
86
- const rawValue = storage.getItem(REMEMBERED_ACCOUNT_STORAGE_KEY);
87
- if (!rawValue) {
88
- return null;
89
- }
90
- const parsed = JSON.parse(rawValue);
91
- return createRememberedAccountHint(parsed);
92
- } catch {
93
- return null;
94
- }
95
- }
96
-
97
- function writeRememberedAccountHint(hint) {
98
- const storage = resolveLocalStorage();
99
- if (!storage || !hint) {
100
- return;
101
- }
102
-
103
- try {
104
- storage.setItem(
105
- REMEMBERED_ACCOUNT_STORAGE_KEY,
106
- JSON.stringify({
107
- email: hint.email,
108
- displayName: hint.displayName,
109
- maskedEmail: hint.maskedEmail,
110
- lastUsedAt: hint.lastUsedAt
111
- })
112
- );
113
- } catch {
114
- // best effort only
115
- }
116
- }
117
-
118
- function clearRememberedAccountHint() {
119
- const storage = resolveLocalStorage();
120
- if (!storage) {
121
- return;
122
- }
123
-
124
- try {
125
- storage.removeItem(REMEMBERED_ACCOUNT_STORAGE_KEY);
126
- } catch {
127
- // best effort only
128
- }
129
- }
130
-
131
- function stripOAuthParamsFromLocation() {
132
- if (typeof window !== "object" || !window.location) {
133
- return;
134
- }
135
-
136
- const nextUrl = new URL(window.location.href);
137
- const oauthParamKeys = [
138
- "code",
139
- "access_token",
140
- "refresh_token",
141
- "provider_token",
142
- "expires_in",
143
- "expires_at",
144
- "token_type",
145
- "state",
146
- "sb",
147
- "type",
148
- "error",
149
- "error_description",
150
- "errorCode",
151
- "errorDescription",
152
- OAUTH_QUERY_PARAM_PROVIDER,
153
- OAUTH_QUERY_PARAM_RETURN_TO
154
- ];
155
-
156
- oauthParamKeys.forEach((key) => {
157
- nextUrl.searchParams.delete(key);
158
- });
159
-
160
- const hashParams = new URLSearchParams((nextUrl.hash || "").replace(/^#/, ""));
161
- oauthParamKeys.forEach((key) => {
162
- hashParams.delete(key);
163
- });
164
-
165
- const nextHash = hashParams.toString();
166
- window.history.replaceState({}, "", `${nextUrl.pathname}${nextUrl.search}${nextHash ? `#${nextHash}` : ""}`);
167
- }
168
-
169
- function readOAuthCallbackParamsFromLocation() {
170
- if (typeof window !== "object" || !window.location) {
171
- return null;
172
- }
173
-
174
- const searchParams = new URLSearchParams(window.location.search || "");
175
- const hashParams = new URLSearchParams((window.location.hash || "").replace(/^#/, ""));
176
-
177
- const code = String(searchParams.get("code") || hashParams.get("code") || "").trim();
178
- const accessToken = String(searchParams.get("access_token") || hashParams.get("access_token") || "").trim();
179
- const refreshToken = String(searchParams.get("refresh_token") || hashParams.get("refresh_token") || "").trim();
180
- const errorCode = String(
181
- searchParams.get("error") ||
182
- hashParams.get("error") ||
183
- searchParams.get("errorCode") ||
184
- hashParams.get("errorCode") ||
185
- ""
186
- ).trim();
187
- const errorDescription = String(
188
- searchParams.get("error_description") ||
189
- hashParams.get("error_description") ||
190
- searchParams.get("errorDescription") ||
191
- hashParams.get("errorDescription") ||
192
- ""
193
- ).trim();
194
- const hasSessionPair = Boolean(accessToken && refreshToken);
195
-
196
- if (!code && !hasSessionPair && !errorCode) {
197
- return null;
198
- }
199
-
200
- return {
201
- code,
202
- accessToken,
203
- refreshToken,
204
- hasSessionPair,
205
- errorCode,
206
- errorDescription,
207
- provider: String(searchParams.get(OAUTH_QUERY_PARAM_PROVIDER) || "").trim().toLowerCase(),
208
- returnTo: String(searchParams.get(OAUTH_QUERY_PARAM_RETURN_TO) || "").trim()
209
- };
210
- }
211
-
212
- function validateCommandSection(commandResource, section, payload) {
213
- if (!commandResource || !commandResource.operation) {
214
- return {
215
- ok: true,
216
- fieldErrors: {},
217
- globalErrors: []
218
- };
219
- }
220
-
221
- return validateOperationSection({
222
- operation: commandResource.operation,
223
- section,
224
- value: payload
225
- });
226
- }
227
-
228
- function resolveValidationMessage(validationResult, fallbackMessage = "Validation failed.") {
229
- if (!validationResult || validationResult.ok) {
230
- return "";
231
- }
232
-
233
- const fieldErrors = validationResult.fieldErrors && typeof validationResult.fieldErrors === "object"
234
- ? validationResult.fieldErrors
235
- : {};
236
- const firstFieldError = Object.values(fieldErrors).find((entry) => String(entry || "").trim().length > 0);
237
- if (firstFieldError) {
238
- return String(firstFieldError);
239
- }
240
-
241
- const globalErrors = Array.isArray(validationResult.globalErrors) ? validationResult.globalErrors : [];
242
- const firstGlobalError = globalErrors.find((entry) => String(entry || "").trim().length > 0);
243
- if (firstGlobalError) {
244
- return String(firstGlobalError);
245
- }
246
-
247
- return String(fallbackMessage || "Validation failed.");
248
- }
249
-
250
- export function useDefaultLoginView() {
251
- const queryClient = useQueryClient();
252
- const errorRuntime = useShellWebErrorRuntime();
253
- const { context: placementContext } = useWebPlacementContext();
254
- const sessionQueryKey = Object.freeze(["auth-web", "session"]);
255
- const mode = ref("login");
256
- const email = ref("");
257
- const password = ref("");
258
- const confirmPassword = ref("");
259
- const otpCode = ref("");
260
- const showPassword = ref(false);
261
- const showConfirmPassword = ref(false);
262
- const emailTouched = ref(false);
263
- const passwordTouched = ref(false);
264
- const confirmPasswordTouched = ref(false);
265
- const otpCodeTouched = ref(false);
266
- const submitAttempted = ref(false);
267
- const rememberAccountOnDevice = ref(true);
268
- const rememberedAccount = ref(null);
269
- const useRememberedAccount = ref(false);
270
- const oauthProviders = ref([]);
271
- const oauthDefaultProvider = ref("");
272
- const loading = ref(false);
273
- const otpRequestPending = ref(false);
274
- const errorMessage = ref("");
275
- const infoMessage = ref("");
276
-
277
- const isLogin = computed(() => mode.value === "login");
278
- const isRegister = computed(() => mode.value === "register");
279
- const isForgot = computed(() => mode.value === "forgot");
280
- const isOtp = computed(() => mode.value === "otp");
281
- const showRememberedAccount = computed(
282
- () => (isLogin.value || isOtp.value) && useRememberedAccount.value && Boolean(rememberedAccount.value)
283
- );
284
- const rememberedAccountDisplayName = computed(() => String(rememberedAccount.value?.displayName || "your account"));
285
- const rememberedAccountMaskedEmail = computed(() => String(rememberedAccount.value?.maskedEmail || ""));
286
- const rememberedAccountSwitchLabel = "Use another account";
287
-
288
- const authTitle = computed(() => {
289
- if (isRegister.value) {
290
- return "Create your account";
291
- }
292
- if (isForgot.value) {
293
- return "Reset your password";
294
- }
295
- if (isOtp.value) {
296
- return "Use one-time code";
297
- }
298
- return "Welcome back";
299
- });
300
-
301
- const authSubtitle = computed(() => {
302
- if (isRegister.value) {
303
- return "Register to access your workspace.";
304
- }
305
- if (isForgot.value) {
306
- return "We will send password reset instructions to your email.";
307
- }
308
- if (isOtp.value) {
309
- return "Request a one-time login code and verify it below.";
310
- }
311
- return "Sign in to continue.";
312
- });
313
-
314
- const submitLabel = computed(() => {
315
- if (isRegister.value) {
316
- return "Register";
317
- }
318
- if (isForgot.value) {
319
- return "Send reset instructions";
320
- }
321
- if (isOtp.value) {
322
- return "Verify code";
323
- }
324
- return "Sign in";
325
- });
326
-
327
- const emailErrorMessages = computed(() => {
328
- const shouldValidate = submitAttempted.value || emailTouched.value;
329
- if (!shouldValidate) {
330
- return [];
331
- }
332
-
333
- const normalizedEmail = String(email.value || "").trim().toLowerCase();
334
- const command = isRegister.value
335
- ? authRegisterCommand
336
- : isForgot.value
337
- ? authPasswordResetRequestCommand
338
- : isOtp.value
339
- ? authLoginOtpRequestCommand
340
- : authLoginPasswordCommand;
341
- const payload = isRegister.value || isLogin.value
342
- ? {
343
- email: normalizedEmail,
344
- password: String(password.value || "")
345
- }
346
- : {
347
- email: normalizedEmail
348
- };
349
- const parsed = validateCommandSection(command, "bodyValidator", payload);
350
- const message = parsed.fieldErrors?.email;
351
- if (message) {
352
- return [String(message)];
353
- }
354
- return [];
355
- });
356
-
357
- const passwordErrorMessages = computed(() => {
358
- const shouldValidate = submitAttempted.value || passwordTouched.value;
359
- if (!shouldValidate || isForgot.value || isOtp.value) {
360
- return [];
361
- }
362
-
363
- const normalizedEmail = String(email.value || "").trim().toLowerCase();
364
- const command = isRegister.value ? authRegisterCommand : authLoginPasswordCommand;
365
- const parsed = validateCommandSection(command, "bodyValidator", {
366
- email: normalizedEmail,
367
- password: String(password.value || "")
368
- });
369
- const message = parsed.fieldErrors?.password;
370
- if (message) {
371
- return [String(message)];
372
- }
373
-
374
- return [];
375
- });
376
-
377
- const confirmPasswordErrorMessages = computed(() => {
378
- const shouldValidate = submitAttempted.value || confirmPasswordTouched.value;
379
- if (!shouldValidate || !isRegister.value) {
380
- return [];
381
- }
382
- if (String(confirmPassword.value || "").trim() !== String(password.value || "").trim()) {
383
- return ["Passwords do not match."];
384
- }
385
- return [];
386
- });
387
-
388
- const otpCodeErrorMessages = computed(() => {
389
- const shouldValidate = submitAttempted.value || otpCodeTouched.value;
390
- if (!shouldValidate || !isOtp.value) {
391
- return [];
392
- }
393
-
394
- const parsed = validateCommandSection(authLoginOtpVerifyCommand, "bodyValidator", {
395
- token: String(otpCode.value || "").trim()
396
- });
397
- const message = parsed.fieldErrors?.token;
398
- if (message) {
399
- return [String(message)];
400
- }
401
-
402
- return [];
403
- });
404
-
405
- const canSubmit = computed(() => {
406
- if (loading.value) {
407
- return false;
408
- }
409
-
410
- if (emailErrorMessages.value.length > 0) {
411
- return false;
412
- }
413
-
414
- if (isRegister.value || isLogin.value) {
415
- return passwordErrorMessages.value.length < 1 && confirmPasswordErrorMessages.value.length < 1;
416
- }
417
- if (isOtp.value) {
418
- return otpCodeErrorMessages.value.length < 1;
419
- }
420
- return true;
421
- });
422
-
423
- const allowedReturnToOrigins = computed(() =>
424
- resolveAllowedReturnToOriginsFromPlacementContext(placementContext.value)
425
- );
426
- const requestedReturnTo = ref(
427
- normalizeAuthReturnToPath(
428
- typeof window === "object" ? new URLSearchParams(window.location.search || "").get("returnTo") : "/",
429
- "/",
430
- {
431
- allowedOrigins: allowedReturnToOrigins.value
432
- }
433
- )
434
- );
435
-
436
- function reportAuthFeedback({
437
- message,
438
- severity = "error",
439
- channel = "banner",
440
- dedupeKey = ""
441
- } = {}) {
442
- const normalizedMessage = String(message || "").trim();
443
- if (!normalizedMessage) {
444
- return;
445
- }
446
-
447
- errorRuntime.report({
448
- source: "auth-web.default-login-view",
449
- message: normalizedMessage,
450
- severity,
451
- channel,
452
- dedupeKey: dedupeKey || `auth-web.default-login-view:${severity}:${normalizedMessage}`,
453
- dedupeWindowMs: 3000
454
- });
455
- }
456
-
457
- function setErrorMessage(message, dedupeKey = "") {
458
- const normalizedMessage = String(message || "").trim();
459
- errorMessage.value = normalizedMessage;
460
- if (!normalizedMessage) {
461
- return;
462
- }
463
-
464
- reportAuthFeedback({
465
- message: normalizedMessage,
466
- severity: "error",
467
- channel: "banner",
468
- dedupeKey
469
- });
470
- }
471
-
472
- function setInfoMessage(message, dedupeKey = "") {
473
- const normalizedMessage = String(message || "").trim();
474
- infoMessage.value = normalizedMessage;
475
- if (!normalizedMessage) {
476
- return;
477
- }
478
-
479
- reportAuthFeedback({
480
- message: normalizedMessage,
481
- severity: "info",
482
- channel: "snackbar",
483
- dedupeKey
484
- });
485
- }
486
-
487
- function clearTransientMessages() {
488
- setErrorMessage("");
489
- setInfoMessage("");
490
- }
491
-
492
- function resetTransientValidationState() {
493
- submitAttempted.value = false;
494
- emailTouched.value = false;
495
- passwordTouched.value = false;
496
- confirmPasswordTouched.value = false;
497
- otpCodeTouched.value = false;
498
- }
499
-
500
- function applyRememberedAccountHint(hint) {
501
- if (!hint) {
502
- rememberedAccount.value = null;
503
- useRememberedAccount.value = false;
504
- return;
505
- }
506
-
507
- rememberedAccount.value = hint;
508
- useRememberedAccount.value = true;
509
- rememberAccountOnDevice.value = true;
510
- email.value = String(hint.email || "").trim();
511
- }
512
-
513
- function applyRememberedAccountPreference({ email: accountEmail, displayName, shouldRemember } = {}) {
514
- const rememberedHint = createRememberedAccountHint({
515
- email: accountEmail,
516
- displayName,
517
- lastUsedAt: new Date().toISOString()
518
- });
519
-
520
- if (shouldRemember && rememberedHint) {
521
- writeRememberedAccountHint(rememberedHint);
522
- applyRememberedAccountHint(rememberedHint);
523
- return;
524
- }
525
-
526
- clearRememberedAccountHint();
527
- rememberedAccount.value = null;
528
- useRememberedAccount.value = false;
529
- }
530
-
531
- function switchAccount() {
532
- clearRememberedAccountHint();
533
- rememberedAccount.value = null;
534
- useRememberedAccount.value = false;
535
- rememberAccountOnDevice.value = false;
536
- mode.value = "login";
537
- email.value = "";
538
- password.value = "";
539
- confirmPassword.value = "";
540
- otpCode.value = "";
541
- showPassword.value = false;
542
- showConfirmPassword.value = false;
543
- clearTransientMessages();
544
- resetTransientValidationState();
545
- }
546
-
547
- function switchMode(nextMode) {
548
- if (nextMode === mode.value) {
549
- return;
550
- }
551
- mode.value = nextMode;
552
- password.value = "";
553
- confirmPassword.value = "";
554
- otpCode.value = "";
555
- showPassword.value = false;
556
- showConfirmPassword.value = false;
557
- clearTransientMessages();
558
- resetTransientValidationState();
559
-
560
- if (nextMode !== "login" && nextMode !== "otp") {
561
- useRememberedAccount.value = false;
562
- return;
563
- }
564
-
565
- if (rememberedAccount.value) {
566
- useRememberedAccount.value = true;
567
- }
568
- }
569
-
570
- function oauthProviderButtonLabel(provider) {
571
- const providerLabel = String(provider?.label || provider?.id || "OAuth provider");
572
- if (isRegister.value) {
573
- return `Register with ${providerLabel}`;
574
- }
575
- if (showRememberedAccount.value) {
576
- return `Continue with ${providerLabel} as ${rememberedAccountDisplayName.value}`;
577
- }
578
- return `Continue with ${providerLabel}`;
579
- }
580
-
581
- function oauthProviderIcon(provider) {
582
- if (String(provider?.id || "").toLowerCase() === "google") {
583
- return mdiGoogle;
584
- }
585
- return undefined;
586
- }
587
-
588
- async function request(path, options = {}) {
589
- return authHttpRequest(path, {
590
- method: options.method || "GET",
591
- ...(options.body ? { body: options.body } : {})
592
- });
593
- }
594
-
595
- function applySessionPayload(payload) {
596
- oauthProviders.value = Array.isArray(payload?.oauthProviders)
597
- ? payload.oauthProviders
598
- .map((provider) => {
599
- if (!provider || typeof provider !== "object") {
600
- return null;
601
- }
602
- const id = String(provider.id || "")
603
- .trim()
604
- .toLowerCase();
605
- if (!id) {
606
- return null;
607
- }
608
- return {
609
- id,
610
- label: String(provider.label || id).trim() || id
611
- };
612
- })
613
- .filter(Boolean)
614
- : [];
615
-
616
- oauthDefaultProvider.value = String(payload?.oauthDefaultProvider || "")
617
- .trim()
618
- .toLowerCase();
619
- }
620
-
621
- function resolveDefaultOAuthProvider() {
622
- const explicit = oauthDefaultProvider.value;
623
- if (explicit) {
624
- return explicit;
625
- }
626
- return String(oauthProviders.value[0]?.id || "")
627
- .trim()
628
- .toLowerCase();
629
- }
630
-
631
- async function refreshSession() {
632
- const session = await queryClient.fetchQuery({
633
- queryKey: sessionQueryKey,
634
- queryFn: () => request(AUTH_PATHS.SESSION),
635
- staleTime: 0
636
- });
637
- applySessionPayload(session);
638
- return session;
639
- }
640
-
641
- async function completeLogin() {
642
- const session = await refreshSession();
643
- if (!session?.authenticated) {
644
- throw new Error("Login succeeded but the session is not active yet. Please retry.");
645
- }
646
- if (typeof window === "object" && window.location) {
647
- window.location.replace(requestedReturnTo.value);
648
- }
649
- }
650
-
651
- async function handleOAuthCallbackIfPresent() {
652
- const callbackParams = readOAuthCallbackParamsFromLocation();
653
- if (!callbackParams) {
654
- return false;
655
- }
656
-
657
- requestedReturnTo.value = normalizeAuthReturnToPath(callbackParams.returnTo, requestedReturnTo.value, {
658
- allowedOrigins: allowedReturnToOrigins.value
659
- });
660
-
661
- const provider = String(callbackParams.provider || resolveDefaultOAuthProvider() || "")
662
- .trim()
663
- .toLowerCase();
664
- const oauthError = callbackParams.errorCode;
665
- const oauthErrorDescription = callbackParams.errorDescription;
666
-
667
- if (!provider) {
668
- setErrorMessage("OAuth provider is missing from callback.", "auth-web.default-login-view:oauth-missing-provider");
669
- stripOAuthParamsFromLocation();
670
- return true;
671
- }
672
-
673
- if (oauthError) {
674
- setErrorMessage(oauthErrorDescription || oauthError, "auth-web.default-login-view:oauth-callback-error");
675
- stripOAuthParamsFromLocation();
676
- return true;
677
- }
678
-
679
- loading.value = true;
680
- setErrorMessage("");
681
- setInfoMessage("");
682
-
683
- try {
684
- const payload = {
685
- provider
686
- };
687
- if (callbackParams.code) {
688
- payload.code = callbackParams.code;
689
- }
690
- if (callbackParams.hasSessionPair) {
691
- payload.accessToken = callbackParams.accessToken;
692
- payload.refreshToken = callbackParams.refreshToken;
693
- }
694
-
695
- const parsedPayload = validateCommandSection(authLoginOAuthCompleteCommand, "bodyValidator", payload);
696
- if (!parsedPayload.ok) {
697
- throw new Error(resolveValidationMessage(parsedPayload, "Invalid OAuth callback payload."));
698
- }
699
-
700
- const oauthResult = await request(AUTH_PATHS.OAUTH_COMPLETE, {
701
- method: "POST",
702
- body: payload
703
- });
704
- applyRememberedAccountPreference({
705
- email: oauthResult?.email || email.value,
706
- displayName: oauthResult?.username || oauthResult?.email || email.value,
707
- shouldRemember: rememberAccountOnDevice.value !== false
708
- });
709
-
710
- stripOAuthParamsFromLocation();
711
- await completeLogin();
712
- } catch (error) {
713
- setErrorMessage(String(error?.message || "Unable to complete OAuth sign-in."));
714
- stripOAuthParamsFromLocation();
715
- } finally {
716
- loading.value = false;
717
- }
718
-
719
- return true;
720
- }
721
-
722
- async function submitAuth() {
723
- submitAttempted.value = true;
724
- clearTransientMessages();
725
- if (!canSubmit.value) {
726
- return;
727
- }
728
-
729
- loading.value = true;
730
-
731
- try {
732
- const normalizedEmail = String(email.value || "").trim().toLowerCase();
733
- const shouldRememberAccount = rememberAccountOnDevice.value !== false;
734
-
735
- if (isRegister.value) {
736
- const registerPayload = {
737
- email: normalizedEmail,
738
- password: String(password.value || "")
739
- };
740
- const parsedRegister = validateCommandSection(authRegisterCommand, "bodyValidator", registerPayload);
741
- if (!parsedRegister.ok) {
742
- throw new Error(resolveValidationMessage(parsedRegister, "Unable to register."));
743
- }
744
-
745
- const registerResult = await request(AUTH_PATHS.REGISTER, {
746
- method: "POST",
747
- body: registerPayload
748
- });
749
- applyRememberedAccountPreference({
750
- email: normalizedEmail,
751
- displayName: registerResult?.username || normalizedEmail,
752
- shouldRemember: shouldRememberAccount
753
- });
754
- await completeLogin();
755
- return;
756
- }
757
-
758
- if (isForgot.value) {
759
- const forgotPayload = { email: normalizedEmail };
760
- const parsedForgot = validateCommandSection(authPasswordResetRequestCommand, "bodyValidator", forgotPayload);
761
- if (!parsedForgot.ok) {
762
- throw new Error(resolveValidationMessage(parsedForgot, "Unable to request password reset."));
763
- }
764
-
765
- await request(AUTH_PATHS.PASSWORD_FORGOT, {
766
- method: "POST",
767
- body: forgotPayload
768
- });
769
- setInfoMessage("Password reset instructions sent.", "auth-web.default-login-view:password-reset-sent");
770
- return;
771
- }
772
-
773
- if (isOtp.value) {
774
- const otpPayload = {
775
- email: normalizedEmail,
776
- token: String(otpCode.value || "").trim(),
777
- type: "email"
778
- };
779
- const parsedOtp = validateCommandSection(authLoginOtpVerifyCommand, "bodyValidator", otpPayload);
780
- if (!parsedOtp.ok) {
781
- throw new Error(resolveValidationMessage(parsedOtp, "Unable to verify one-time code."));
782
- }
783
-
784
- const otpResult = await request(AUTH_PATHS.LOGIN_OTP_VERIFY, {
785
- method: "POST",
786
- body: otpPayload
787
- });
788
- applyRememberedAccountPreference({
789
- email: normalizedEmail,
790
- displayName: otpResult?.username || normalizedEmail,
791
- shouldRemember: shouldRememberAccount
792
- });
793
- await completeLogin();
794
- return;
795
- }
796
-
797
- const loginPayload = {
798
- email: normalizedEmail,
799
- password: String(password.value || "")
800
- };
801
- const parsedLogin = validateCommandSection(authLoginPasswordCommand, "bodyValidator", loginPayload);
802
- if (!parsedLogin.ok) {
803
- throw new Error(resolveValidationMessage(parsedLogin, "Unable to sign in."));
804
- }
805
-
806
- const loginResult = await request(AUTH_PATHS.LOGIN, {
807
- method: "POST",
808
- body: loginPayload
809
- });
810
- applyRememberedAccountPreference({
811
- email: normalizedEmail,
812
- displayName: loginResult?.username || normalizedEmail,
813
- shouldRemember: shouldRememberAccount
814
- });
815
- await completeLogin();
816
- } catch (error) {
817
- setErrorMessage(String(error?.message || "Authentication failed."));
818
- } finally {
819
- loading.value = false;
820
- }
821
- }
822
-
823
- async function requestOtpCode() {
824
- otpRequestPending.value = true;
825
- setErrorMessage("");
826
- setInfoMessage("");
827
- try {
828
- const normalizedEmail = String(email.value || "").trim().toLowerCase();
829
- const otpRequestPayload = {
830
- email: normalizedEmail,
831
- returnTo: requestedReturnTo.value
832
- };
833
- const parsedRequest = validateCommandSection(authLoginOtpRequestCommand, "bodyValidator", otpRequestPayload);
834
- if (!parsedRequest.ok) {
835
- throw new Error(resolveValidationMessage(parsedRequest, "Unable to request one-time code."));
836
- }
837
- await request(AUTH_PATHS.LOGIN_OTP_REQUEST, {
838
- method: "POST",
839
- body: otpRequestPayload
840
- });
841
- setInfoMessage("One-time code sent. Check your inbox.", "auth-web.default-login-view:otp-code-sent");
842
- } catch (error) {
843
- setErrorMessage(String(error?.message || "Unable to request one-time code."));
844
- } finally {
845
- otpRequestPending.value = false;
846
- }
847
- }
848
-
849
- function startOAuthSignIn(providerId) {
850
- const provider = String(providerId || "").trim().toLowerCase();
851
- if (!provider || typeof window !== "object" || !window.location) {
852
- return;
853
- }
854
-
855
- const paramsPayload = {
856
- provider
857
- };
858
- const queryPayload = {
859
- returnTo: requestedReturnTo.value
860
- };
861
- const parsedParams = validateCommandSection(authLoginOAuthStartCommand, "paramsValidator", paramsPayload);
862
- if (!parsedParams.ok) {
863
- setErrorMessage(resolveValidationMessage(parsedParams, "OAuth provider id is invalid."));
864
- return;
865
- }
866
- const parsedQuery = validateCommandSection(authLoginOAuthStartCommand, "queryValidator", queryPayload);
867
- if (!parsedQuery.ok) {
868
- setErrorMessage(resolveValidationMessage(parsedQuery, "OAuth return path is invalid."));
869
- return;
870
- }
871
-
872
- const params = new URLSearchParams(queryPayload);
873
- const oauthStartPath = buildAuthOauthStartPath(provider);
874
- window.location.assign(`${oauthStartPath}?${params.toString()}`);
875
- }
876
-
877
- onMounted(async () => {
878
- applyRememberedAccountHint(readRememberedAccountHint());
879
- loading.value = true;
880
- try {
881
- const session = await refreshSession();
882
- const callbackHandled = await handleOAuthCallbackIfPresent();
883
- if (!callbackHandled && session?.authenticated && typeof window === "object" && window.location) {
884
- window.location.replace(requestedReturnTo.value);
885
- return;
886
- }
887
- } catch (error) {
888
- setErrorMessage(String(error?.message || "Unable to initialize sign in."));
889
- } finally {
890
- loading.value = false;
891
- }
892
- });
893
-
894
- return {
895
- authTitle,
896
- authSubtitle,
897
- isForgot,
898
- isOtp,
899
- isLogin,
900
- isRegister,
901
- showRememberedAccount,
902
- switchMode,
903
- submitAuth,
904
- rememberedAccountDisplayName,
905
- rememberedAccountMaskedEmail,
906
- rememberedAccountSwitchLabel,
907
- switchAccount,
908
- email,
909
- emailErrorMessages,
910
- emailTouched,
911
- password,
912
- showPassword,
913
- passwordErrorMessages,
914
- passwordTouched,
915
- confirmPassword,
916
- showConfirmPassword,
917
- confirmPasswordErrorMessages,
918
- confirmPasswordTouched,
919
- otpCode,
920
- otpCodeErrorMessages,
921
- otpCodeTouched,
922
- rememberAccountOnDevice,
923
- otpRequestPending,
924
- requestOtpCode,
925
- oauthProviders,
926
- loading,
927
- oauthProviderIcon,
928
- startOAuthSignIn,
929
- oauthProviderButtonLabel,
930
- errorMessage,
931
- infoMessage,
932
- canSubmit,
933
- submitLabel
934
- };
935
- }