@luminaryworks/auth-react 0.4.4 → 0.5.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/index.js CHANGED
@@ -1,4 +1,4 @@
1
- (function(){if(typeof document==="undefined")return;var id="lw-auth-react-headless-login";if(document.getElementById(id))return;var s=document.createElement("style");s.id=id;s.textContent="/* src/HeadlessLoginPanel.module.scss */\n.HeadlessLoginPanel_module_panel {\n --lw-auth-theme: #1677ff;\n --lw-auth-theme-deep: color-mix(in srgb, var(--lw-auth-theme) 72%, #000);\n width: min(100%, 400px);\n padding: 28px 28px 22px;\n border-radius: 16px;\n background: rgba(255, 255, 255, 0.96);\n color: #14212b;\n box-shadow: 0 24px 64px rgba(15, 23, 42, 0.28);\n font-family:\n \"Segoe UI\",\n \"PingFang SC\",\n \"Noto Sans SC\",\n sans-serif;\n}\n.HeadlessLoginPanel_module_brand {\n display: flex;\n gap: 14px;\n align-items: center;\n margin-bottom: 12px;\n}\n.HeadlessLoginPanel_module_logo {\n border-radius: 12px;\n display: block;\n}\n.HeadlessLoginPanel_module_product {\n margin: 0;\n font-size: 13px;\n font-weight: 650;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--lw-auth-theme);\n}\n.HeadlessLoginPanel_module_title {\n margin: 2px 0 0;\n font-size: 22px;\n font-weight: 700;\n line-height: 1.25;\n}\n.HeadlessLoginPanel_module_subtitle {\n margin: 0 0 18px;\n font-size: 14px;\n line-height: 1.5;\n color: #4b5c68;\n}\n.HeadlessLoginPanel_module_form {\n display: grid;\n gap: 10px;\n}\n.HeadlessLoginPanel_module_socialStack {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n font-size: 12px;\n}\n.HeadlessLoginPanel_module_input {\n width: 100%;\n box-sizing: border-box;\n border: 1px solid #c9d5de;\n border-radius: 10px;\n padding: 10px 12px;\n font-size: 14px;\n outline: none;\n background: #f7fafc;\n}\n.HeadlessLoginPanel_module_input:focus {\n border-color: var(--lw-auth-theme);\n}\n.HeadlessLoginPanel_module_input:disabled {\n opacity: 0.65;\n cursor: not-allowed;\n}\n.HeadlessLoginPanel_module_passwordField {\n position: relative;\n display: flex;\n align-items: center;\n}\n.HeadlessLoginPanel_module_passwordInput {\n padding-right: 42px;\n}\n.HeadlessLoginPanel_module_passwordToggle {\n position: absolute;\n right: 4px;\n top: 50%;\n transform: translateY(-50%);\n z-index: 1;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n border: none;\n border-radius: 4px;\n padding: 0;\n color: #5a6b76;\n background: transparent;\n cursor: pointer;\n}\n.HeadlessLoginPanel_module_passwordToggle:hover:not(:disabled) {\n color: var(--lw-auth-theme);\n background: color-mix(in srgb, var(--lw-auth-theme) 12%, transparent);\n}\n.HeadlessLoginPanel_module_passwordToggle:focus-visible {\n outline: 2px solid var(--lw-auth-theme);\n outline-offset: 1px;\n}\n.HeadlessLoginPanel_module_passwordToggle:disabled {\n opacity: 0.65;\n cursor: not-allowed;\n}\n.HeadlessLoginPanel_module_primaryBtn {\n border: none;\n border-radius: 10px;\n padding: 11px 14px;\n font-size: 14px;\n font-weight: 650;\n cursor: pointer;\n color: #fff;\n background:\n linear-gradient(\n 135deg,\n var(--lw-auth-theme) 0%,\n var(--lw-auth-theme-deep) 100%);\n}\n.HeadlessLoginPanel_module_primaryBtn:disabled {\n opacity: 0.65;\n cursor: not-allowed;\n}\n.HeadlessLoginPanel_module_socialBtn {\n flex: 1 1 calc(50% - 5px);\n min-width: min(140px, 100%);\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n border: 1px solid #b7c9d4;\n border-radius: 10px;\n padding: 11px 10px;\n font-size: 14px;\n font-weight: 650;\n cursor: pointer;\n color: #14212b;\n background: #fff;\n}\n.HeadlessLoginPanel_module_socialBtn:disabled {\n opacity: 0.65;\n cursor: not-allowed;\n}\n.HeadlessLoginPanel_module_socialLogo {\n flex-shrink: 0;\n display: block;\n}\n.HeadlessLoginPanel_module_logoFallback {\n width: 18px;\n height: 18px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--lw-auth-theme) 14%, #fff);\n color: var(--lw-auth-theme-deep);\n font-size: 11px;\n font-weight: 700;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n.HeadlessLoginPanel_module_cancelBtn {\n margin-top: 10px;\n width: 100%;\n border: none;\n background: transparent;\n color: #5a6b76;\n font-size: 13px;\n cursor: pointer;\n padding: 8px;\n}\n.HeadlessLoginPanel_module_cancelBtn:disabled {\n opacity: 0.65;\n cursor: not-allowed;\n}\n.HeadlessLoginPanel_module_divider {\n display: flex;\n align-items: center;\n gap: 10px;\n margin: 14px 0;\n color: #90a0ab;\n}\n.HeadlessLoginPanel_module_dividerLine {\n flex: 1;\n height: 1px;\n background: #d5e0e7;\n}\n.HeadlessLoginPanel_module_dividerText {\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n}\n.HeadlessLoginPanel_module_hint {\n margin: 10px 0 0;\n font-size: 12px;\n color: #6b7c87;\n line-height: 1.45;\n}\n.HeadlessLoginPanel_module_error {\n margin: 12px 0 0;\n font-size: 13px;\n color: #b42318;\n line-height: 1.4;\n}\n.HeadlessLoginPanel_module_footer {\n margin-top: 18px;\n padding-top: 12px;\n border-top: 1px solid #e4ebf0;\n font-size: 12px;\n color: #6b7c87;\n display: flex;\n gap: 8px;\n justify-content: center;\n}\n/*# sourceMappingURL=index.css.map */";document.head.appendChild(s);})();
1
+ (function(){if(typeof document==="undefined")return;var id="lw-auth-react-headless-login";if(document.getElementById(id))return;var s=document.createElement("style");s.id=id;s.textContent="/* src/HeadlessLoginPanel.module.scss */\n.HeadlessLoginPanel_module_panel {\n --lw-auth-theme: #1677ff;\n --lw-auth-theme-deep: color-mix(in srgb, var(--lw-auth-theme) 72%, #000);\n width: min(100%, 400px);\n padding: 28px 28px 22px;\n border-radius: 16px;\n background: rgba(255, 255, 255, 0.96);\n color: #14212b;\n box-shadow: 0 24px 64px rgba(15, 23, 42, 0.28);\n font-family:\n \"Segoe UI\",\n \"PingFang SC\",\n \"Noto Sans SC\",\n sans-serif;\n}\n.HeadlessLoginPanel_module_brand {\n display: flex;\n gap: 14px;\n align-items: center;\n margin-bottom: 12px;\n}\n.HeadlessLoginPanel_module_logo {\n border-radius: 12px;\n display: block;\n}\n.HeadlessLoginPanel_module_product {\n margin: 0;\n font-size: 13px;\n font-weight: 650;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--lw-auth-theme);\n}\n.HeadlessLoginPanel_module_title {\n margin: 2px 0 0;\n font-size: 22px;\n font-weight: 700;\n line-height: 1.25;\n}\n.HeadlessLoginPanel_module_subtitle {\n margin: 0 0 18px;\n font-size: 14px;\n line-height: 1.5;\n color: #4b5c68;\n}\n.HeadlessLoginPanel_module_form {\n display: grid;\n gap: 10px;\n}\n.HeadlessLoginPanel_module_channelTabs {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 6px;\n margin-bottom: 2px;\n}\n.HeadlessLoginPanel_module_channelTab {\n border: 1px solid #c9d5de;\n border-radius: 10px;\n padding: 8px 10px;\n font-size: 13px;\n font-weight: 650;\n cursor: pointer;\n color: #4b5c68;\n background: #f7fafc;\n}\n.HeadlessLoginPanel_module_channelTab:disabled {\n opacity: 0.65;\n cursor: not-allowed;\n}\n.HeadlessLoginPanel_module_channelTabActive {\n color: var(--lw-auth-theme-deep);\n border-color: var(--lw-auth-theme);\n background: color-mix(in srgb, var(--lw-auth-theme) 10%, #fff);\n}\n.HeadlessLoginPanel_module_codeRow {\n display: grid;\n grid-template-columns: 1fr auto;\n gap: 8px;\n align-items: stretch;\n}\n.HeadlessLoginPanel_module_codeInput {\n min-width: 0;\n}\n.HeadlessLoginPanel_module_secondaryBtn {\n border: 1px solid #b7c9d4;\n border-radius: 10px;\n padding: 0 12px;\n font-size: 13px;\n font-weight: 650;\n cursor: pointer;\n color: #14212b;\n background: #fff;\n white-space: nowrap;\n}\n.HeadlessLoginPanel_module_secondaryBtn:disabled {\n opacity: 0.65;\n cursor: not-allowed;\n}\n.HeadlessLoginPanel_module_socialStack {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n font-size: 12px;\n}\n.HeadlessLoginPanel_module_input {\n width: 100%;\n box-sizing: border-box;\n border: 1px solid #c9d5de;\n border-radius: 10px;\n padding: 10px 12px;\n font-size: 14px;\n outline: none;\n background: #f7fafc;\n}\n.HeadlessLoginPanel_module_input:focus {\n border-color: var(--lw-auth-theme);\n}\n.HeadlessLoginPanel_module_input:disabled {\n opacity: 0.65;\n cursor: not-allowed;\n}\n.HeadlessLoginPanel_module_passwordField {\n position: relative;\n display: flex;\n align-items: center;\n}\n.HeadlessLoginPanel_module_passwordInput {\n padding-right: 42px;\n}\n.HeadlessLoginPanel_module_passwordToggle {\n position: absolute;\n right: 4px;\n top: 50%;\n transform: translateY(-50%);\n z-index: 1;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n border: none;\n border-radius: 4px;\n padding: 0;\n color: #5a6b76;\n background: transparent;\n cursor: pointer;\n}\n.HeadlessLoginPanel_module_passwordToggle:hover:not(:disabled) {\n color: var(--lw-auth-theme);\n background: color-mix(in srgb, var(--lw-auth-theme) 12%, transparent);\n}\n.HeadlessLoginPanel_module_passwordToggle:focus-visible {\n outline: 2px solid var(--lw-auth-theme);\n outline-offset: 1px;\n}\n.HeadlessLoginPanel_module_passwordToggle:disabled {\n opacity: 0.65;\n cursor: not-allowed;\n}\n.HeadlessLoginPanel_module_primaryBtn {\n border: none;\n border-radius: 10px;\n padding: 11px 14px;\n font-size: 14px;\n font-weight: 650;\n cursor: pointer;\n color: #fff;\n background:\n linear-gradient(\n 135deg,\n var(--lw-auth-theme) 0%,\n var(--lw-auth-theme-deep) 100%);\n}\n.HeadlessLoginPanel_module_primaryBtn:disabled {\n opacity: 0.65;\n cursor: not-allowed;\n}\n.HeadlessLoginPanel_module_socialBtn {\n flex: 1 1 calc(50% - 5px);\n min-width: min(140px, 100%);\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n border: 1px solid #b7c9d4;\n border-radius: 10px;\n padding: 11px 10px;\n font-size: 14px;\n font-weight: 650;\n cursor: pointer;\n color: #14212b;\n background: #fff;\n}\n.HeadlessLoginPanel_module_socialBtn:disabled {\n opacity: 0.65;\n cursor: not-allowed;\n}\n.HeadlessLoginPanel_module_socialLogo {\n flex-shrink: 0;\n display: block;\n}\n.HeadlessLoginPanel_module_logoFallback {\n width: 18px;\n height: 18px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--lw-auth-theme) 14%, #fff);\n color: var(--lw-auth-theme-deep);\n font-size: 11px;\n font-weight: 700;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n.HeadlessLoginPanel_module_cancelBtn {\n margin-top: 10px;\n width: 100%;\n border: none;\n background: transparent;\n color: #5a6b76;\n font-size: 13px;\n cursor: pointer;\n padding: 8px;\n}\n.HeadlessLoginPanel_module_cancelBtn:disabled {\n opacity: 0.65;\n cursor: not-allowed;\n}\n.HeadlessLoginPanel_module_divider {\n display: flex;\n align-items: center;\n gap: 10px;\n margin: 14px 0;\n color: #90a0ab;\n}\n.HeadlessLoginPanel_module_dividerLine {\n flex: 1;\n height: 1px;\n background: #d5e0e7;\n}\n.HeadlessLoginPanel_module_dividerText {\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n}\n.HeadlessLoginPanel_module_hint {\n margin: 10px 0 0;\n font-size: 12px;\n color: #6b7c87;\n line-height: 1.45;\n}\n.HeadlessLoginPanel_module_domainsHint {\n margin: -2px 0 0;\n font-size: 12px;\n line-height: 1.45;\n color: #4b5c68;\n word-break: break-word;\n}\n.HeadlessLoginPanel_module_switchMode {\n margin: 12px 0 0;\n text-align: center;\n}\n.HeadlessLoginPanel_module_switchModeBtn {\n border: none;\n background: transparent;\n color: var(--lw-auth-theme);\n font-size: 13px;\n font-weight: 650;\n cursor: pointer;\n padding: 4px 8px;\n text-decoration: underline;\n text-underline-offset: 3px;\n}\n.HeadlessLoginPanel_module_switchModeBtn:disabled {\n opacity: 0.65;\n cursor: not-allowed;\n}\n.HeadlessLoginPanel_module_error {\n margin: 12px 0 0;\n font-size: 13px;\n color: #b42318;\n line-height: 1.4;\n}\n.HeadlessLoginPanel_module_footer {\n margin-top: 18px;\n padding-top: 12px;\n border-top: 1px solid #e4ebf0;\n font-size: 12px;\n color: #6b7c87;\n display: flex;\n gap: 8px;\n justify-content: center;\n}\n/*# sourceMappingURL=index.css.map */";document.head.appendChild(s);})();
2
2
  'use strict';
3
3
 
4
4
  var oidcClientTs = require('oidc-client-ts');
@@ -55,6 +55,8 @@ var LOGIN_EXPERIENCE_CAPABILITIES = {
55
55
  passwordSignIn: "password-sign-in",
56
56
  /** Username + password self-register via Experience (Logto NewPasswordIdentity). */
57
57
  passwordSignUp: "password-sign-up",
58
+ /** Email + verification code + password self-register. */
59
+ emailCodeSignUp: "email-code-sign-up",
58
60
  socialConnectors: "social-connectors",
59
61
  socialDirectSignIn: "social-direct-sign-in"
60
62
  };
@@ -86,6 +88,160 @@ function resolveLoginExperienceProviderFromEnv(env = {}) {
86
88
  return env.VITE_IAM_PROVIDER ?? env.PUBLIC_IAM_PROVIDER ?? env.IAM_PROVIDER ?? env.IDP_MODE ?? "logto";
87
89
  }
88
90
 
91
+ // src/register-policy.ts
92
+ var DEFAULT_REGISTER_EMAIL_ALLOWLIST = [
93
+ // Google
94
+ "gmail.com",
95
+ "googlemail.com",
96
+ // Tencent / QQ
97
+ "qq.com",
98
+ "foxmail.com",
99
+ // NetEase
100
+ "163.com",
101
+ "126.com",
102
+ "yeah.net",
103
+ // Microsoft
104
+ "outlook.com",
105
+ "hotmail.com",
106
+ "live.com",
107
+ "msn.com",
108
+ // Apple
109
+ "icloud.com",
110
+ "me.com",
111
+ "mac.com",
112
+ // Yahoo
113
+ "yahoo.com",
114
+ "yahoo.co.jp",
115
+ // Proton
116
+ "proton.me",
117
+ "protonmail.com",
118
+ // Others commonly used by real users
119
+ "sina.com",
120
+ "sina.cn",
121
+ "aliyun.com",
122
+ "139.com"
123
+ ];
124
+ var DEFAULT_DISPOSABLE_EMAIL_BLOCKLIST = [
125
+ "mailinator.com",
126
+ "guerrillamail.com",
127
+ "guerrillamail.net",
128
+ "10minutemail.com",
129
+ "tempmail.com",
130
+ "temp-mail.org",
131
+ "yopmail.com",
132
+ "trashmail.com",
133
+ "sharklasers.com",
134
+ "getnada.com",
135
+ "maildrop.cc",
136
+ "discard.email",
137
+ "mailnesia.com"
138
+ ];
139
+ function parseList(raw) {
140
+ if (!raw) return [];
141
+ const parts = Array.isArray(raw) ? raw : String(raw).split(/[\s,;]+/);
142
+ return [
143
+ ...new Set(
144
+ parts.map((s) => s.trim().toLowerCase().replace(/^@/, "")).filter(Boolean)
145
+ )
146
+ ];
147
+ }
148
+ function normalizeRegisterEmailMode(raw) {
149
+ const key = (raw || "allowlist").trim().toLowerCase();
150
+ if (key === "off" || key === "false" || key === "0" || key === "disabled") return "off";
151
+ if (key === "blocklist" || key === "deny" || key === "blacklist") return "blocklist";
152
+ return "allowlist";
153
+ }
154
+ function resolveRegisterEmailPolicy(input = {}) {
155
+ const mode = normalizeRegisterEmailMode(input.mode);
156
+ const allowlist = parseList(input.allowlist);
157
+ const blocklist = parseList(input.blocklist);
158
+ return {
159
+ mode,
160
+ allowlist: allowlist.length ? allowlist : DEFAULT_REGISTER_EMAIL_ALLOWLIST,
161
+ blocklist: blocklist.length ? blocklist : DEFAULT_DISPOSABLE_EMAIL_BLOCKLIST
162
+ };
163
+ }
164
+ function readRegisterEmailPolicyFromEnv(env = {}) {
165
+ return resolveRegisterEmailPolicy({
166
+ mode: env.VITE_AUTH_REGISTER_EMAIL_MODE || env.PUBLIC_AUTH_REGISTER_EMAIL_MODE || env.NEXT_PUBLIC_AUTH_REGISTER_EMAIL_MODE || env.AUTH_REGISTER_EMAIL_MODE,
167
+ allowlist: env.VITE_AUTH_REGISTER_EMAIL_ALLOWLIST || env.PUBLIC_AUTH_REGISTER_EMAIL_ALLOWLIST || env.NEXT_PUBLIC_AUTH_REGISTER_EMAIL_ALLOWLIST || env.AUTH_REGISTER_EMAIL_ALLOWLIST,
168
+ blocklist: env.VITE_AUTH_REGISTER_EMAIL_BLOCKLIST || env.PUBLIC_AUTH_REGISTER_EMAIL_BLOCKLIST || env.NEXT_PUBLIC_AUTH_REGISTER_EMAIL_BLOCKLIST || env.AUTH_REGISTER_EMAIL_BLOCKLIST
169
+ });
170
+ }
171
+ function emailDomain(email) {
172
+ const trimmed = email.trim().toLowerCase();
173
+ const at = trimmed.lastIndexOf("@");
174
+ if (at <= 0 || at === trimmed.length - 1) return null;
175
+ if (trimmed.includes(" ") || trimmed.indexOf("@") !== at) return null;
176
+ return trimmed.slice(at + 1);
177
+ }
178
+ function isValidEmailShape(email) {
179
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim());
180
+ }
181
+ function evaluateRegisterEmail(email, policy = resolveRegisterEmailPolicy()) {
182
+ if (!isValidEmailShape(email)) return { ok: false, reason: "invalid" };
183
+ const domain = emailDomain(email);
184
+ if (!domain) return { ok: false, reason: "invalid" };
185
+ if (policy.blocklist.includes(domain)) {
186
+ return { ok: false, reason: "disposable", domain };
187
+ }
188
+ if (policy.mode === "off") return { ok: true, domain };
189
+ if (policy.mode === "blocklist") return { ok: true, domain };
190
+ if (!policy.allowlist.includes(domain)) {
191
+ return { ok: false, reason: "not_allowed", domain };
192
+ }
193
+ return { ok: true, domain };
194
+ }
195
+ function registerEmailRejectionMessage(decision) {
196
+ if (decision.ok) return "";
197
+ if (decision.reason === "invalid") return "Enter a valid email address";
198
+ if (decision.reason === "disposable") {
199
+ return "Temporary or disposable email addresses are not allowed";
200
+ }
201
+ return "This email provider is not accepted for self-registration. Use Gmail, Outlook, QQ, 163, iCloud, or another allowed provider \u2014 or sign in with Google / GitHub.";
202
+ }
203
+ function formatAllowedEmailDomains(policy, options) {
204
+ if (policy.mode !== "allowlist") return null;
205
+ const list = [...policy.allowlist].map((d) => d.toLowerCase()).filter(Boolean);
206
+ if (!list.length) return null;
207
+ const max = options?.max ?? 16;
208
+ const shown = list.slice(0, max);
209
+ const rest = list.length - shown.length;
210
+ const body = shown.join(", ");
211
+ return rest > 0 ? `${body}, +${rest} more` : body;
212
+ }
213
+ function registerEmailDomainsHint(policy, labels) {
214
+ const prefix = labels?.allowedDomainsPrefix ?? "Allowed email domains";
215
+ const formatted = formatAllowedEmailDomains(policy);
216
+ if (formatted) return `${prefix}: ${formatted}`;
217
+ if (policy.mode === "blocklist") {
218
+ return labels?.allowedDomainsBlocklist ?? "Most email providers are accepted. Temporary / disposable addresses are blocked.";
219
+ }
220
+ if (policy.mode === "off" && policy.blocklist.length > 0) {
221
+ return labels?.allowedDomainsBlocklist ?? "Most email providers are accepted. Temporary / disposable addresses are blocked.";
222
+ }
223
+ return labels?.allowedDomainsOpen ?? "Email registration is open for most providers.";
224
+ }
225
+ async function fetchRegisterEmailPolicy(apiBase, init) {
226
+ const base = apiBase.replace(/\/$/, "");
227
+ try {
228
+ const res = await fetch(`${base}/api/register-policy`, {
229
+ credentials: "omit",
230
+ headers: { Accept: "application/json" },
231
+ ...init
232
+ });
233
+ if (!res.ok) return resolveRegisterEmailPolicy();
234
+ const data = await res.json();
235
+ return resolveRegisterEmailPolicy({
236
+ mode: data.mode,
237
+ allowlist: data.allowlist,
238
+ blocklist: data.blocklist
239
+ });
240
+ } catch {
241
+ return resolveRegisterEmailPolicy();
242
+ }
243
+ }
244
+
89
245
  // src/logto-experience-adapter.ts
90
246
  var LOGTO_USERNAME_RE = /^[A-Za-z_]\w*$/;
91
247
  function isLogtoRegisterUsername(value) {
@@ -173,9 +329,11 @@ var LogtoExperienceAdapter = class {
173
329
  capabilities = [
174
330
  LOGIN_EXPERIENCE_CAPABILITIES.passwordSignIn,
175
331
  LOGIN_EXPERIENCE_CAPABILITIES.passwordSignUp,
332
+ LOGIN_EXPERIENCE_CAPABILITIES.emailCodeSignUp,
176
333
  LOGIN_EXPERIENCE_CAPABILITIES.socialConnectors,
177
334
  LOGIN_EXPERIENCE_CAPABILITIES.socialDirectSignIn
178
335
  ];
336
+ emailPolicy = resolveRegisterEmailPolicy();
179
337
  async experiencePasswordSignIn(input) {
180
338
  const { apiBase, identifier, password } = input;
181
339
  await bootstrapOidcInteraction(input);
@@ -240,7 +398,10 @@ var LogtoExperienceAdapter = class {
240
398
  await bootstrapOidcInteraction(input);
241
399
  await experienceFetch(apiBase, "", {
242
400
  method: "PUT",
243
- body: JSON.stringify({ interactionEvent: "Register" })
401
+ body: JSON.stringify({
402
+ interactionEvent: "Register",
403
+ ...input.captchaToken ? { captchaToken: input.captchaToken } : {}
404
+ })
244
405
  });
245
406
  const verified = await experienceFetch(
246
407
  apiBase,
@@ -267,6 +428,96 @@ var LogtoExperienceAdapter = class {
267
428
  });
268
429
  return { redirectTo: submitted?.redirectTo, raw: submitted };
269
430
  }
431
+ async sendRegisterEmailCode(input) {
432
+ const email = (input.email || input.identifier || "").trim().toLowerCase();
433
+ const decision = evaluateRegisterEmail(email, this.emailPolicy);
434
+ if (!decision.ok) {
435
+ throw new Error(registerEmailRejectionMessage(decision));
436
+ }
437
+ await bootstrapOidcInteraction({
438
+ apiBase: input.apiBase,
439
+ issuer: input.issuer,
440
+ clientId: input.clientId,
441
+ redirectUri: input.redirectUri,
442
+ audience: input.audience,
443
+ scopes: input.scopes,
444
+ returnUrl: input.returnUrl
445
+ });
446
+ await experienceFetch(input.apiBase, "", {
447
+ method: "PUT",
448
+ body: JSON.stringify({
449
+ interactionEvent: "Register",
450
+ ...input.captchaToken ? { captchaToken: input.captchaToken } : {}
451
+ })
452
+ });
453
+ const sent = await experienceFetch(
454
+ input.apiBase,
455
+ "/verification/verification-code",
456
+ {
457
+ method: "POST",
458
+ body: JSON.stringify({
459
+ identifier: { type: "email", value: email },
460
+ interactionEvent: "Register"
461
+ })
462
+ }
463
+ );
464
+ if (!sent?.verificationId) {
465
+ throw new Error("Experience API did not return verificationId for email code");
466
+ }
467
+ return { verificationId: sent.verificationId };
468
+ }
469
+ async experienceEmailPasswordSignUp(input) {
470
+ const email = (input.email || input.identifier || "").trim().toLowerCase();
471
+ const decision = evaluateRegisterEmail(email, this.emailPolicy);
472
+ if (!decision.ok) {
473
+ throw new Error(registerEmailRejectionMessage(decision));
474
+ }
475
+ if (!input.password || input.password.length < 8) {
476
+ throw new Error("Password must be at least 8 characters");
477
+ }
478
+ if (!input.code?.trim() || !input.verificationId?.trim()) {
479
+ throw new Error("Email verification code is required");
480
+ }
481
+ const verified = await experienceFetch(
482
+ input.apiBase,
483
+ "/verification/verification-code/verify",
484
+ {
485
+ method: "POST",
486
+ body: JSON.stringify({
487
+ identifier: { type: "email", value: email },
488
+ verificationId: input.verificationId,
489
+ code: input.code.trim()
490
+ })
491
+ }
492
+ );
493
+ const codeVerificationId = verified?.verificationId || input.verificationId;
494
+ try {
495
+ await experienceFetch(input.apiBase, "/identification", {
496
+ method: "POST",
497
+ body: JSON.stringify({ verificationId: codeVerificationId })
498
+ });
499
+ } catch (error) {
500
+ const msg = error instanceof Error ? error.message : String(error);
501
+ if (!/missing_profile/i.test(msg)) {
502
+ if (!/422|password|profile/i.test(msg)) {
503
+ throw error instanceof Error ? error : new Error(msg);
504
+ }
505
+ }
506
+ }
507
+ await experienceFetch(input.apiBase, "/profile", {
508
+ method: "POST",
509
+ body: JSON.stringify({ type: "password", value: input.password })
510
+ });
511
+ await experienceFetch(input.apiBase, "/identification", {
512
+ method: "POST",
513
+ body: JSON.stringify({})
514
+ });
515
+ const submitted = await experienceFetch(input.apiBase, "/submit", {
516
+ method: "POST",
517
+ body: JSON.stringify({})
518
+ });
519
+ return { redirectTo: submitted?.redirectTo, raw: submitted };
520
+ }
270
521
  async fetchSocialConnectors(input) {
271
522
  const url = new URL(`${input.apiBase.replace(/\/$/, "")}/api/.well-known/sign-in-exp`);
272
523
  if (input.appId?.trim()) url.searchParams.set("appId", input.appId.trim());
@@ -678,6 +929,12 @@ var HeadlessLoginPanel_module_default = {
678
929
  title: "HeadlessLoginPanel_module_title",
679
930
  subtitle: "HeadlessLoginPanel_module_subtitle",
680
931
  form: "HeadlessLoginPanel_module_form",
932
+ channelTabs: "HeadlessLoginPanel_module_channelTabs",
933
+ channelTab: "HeadlessLoginPanel_module_channelTab",
934
+ channelTabActive: "HeadlessLoginPanel_module_channelTabActive",
935
+ codeRow: "HeadlessLoginPanel_module_codeRow",
936
+ codeInput: "HeadlessLoginPanel_module_codeInput",
937
+ secondaryBtn: "HeadlessLoginPanel_module_secondaryBtn",
681
938
  socialStack: "HeadlessLoginPanel_module_socialStack",
682
939
  input: "HeadlessLoginPanel_module_input",
683
940
  passwordField: "HeadlessLoginPanel_module_passwordField",
@@ -692,6 +949,9 @@ var HeadlessLoginPanel_module_default = {
692
949
  dividerLine: "HeadlessLoginPanel_module_dividerLine",
693
950
  dividerText: "HeadlessLoginPanel_module_dividerText",
694
951
  hint: "HeadlessLoginPanel_module_hint",
952
+ domainsHint: "HeadlessLoginPanel_module_domainsHint",
953
+ switchMode: "HeadlessLoginPanel_module_switchMode",
954
+ switchModeBtn: "HeadlessLoginPanel_module_switchModeBtn",
695
955
  error: "HeadlessLoginPanel_module_error",
696
956
  footer: "HeadlessLoginPanel_module_footer"
697
957
  };
@@ -710,14 +970,26 @@ var defaults = {
710
970
  submitGithub: "GitHub",
711
971
  socialDivider: "or",
712
972
  hint: "Social providers open directly. Password uses your LuminaryWorks account.",
713
- registerHint: "Create a username (start with a letter or _). Prefer email? Use a social provider below.",
973
+ registerHint: "Email register needs a code (Gmail / Outlook / QQ / 163 / iCloud \u2026). Username is an alternative. Prefer one-click? Use a social provider.",
714
974
  cancel: "Cancel",
715
975
  experienceUnavailable: "Password sign-in is unavailable; use a social provider instead.",
716
976
  showPassword: "Show password",
717
977
  hidePassword: "Hide password",
718
978
  registerLink: "Create an account",
719
979
  loginLink: "Already have an account? Sign in",
720
- passwordMismatch: "Passwords do not match"
980
+ passwordMismatch: "Passwords do not match",
981
+ registerTitle: "Create account",
982
+ registerSubtitle: "Create your LuminaryWorks unified account",
983
+ registerWithEmail: "Email",
984
+ registerWithUsername: "Username",
985
+ emailPlaceholder: "Email",
986
+ verificationCodePlaceholder: "Verification code",
987
+ sendCode: "Send code",
988
+ resendCode: "Resend code",
989
+ allowedDomainsPrefix: "Allowed email domains",
990
+ allowedDomainsOpen: "Email registration is open for most providers.",
991
+ allowedDomainsBlocklist: "Most email providers are accepted. Temporary / disposable addresses are blocked.",
992
+ waitingExternalBrowser: "Finish signing in with your browser, then return to this app."
721
993
  };
722
994
  function resolveSieBase(config) {
723
995
  const explicit = config.experienceApiBase?.trim();
@@ -746,8 +1018,12 @@ function HeadlessLoginPanel({
746
1018
  labels: labelsProp,
747
1019
  returnUrl,
748
1020
  mode = "popup",
1021
+ openExternalUrl,
749
1022
  showSocialConnectors = true,
750
1023
  showRegister = true,
1024
+ registerEmailPolicy: registerEmailPolicyProp,
1025
+ fetchRegisterPolicy = true,
1026
+ getCaptchaToken,
751
1027
  socialProviders = "auto",
752
1028
  showCancel,
753
1029
  onCancel,
@@ -762,12 +1038,50 @@ function HeadlessLoginPanel({
762
1038
  experienceAdapterProp,
763
1039
  config.iamProvider
764
1040
  );
1041
+ const [emailPolicy, setEmailPolicy] = react.useState(
1042
+ () => registerEmailPolicyProp ?? resolveRegisterEmailPolicy()
1043
+ );
1044
+ react.useEffect(() => {
1045
+ if (registerEmailPolicyProp) {
1046
+ setEmailPolicy(registerEmailPolicyProp);
1047
+ return;
1048
+ }
1049
+ if (!fetchRegisterPolicy || !showRegister) {
1050
+ setEmailPolicy(resolveRegisterEmailPolicy());
1051
+ return;
1052
+ }
1053
+ const base = config.experienceApiBase?.trim();
1054
+ if (!base) {
1055
+ setEmailPolicy(resolveRegisterEmailPolicy());
1056
+ return;
1057
+ }
1058
+ let cancelled = false;
1059
+ void fetchRegisterEmailPolicy(base).then((policy) => {
1060
+ if (!cancelled) setEmailPolicy(policy);
1061
+ });
1062
+ return () => {
1063
+ cancelled = true;
1064
+ };
1065
+ }, [
1066
+ config.experienceApiBase,
1067
+ fetchRegisterPolicy,
1068
+ registerEmailPolicyProp,
1069
+ showRegister
1070
+ ]);
1071
+ react.useEffect(() => {
1072
+ if (experienceAdapter instanceof LogtoExperienceAdapter) {
1073
+ experienceAdapter.emailPolicy = emailPolicy;
1074
+ }
1075
+ }, [emailPolicy, experienceAdapter]);
765
1076
  const labels = { ...defaults, ...labelsProp };
766
1077
  const configured = isIdpConfigured(config);
767
1078
  const [panelMode, setPanelMode] = react.useState("sign-in");
1079
+ const [registerChannel, setRegisterChannel] = react.useState("email");
768
1080
  const [identifier, setIdentifier] = react.useState("");
769
1081
  const [password, setPassword] = react.useState("");
770
1082
  const [confirmPassword, setConfirmPassword] = react.useState("");
1083
+ const [verificationCode, setVerificationCode] = react.useState("");
1084
+ const [emailVerificationId, setEmailVerificationId] = react.useState(null);
771
1085
  const [passwordVisible, setPasswordVisible] = react.useState(false);
772
1086
  const panelStyle = {
773
1087
  ...style,
@@ -775,12 +1089,20 @@ function HeadlessLoginPanel({
775
1089
  };
776
1090
  const [loading, setLoading] = react.useState(null);
777
1091
  const [error, setError] = react.useState("");
1092
+ const [awaitingExternal, setAwaitingExternal] = react.useState(false);
778
1093
  const [connectors, setConnectors] = react.useState([]);
779
1094
  const socialEnabled = showSocialConnectors !== false && !(Array.isArray(socialProviders) && socialProviders.length === 0) && experienceAdapter.capabilities.includes(LOGIN_EXPERIENCE_CAPABILITIES.socialConnectors) && experienceAdapter.capabilities.includes(LOGIN_EXPERIENCE_CAPABILITIES.socialDirectSignIn) && typeof experienceAdapter.fetchSocialConnectors === "function" && typeof experienceAdapter.createSocialSignInRequest === "function";
780
1095
  const passwordEnabled = experienceAdapter.capabilities.includes(LOGIN_EXPERIENCE_CAPABILITIES.passwordSignIn) && typeof experienceAdapter.experiencePasswordSignIn === "function";
781
- showRegister !== false && experienceAdapter.capabilities.includes(LOGIN_EXPERIENCE_CAPABILITIES.passwordSignUp) && typeof experienceAdapter.experiencePasswordSignUp === "function";
1096
+ const registerEnabled = showRegister !== false && (experienceAdapter.capabilities.includes(LOGIN_EXPERIENCE_CAPABILITIES.passwordSignUp) && typeof experienceAdapter.experiencePasswordSignUp === "function" || experienceAdapter.capabilities.includes(LOGIN_EXPERIENCE_CAPABILITIES.emailCodeSignUp) && typeof experienceAdapter.sendRegisterEmailCode === "function" && typeof experienceAdapter.experienceEmailPasswordSignUp === "function");
1097
+ const emailRegisterEnabled = experienceAdapter.capabilities.includes(LOGIN_EXPERIENCE_CAPABILITIES.emailCodeSignUp) && typeof experienceAdapter.sendRegisterEmailCode === "function" && typeof experienceAdapter.experienceEmailPasswordSignUp === "function";
1098
+ const usernameRegisterEnabled = experienceAdapter.capabilities.includes(LOGIN_EXPERIENCE_CAPABILITIES.passwordSignUp) && typeof experienceAdapter.experiencePasswordSignUp === "function";
1099
+ const isRegister = panelMode === "register" && registerEnabled;
1100
+ const useEmailRegister = isRegister && registerChannel === "email" && emailRegisterEnabled;
782
1101
  react.useEffect(() => {
783
- const resetBusy = () => setLoading(null);
1102
+ const resetBusy = () => {
1103
+ setLoading(null);
1104
+ setAwaitingExternal(false);
1105
+ };
784
1106
  window.addEventListener("pageshow", resetBusy);
785
1107
  window.addEventListener("popstate", resetBusy);
786
1108
  return () => {
@@ -823,6 +1145,19 @@ function HeadlessLoginPanel({
823
1145
  ]);
824
1146
  const runOidc = async (experienceRequest) => {
825
1147
  if (!configured) throw new Error("IdP not configured");
1148
+ if (mode === "external") {
1149
+ if (!openExternalUrl) {
1150
+ throw new Error('openExternalUrl is required when mode is "external"');
1151
+ }
1152
+ const url = await prepareSignInRequestUrl(config, {
1153
+ returnUrl,
1154
+ ...experienceRequest
1155
+ });
1156
+ setAwaitingExternal(true);
1157
+ await openExternalUrl(url);
1158
+ setLoading(null);
1159
+ return;
1160
+ }
826
1161
  if (mode === "redirect") {
827
1162
  await signInRedirect(config, { returnUrl, ...experienceRequest });
828
1163
  return;
@@ -846,6 +1181,7 @@ function HeadlessLoginPanel({
846
1181
  } catch (e) {
847
1182
  setError(e instanceof Error ? e.message : String(e));
848
1183
  setLoading(null);
1184
+ setAwaitingExternal(false);
849
1185
  }
850
1186
  };
851
1187
  const runHostedOidc = async () => {
@@ -856,6 +1192,7 @@ function HeadlessLoginPanel({
856
1192
  } catch (e) {
857
1193
  setError(e instanceof Error ? e.message : String(e));
858
1194
  setLoading(null);
1195
+ setAwaitingExternal(false);
859
1196
  }
860
1197
  };
861
1198
  const followExperienceRedirect = (redirectTo) => {
@@ -875,6 +1212,59 @@ function HeadlessLoginPanel({
875
1212
  if (!apiBase) {
876
1213
  throw new Error(labels.experienceUnavailable);
877
1214
  }
1215
+ const captchaToken = getCaptchaToken ? await getCaptchaToken() : void 0;
1216
+ if (isRegister) {
1217
+ if (password !== confirmPassword) {
1218
+ throw new Error(labels.passwordMismatch);
1219
+ }
1220
+ if (useEmailRegister) {
1221
+ if (!experienceAdapter.experienceEmailPasswordSignUp || !emailVerificationId) {
1222
+ throw new Error("Send and enter the email verification code first");
1223
+ }
1224
+ const decision = evaluateRegisterEmail(identifier.trim(), emailPolicy);
1225
+ if (!decision.ok) throw new Error(registerEmailRejectionMessage(decision));
1226
+ const result3 = await experienceAdapter.experienceEmailPasswordSignUp({
1227
+ apiBase,
1228
+ identifier: identifier.trim(),
1229
+ email: identifier.trim(),
1230
+ password,
1231
+ code: verificationCode,
1232
+ verificationId: emailVerificationId,
1233
+ captchaToken,
1234
+ issuer: config.issuer,
1235
+ clientId: config.clientId,
1236
+ redirectUri: config.redirectUri,
1237
+ audience: config.audience,
1238
+ scopes: config.scopes,
1239
+ returnUrl
1240
+ });
1241
+ if (result3.redirectTo) {
1242
+ followExperienceRedirect(result3.redirectTo);
1243
+ return;
1244
+ }
1245
+ throw new Error("Experience API did not return redirectTo");
1246
+ }
1247
+ if (!usernameRegisterEnabled || !experienceAdapter.experiencePasswordSignUp) {
1248
+ throw new Error(labels.experienceUnavailable);
1249
+ }
1250
+ const result2 = await experienceAdapter.experiencePasswordSignUp({
1251
+ apiBase,
1252
+ identifier: identifier.trim(),
1253
+ password,
1254
+ captchaToken,
1255
+ issuer: config.issuer,
1256
+ clientId: config.clientId,
1257
+ redirectUri: config.redirectUri,
1258
+ audience: config.audience,
1259
+ scopes: config.scopes,
1260
+ returnUrl
1261
+ });
1262
+ if (result2.redirectTo) {
1263
+ followExperienceRedirect(result2.redirectTo);
1264
+ return;
1265
+ }
1266
+ throw new Error("Experience API did not return redirectTo");
1267
+ }
878
1268
  if (!passwordEnabled || !experienceAdapter.capabilities.includes(
879
1269
  LOGIN_EXPERIENCE_CAPABILITIES.passwordSignIn
880
1270
  ) || !experienceAdapter.experiencePasswordSignIn) {
@@ -901,40 +1291,164 @@ function HeadlessLoginPanel({
901
1291
  setLoading(null);
902
1292
  }
903
1293
  };
1294
+ const runSendCode = async () => {
1295
+ if (!configured || !useEmailRegister) return;
1296
+ setError("");
1297
+ setLoading("send-code");
1298
+ try {
1299
+ const apiBase = config.experienceApiBase?.trim();
1300
+ if (!apiBase || !experienceAdapter.sendRegisterEmailCode) {
1301
+ throw new Error(labels.experienceUnavailable);
1302
+ }
1303
+ const email = identifier.trim();
1304
+ const decision = evaluateRegisterEmail(email, emailPolicy);
1305
+ if (!decision.ok) throw new Error(registerEmailRejectionMessage(decision));
1306
+ const captchaToken = getCaptchaToken ? await getCaptchaToken() : void 0;
1307
+ const sent = await experienceAdapter.sendRegisterEmailCode({
1308
+ apiBase,
1309
+ identifier: email,
1310
+ email,
1311
+ captchaToken,
1312
+ issuer: config.issuer,
1313
+ clientId: config.clientId,
1314
+ redirectUri: config.redirectUri,
1315
+ audience: config.audience,
1316
+ scopes: config.scopes,
1317
+ returnUrl
1318
+ });
1319
+ setEmailVerificationId(sent.verificationId);
1320
+ } catch (e) {
1321
+ setError(e instanceof Error ? e.message : String(e));
1322
+ } finally {
1323
+ setLoading(null);
1324
+ }
1325
+ };
1326
+ const switchPanelMode = (next) => {
1327
+ setPanelMode(next);
1328
+ setError("");
1329
+ setConfirmPassword("");
1330
+ setVerificationCode("");
1331
+ setEmailVerificationId(null);
1332
+ };
904
1333
  const busy = loading !== null;
905
1334
  const showSocial = socialEnabled && connectors.length > 0;
906
- const showHint = Boolean(labelsProp?.hint) || showSocial;
1335
+ const showHint = Boolean(labelsProp?.hint) || showSocial || isRegister;
1336
+ const formReady = Boolean(identifier.trim()) && Boolean(password) && (!isRegister || Boolean(confirmPassword)) && (!useEmailRegister || Boolean(verificationCode.trim()) && Boolean(emailVerificationId));
1337
+ const titleText = isRegister ? labels.registerTitle : labels.title;
1338
+ const subtitleText = isRegister ? labels.registerSubtitle : labels.subtitle;
1339
+ const identifierPlaceholder = isRegister ? useEmailRegister ? labels.emailPlaceholder : labels.registerIdentifierPlaceholder : labels.identifierPlaceholder;
1340
+ const submitLabel = isRegister ? labels.submitRegister : labels.submitPassword;
1341
+ const hintText = isRegister ? labels.registerHint : labels.hint;
1342
+ const domainsHint = useEmailRegister ? registerEmailDomainsHint(emailPolicy, {
1343
+ allowedDomainsPrefix: labels.allowedDomainsPrefix,
1344
+ allowedDomainsOpen: labels.allowedDomainsOpen,
1345
+ allowedDomainsBlocklist: labels.allowedDomainsBlocklist
1346
+ }) : null;
907
1347
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx(HeadlessLoginPanel_module_default.panel, className), style: panelStyle, children: [
908
1348
  /* @__PURE__ */ jsxRuntime.jsxs("header", { className: HeadlessLoginPanel_module_default.brand, children: [
909
1349
  logoSrc ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: logoSrc, alt: "", width: 48, height: 48, className: HeadlessLoginPanel_module_default.logo }) : null,
910
1350
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
911
1351
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: HeadlessLoginPanel_module_default.product, children: productName }),
912
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: HeadlessLoginPanel_module_default.title, children: labels.title })
1352
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: HeadlessLoginPanel_module_default.title, children: titleText })
913
1353
  ] })
914
1354
  ] }),
915
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: HeadlessLoginPanel_module_default.subtitle, children: labels.subtitle }),
1355
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: HeadlessLoginPanel_module_default.subtitle, children: subtitleText }),
916
1356
  configured ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
917
1357
  passwordEnabled ? /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: (e) => void runPassword(e), className: HeadlessLoginPanel_module_default.form, children: [
1358
+ isRegister && emailRegisterEnabled && usernameRegisterEnabled ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: HeadlessLoginPanel_module_default.channelTabs, role: "tablist", "aria-label": "Register method", children: [
1359
+ /* @__PURE__ */ jsxRuntime.jsx(
1360
+ "button",
1361
+ {
1362
+ type: "button",
1363
+ role: "tab",
1364
+ className: cx(
1365
+ HeadlessLoginPanel_module_default.channelTab,
1366
+ registerChannel === "email" && HeadlessLoginPanel_module_default.channelTabActive
1367
+ ),
1368
+ "aria-selected": registerChannel === "email",
1369
+ disabled: busy,
1370
+ onClick: () => {
1371
+ setRegisterChannel("email");
1372
+ setEmailVerificationId(null);
1373
+ setVerificationCode("");
1374
+ setError("");
1375
+ },
1376
+ children: labels.registerWithEmail
1377
+ }
1378
+ ),
1379
+ /* @__PURE__ */ jsxRuntime.jsx(
1380
+ "button",
1381
+ {
1382
+ type: "button",
1383
+ role: "tab",
1384
+ className: cx(
1385
+ HeadlessLoginPanel_module_default.channelTab,
1386
+ registerChannel === "username" && HeadlessLoginPanel_module_default.channelTabActive
1387
+ ),
1388
+ "aria-selected": registerChannel === "username",
1389
+ disabled: busy,
1390
+ onClick: () => {
1391
+ setRegisterChannel("username");
1392
+ setEmailVerificationId(null);
1393
+ setVerificationCode("");
1394
+ setError("");
1395
+ },
1396
+ children: labels.registerWithUsername
1397
+ }
1398
+ )
1399
+ ] }) : null,
918
1400
  /* @__PURE__ */ jsxRuntime.jsx(
919
1401
  "input",
920
1402
  {
921
- type: "text",
1403
+ type: useEmailRegister ? "email" : "text",
922
1404
  name: "identifier",
923
- autoComplete: "username",
924
- placeholder: labels.identifierPlaceholder,
1405
+ autoComplete: isRegister ? "username" : "username",
1406
+ placeholder: identifierPlaceholder,
925
1407
  value: identifier,
926
- onChange: (e) => setIdentifier(e.target.value),
1408
+ onChange: (e) => {
1409
+ setIdentifier(e.target.value);
1410
+ if (useEmailRegister) {
1411
+ setEmailVerificationId(null);
1412
+ }
1413
+ },
927
1414
  className: HeadlessLoginPanel_module_default.input,
928
1415
  disabled: busy
929
1416
  }
930
1417
  ),
1418
+ domainsHint ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: HeadlessLoginPanel_module_default.domainsHint, "data-testid": "register-allowed-domains", children: domainsHint }) : null,
1419
+ useEmailRegister ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: HeadlessLoginPanel_module_default.codeRow, children: [
1420
+ /* @__PURE__ */ jsxRuntime.jsx(
1421
+ "input",
1422
+ {
1423
+ type: "text",
1424
+ name: "verificationCode",
1425
+ inputMode: "numeric",
1426
+ autoComplete: "one-time-code",
1427
+ placeholder: labels.verificationCodePlaceholder,
1428
+ value: verificationCode,
1429
+ onChange: (e) => setVerificationCode(e.target.value),
1430
+ className: cx(HeadlessLoginPanel_module_default.input, HeadlessLoginPanel_module_default.codeInput),
1431
+ disabled: busy
1432
+ }
1433
+ ),
1434
+ /* @__PURE__ */ jsxRuntime.jsx(
1435
+ "button",
1436
+ {
1437
+ type: "button",
1438
+ className: HeadlessLoginPanel_module_default.secondaryBtn,
1439
+ disabled: busy || !identifier.trim(),
1440
+ onClick: () => void runSendCode(),
1441
+ children: loading === "send-code" ? "\u2026" : emailVerificationId ? labels.resendCode : labels.sendCode
1442
+ }
1443
+ )
1444
+ ] }) : null,
931
1445
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: HeadlessLoginPanel_module_default.passwordField, children: [
932
1446
  /* @__PURE__ */ jsxRuntime.jsx(
933
1447
  "input",
934
1448
  {
935
1449
  type: passwordVisible ? "text" : "password",
936
1450
  name: "password",
937
- autoComplete: "current-password",
1451
+ autoComplete: isRegister ? "new-password" : "current-password",
938
1452
  placeholder: labels.passwordPlaceholder,
939
1453
  value: password,
940
1454
  onChange: (e) => setPassword(e.target.value),
@@ -955,7 +1469,20 @@ function HeadlessLoginPanel({
955
1469
  }
956
1470
  )
957
1471
  ] }),
958
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", className: HeadlessLoginPanel_module_default.primaryBtn, disabled: busy || !identifier || !password, children: loading === "password" ? "\u2026" : labels.submitPassword })
1472
+ isRegister ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: HeadlessLoginPanel_module_default.passwordField, children: /* @__PURE__ */ jsxRuntime.jsx(
1473
+ "input",
1474
+ {
1475
+ type: passwordVisible ? "text" : "password",
1476
+ name: "confirmPassword",
1477
+ autoComplete: "new-password",
1478
+ placeholder: labels.confirmPasswordPlaceholder,
1479
+ value: confirmPassword,
1480
+ onChange: (e) => setConfirmPassword(e.target.value),
1481
+ className: cx(HeadlessLoginPanel_module_default.input, HeadlessLoginPanel_module_default.passwordInput),
1482
+ disabled: busy
1483
+ }
1484
+ ) }) : null,
1485
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", className: HeadlessLoginPanel_module_default.primaryBtn, disabled: busy || !formReady, children: loading === "password" ? "\u2026" : submitLabel })
959
1486
  ] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: HeadlessLoginPanel_module_default.form, children: /* @__PURE__ */ jsxRuntime.jsx(
960
1487
  "button",
961
1488
  {
@@ -966,7 +1493,18 @@ function HeadlessLoginPanel({
966
1493
  children: loading === "sso" ? "\u2026" : labels.submitSso
967
1494
  }
968
1495
  ) }),
969
- showHint ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: HeadlessLoginPanel_module_default.hint, children: labels.hint }) : null,
1496
+ showHint ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: HeadlessLoginPanel_module_default.hint, children: hintText }) : null,
1497
+ registerEnabled && passwordEnabled ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: HeadlessLoginPanel_module_default.switchMode, children: /* @__PURE__ */ jsxRuntime.jsx(
1498
+ "button",
1499
+ {
1500
+ type: "button",
1501
+ className: HeadlessLoginPanel_module_default.switchModeBtn,
1502
+ disabled: busy,
1503
+ onClick: () => switchPanelMode(isRegister ? "sign-in" : "register"),
1504
+ children: isRegister ? labels.loginLink : labels.registerLink
1505
+ }
1506
+ ) }) : null,
1507
+ awaitingExternal ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: HeadlessLoginPanel_module_default.hint, role: "status", children: labels.waitingExternalBrowser }) : null,
970
1508
  showSocial ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: HeadlessLoginPanel_module_default.divider, "aria-hidden": true, children: [
971
1509
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: HeadlessLoginPanel_module_default.dividerLine }),
972
1510
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: HeadlessLoginPanel_module_default.dividerText, children: labels.socialDivider }),
@@ -1060,6 +1598,7 @@ function ReauthOverlay({
1060
1598
  },
1061
1599
  returnUrl: resolvedReturnUrl,
1062
1600
  mode: "popup",
1601
+ showRegister: false,
1063
1602
  showCancel: dismissible,
1064
1603
  onCancel: handleCancel,
1065
1604
  onOidcSession: handleSession
@@ -1190,7 +1729,9 @@ function createPostLoginPathHelpers(options) {
1190
1729
  }
1191
1730
 
1192
1731
  exports.AuthGateProvider = AuthGateProvider;
1732
+ exports.DEFAULT_DISPOSABLE_EMAIL_BLOCKLIST = DEFAULT_DISPOSABLE_EMAIL_BLOCKLIST;
1193
1733
  exports.DEFAULT_LOGIN_THEME_COLOR = DEFAULT_LOGIN_THEME_COLOR;
1734
+ exports.DEFAULT_REGISTER_EMAIL_ALLOWLIST = DEFAULT_REGISTER_EMAIL_ALLOWLIST;
1194
1735
  exports.HeadlessLoginPanel = HeadlessLoginPanel;
1195
1736
  exports.HostedOidcExperienceAdapter = HostedOidcExperienceAdapter;
1196
1737
  exports.LOGIN_EXPERIENCE_CAPABILITIES = LOGIN_EXPERIENCE_CAPABILITIES;
@@ -1203,8 +1744,12 @@ exports.createLoginExperienceAdapter = createLoginExperienceAdapter;
1203
1744
  exports.createLogtoDirectSignInRequest = createLogtoDirectSignInRequest;
1204
1745
  exports.createPostLoginPathHelpers = createPostLoginPathHelpers;
1205
1746
  exports.createUserManager = createUserManager;
1747
+ exports.emailDomain = emailDomain;
1748
+ exports.evaluateRegisterEmail = evaluateRegisterEmail;
1206
1749
  exports.experiencePasswordSignIn = experiencePasswordSignIn;
1750
+ exports.fetchRegisterEmailPolicy = fetchRegisterEmailPolicy;
1207
1751
  exports.fetchSocialConnectors = fetchSocialConnectors;
1752
+ exports.formatAllowedEmailDomains = formatAllowedEmailDomains;
1208
1753
  exports.getCurrentUser = getCurrentUser;
1209
1754
  exports.getStoredAccessToken = getStoredAccessToken;
1210
1755
  exports.handleSignInCallback = handleSignInCallback;
@@ -1212,13 +1757,19 @@ exports.handleSignInPopupCallback = handleSignInPopupCallback;
1212
1757
  exports.isIdpConfigured = isIdpConfigured;
1213
1758
  exports.isLogtoRegisterUsername = isLogtoRegisterUsername;
1214
1759
  exports.isOidcPopupWindow = isOidcPopupWindow;
1760
+ exports.isValidEmailShape = isValidEmailShape;
1761
+ exports.normalizeRegisterEmailMode = normalizeRegisterEmailMode;
1215
1762
  exports.prepareSignInRequestUrl = prepareSignInRequestUrl;
1216
1763
  exports.readIdpConfigFromEnv = readIdpConfigFromEnv;
1764
+ exports.readRegisterEmailPolicyFromEnv = readRegisterEmailPolicyFromEnv;
1765
+ exports.registerEmailDomainsHint = registerEmailDomainsHint;
1766
+ exports.registerEmailRejectionMessage = registerEmailRejectionMessage;
1217
1767
  exports.resetUserManager = resetUserManager;
1218
1768
  exports.resolveExperienceApiBase = resolveExperienceApiBase;
1219
1769
  exports.resolveIssuer = resolveIssuer;
1220
1770
  exports.resolveLoginExperienceAdapter = resolveLoginExperienceAdapter;
1221
1771
  exports.resolveLoginExperienceProviderFromEnv = resolveLoginExperienceProviderFromEnv;
1772
+ exports.resolveRegisterEmailPolicy = resolveRegisterEmailPolicy;
1222
1773
  exports.sameOriginAuthorizeUrl = sameOriginAuthorizeUrl;
1223
1774
  exports.signInPopup = signInPopup;
1224
1775
  exports.signInRedirect = signInRedirect;