@jskit-ai/auth-web 0.1.9 → 0.1.11
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.descriptor.mjs +11 -7
- package/package.json +5 -5
- package/src/client/composables/loginView/constants.js +42 -0
- package/src/client/composables/loginView/identityHelpers.js +23 -0
- package/src/client/composables/loginView/oauthCallbackUrl.js +90 -0
- package/src/client/composables/loginView/registerCompletion.js +18 -0
- package/src/client/composables/loginView/rememberedAccountStorage.js +95 -0
- package/src/client/composables/loginView/useLoginViewActions.js +489 -0
- package/src/client/composables/loginView/useLoginViewState.js +262 -0
- package/src/client/composables/loginView/useLoginViewValidation.js +124 -0
- package/src/client/composables/loginView/validationHelpers.js +65 -0
- package/src/client/runtime/authGuardRuntime.js +83 -15
- package/src/client/runtime/useLoginView.js +69 -3
- package/src/client/views/DefaultLoginView.vue +215 -134
- package/src/server/constants/authActionIds.js +1 -0
- package/src/server/controllers/AuthController.js +6 -0
- package/src/server/routes/authRoutes.js +35 -11
- package/src/server/services/AuthWebService.js +7 -0
- package/test/authGuardRuntime.test.js +44 -0
- package/test/clientSurface.test.js +9 -4
- package/test/providerRuntime.test.js +15 -0
- package/test/registerFlow.test.js +40 -0
- package/src/client/composables/useDefaultLoginView.js +0 -935
|
@@ -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
|
-
}
|