@luminaryworks/auth-react 0.4.5 → 0.6.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_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);})();
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: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\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 width: 100%;\n min-width: 0;\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\n/* src/LoginLanguageSwitcher.module.scss */\n.LoginLanguageSwitcher_module_switch {\n position: relative;\n display: inline-flex;\n}\n.LoginLanguageSwitcher_module_trigger {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 32px;\n padding: 0 10px 0 8px;\n border: 1px solid rgba(15, 23, 42, 0.12);\n border-radius: 999px;\n background: rgba(255, 255, 255, 0.86);\n color: #243044;\n font: inherit;\n font-size: 13px;\n line-height: 1;\n cursor: pointer;\n backdrop-filter: blur(8px);\n}\n.LoginLanguageSwitcher_module_trigger:hover,\n.LoginLanguageSwitcher_module_trigger[aria-expanded=true] {\n border-color: var(--lw-login-accent, #1677ff);\n color: #0f172a;\n}\n.LoginLanguageSwitcher_module_icon {\n width: 14px;\n height: 14px;\n display: block;\n}\n.LoginLanguageSwitcher_module_caret {\n width: 10px;\n height: 10px;\n opacity: 0.65;\n}\n.LoginLanguageSwitcher_module_menu {\n position: absolute;\n top: calc(100% + 6px);\n right: 0;\n z-index: 20;\n min-width: 168px;\n margin: 0;\n padding: 6px;\n list-style: none;\n border-radius: 12px;\n background: #fff;\n box-shadow: 0 12px 40px rgba(15, 23, 42, 0.16);\n}\n.LoginLanguageSwitcher_module_item {\n display: block;\n width: 100%;\n border: 0;\n border-radius: 8px;\n padding: 8px 10px;\n background: transparent;\n color: #1f2937;\n font: inherit;\n font-size: 14px;\n text-align: left;\n cursor: pointer;\n}\n.LoginLanguageSwitcher_module_item:hover {\n background: #f3f6fb;\n}\n.LoginLanguageSwitcher_module_itemActive {\n background: color-mix(in srgb, var(--lw-login-accent, #1677ff) 14%, #fff);\n color: var(--lw-login-accent, #1677ff);\n font-weight: 650;\n}\n.LoginLanguageSwitcher_module_onDark {\n border-color: rgba(255, 255, 255, 0.22);\n background: rgba(15, 23, 42, 0.28);\n color: rgba(255, 255, 255, 0.92);\n}\n.LoginLanguageSwitcher_module_onDark:hover,\n.LoginLanguageSwitcher_module_onDark[aria-expanded=true] {\n border-color: rgba(255, 255, 255, 0.55);\n color: #fff;\n}\n\n/* src/LoginCanvas.module.scss */\n.LoginCanvas_module_canvas {\n --lw-login-accent: #1677ff;\n position: relative;\n min-height: 100dvh;\n display: grid;\n place-items: center;\n padding: 56px 20px 24px;\n overflow: hidden;\n background:\n radial-gradient(\n 900px 480px at 8% -8%,\n color-mix(in srgb, var(--lw-login-accent) 28%, transparent),\n transparent 58%),\n radial-gradient(\n 720px 420px at 100% 12%,\n color-mix(in srgb, var(--lw-login-accent) 16%, transparent),\n transparent 52%),\n linear-gradient(\n 165deg,\n var(--lw-login-bg-a, #f4f7fb) 0%,\n var(--lw-login-bg-b, #e7eef6) 100%);\n color: var(--lw-login-ink, #122033);\n}\n.LoginCanvas_module_canvas[data-tone=console] {\n --lw-login-accent: #7eb6ff;\n --lw-login-bg-a: #0e1726;\n --lw-login-bg-b: #1a2740;\n --lw-login-ink: #e8eef8;\n --lw-login-grid: rgba(255, 255, 255, 0.05);\n}\n.LoginCanvas_module_canvas[data-tone=vista] {\n --lw-login-accent: #1677ff;\n --lw-login-bg-a: #f4f7fb;\n --lw-login-bg-b: #dde7f0;\n --lw-login-grid: rgba(11, 18, 32, 0.045);\n}\n.LoginCanvas_module_canvas[data-tone=data] {\n --lw-login-accent: #5aa2ff;\n --lw-login-bg-a: #070b14;\n --lw-login-bg-b: #12203a;\n --lw-login-ink: #e8eefc;\n --lw-login-grid: rgba(255, 255, 255, 0.045);\n}\n.LoginCanvas_module_canvas[data-tone=edu] {\n --lw-login-accent: #f59e0b;\n --lw-login-bg-a: #fff7ed;\n --lw-login-bg-b: #e0e7ff;\n --lw-login-grid: rgba(67, 56, 202, 0.06);\n}\n.LoginCanvas_module_grid {\n position: absolute;\n inset: 0;\n pointer-events: none;\n background-image:\n linear-gradient(var(--lw-login-grid, rgba(15, 23, 42, 0.04)) 1px, transparent 1px),\n linear-gradient(\n 90deg,\n var(--lw-login-grid, rgba(15, 23, 42, 0.04)) 1px,\n transparent 1px);\n background-size: 48px 48px;\n mask-image:\n radial-gradient(\n ellipse at center,\n #000 18%,\n transparent 74%);\n}\n.LoginCanvas_module_lang {\n position: fixed;\n top: 14px;\n right: 16px;\n z-index: 5;\n}\n.LoginCanvas_module_decor {\n position: absolute;\n inset: 0;\n pointer-events: none;\n z-index: 0;\n}\n.LoginCanvas_module_slot {\n position: relative;\n z-index: 1;\n width: min(440px, 100%);\n}\n.LoginCanvas_module_slotFill {\n width: min(1120px, 100%);\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());
@@ -669,6 +920,138 @@ function useLuminaryAuth() {
669
920
  return ctx;
670
921
  }
671
922
 
923
+ // src/login-labels.ts
924
+ var DEFAULT_LOGIN_LOCALES = [
925
+ { code: "en", label: "English", short: "EN" },
926
+ { code: "zh-CN", label: "\u7B80\u4F53\u4E2D\u6587", short: "\u7B80" },
927
+ { code: "zh-TW", label: "\u7E41\u9AD4\u4E2D\u6587", short: "\u7E41" },
928
+ { code: "es", label: "Espa\xF1ol", short: "ES" }
929
+ ];
930
+ var ZH_CN = {
931
+ title: "\u767B\u5F55",
932
+ subtitle: "\u4F7F\u7528 LuminaryWorks \u7EDF\u4E00\u8D26\u53F7",
933
+ identifierPlaceholder: "\u90AE\u7BB1\u6216\u7528\u6237\u540D",
934
+ registerIdentifierPlaceholder: "\u7528\u6237\u540D",
935
+ passwordPlaceholder: "\u5BC6\u7801",
936
+ confirmPasswordPlaceholder: "\u786E\u8BA4\u5BC6\u7801",
937
+ submitPassword: "\u5BC6\u7801\u767B\u5F55",
938
+ submitRegister: "\u521B\u5EFA\u8D26\u53F7",
939
+ submitSso: "\u4F7F\u7528\u7EDF\u4E00\u8D26\u53F7\u7EE7\u7EED",
940
+ submitGoogle: "Google",
941
+ submitGithub: "GitHub",
942
+ socialDivider: "\u6216",
943
+ hint: "\u793E\u4EA4\u8D26\u53F7\u4F1A\u76F4\u63A5\u6253\u5F00\u5BF9\u5E94\u670D\u52A1\u3002\u5BC6\u7801\u767B\u5F55\u4F7F\u7528\u4F60\u7684 LuminaryWorks \u8D26\u53F7\u3002",
944
+ registerHint: "\u90AE\u7BB1\u6CE8\u518C\u9700\u8981\u9A8C\u8BC1\u7801\u3002\u4E5F\u53EF\u4EE5\u7528\u7528\u6237\u540D\u6CE8\u518C\uFF0C\u6216\u6539\u7528\u793E\u4EA4\u8D26\u53F7\u4E00\u952E\u767B\u5F55\u3002",
945
+ cancel: "\u53D6\u6D88",
946
+ experienceUnavailable: "\u5BC6\u7801\u767B\u5F55\u6682\u4E0D\u53EF\u7528\uFF0C\u8BF7\u6539\u7528\u793E\u4EA4\u8D26\u53F7\u3002",
947
+ showPassword: "\u663E\u793A\u5BC6\u7801",
948
+ hidePassword: "\u9690\u85CF\u5BC6\u7801",
949
+ registerLink: "\u521B\u5EFA\u8D26\u53F7",
950
+ loginLink: "\u5DF2\u6709\u8D26\u53F7\uFF1F\u53BB\u767B\u5F55",
951
+ passwordMismatch: "\u4E24\u6B21\u5BC6\u7801\u4E0D\u4E00\u81F4",
952
+ registerTitle: "\u521B\u5EFA\u8D26\u53F7",
953
+ registerSubtitle: "\u521B\u5EFA LuminaryWorks \u7EDF\u4E00\u8D26\u53F7",
954
+ registerWithEmail: "\u90AE\u7BB1",
955
+ registerWithUsername: "\u7528\u6237\u540D",
956
+ emailPlaceholder: "\u90AE\u7BB1",
957
+ verificationCodePlaceholder: "\u9A8C\u8BC1\u7801",
958
+ sendCode: "\u53D1\u9001\u9A8C\u8BC1\u7801",
959
+ resendCode: "\u91CD\u65B0\u53D1\u9001",
960
+ allowedDomainsPrefix: "\u5141\u8BB8\u7684\u90AE\u7BB1\u57DF\u540D",
961
+ allowedDomainsOpen: "\u5927\u591A\u6570\u90AE\u7BB1\u90FD\u53EF\u4EE5\u6CE8\u518C\u3002",
962
+ allowedDomainsBlocklist: "\u5927\u591A\u6570\u90AE\u7BB1\u53EF\u7528\u3002\u4E34\u65F6\u90AE\u7BB1\u4F1A\u88AB\u62D2\u7EDD\u3002",
963
+ waitingExternalBrowser: "\u8BF7\u5728\u6D4F\u89C8\u5668\u4E2D\u5B8C\u6210\u767B\u5F55\uFF0C\u7136\u540E\u56DE\u5230\u672C\u5E94\u7528\u3002"
964
+ };
965
+ var ZH_TW = {
966
+ title: "\u767B\u5165",
967
+ subtitle: "\u4F7F\u7528 LuminaryWorks \u7D71\u4E00\u5E33\u865F",
968
+ identifierPlaceholder: "\u96FB\u5B50\u90F5\u4EF6\u6216\u4F7F\u7528\u8005\u540D\u7A31",
969
+ registerIdentifierPlaceholder: "\u4F7F\u7528\u8005\u540D\u7A31",
970
+ passwordPlaceholder: "\u5BC6\u78BC",
971
+ confirmPasswordPlaceholder: "\u78BA\u8A8D\u5BC6\u78BC",
972
+ submitPassword: "\u5BC6\u78BC\u767B\u5165",
973
+ submitRegister: "\u5EFA\u7ACB\u5E33\u865F",
974
+ submitSso: "\u4F7F\u7528\u7D71\u4E00\u5E33\u865F\u7E7C\u7E8C",
975
+ submitGoogle: "Google",
976
+ submitGithub: "GitHub",
977
+ socialDivider: "\u6216",
978
+ hint: "\u793E\u4EA4\u5E33\u865F\u6703\u76F4\u63A5\u958B\u555F\u5C0D\u61C9\u670D\u52D9\u3002\u5BC6\u78BC\u767B\u5165\u4F7F\u7528\u4F60\u7684 LuminaryWorks \u5E33\u865F\u3002",
979
+ registerHint: "\u96FB\u5B50\u90F5\u4EF6\u8A3B\u518A\u9700\u8981\u9A57\u8B49\u78BC\u3002\u4E5F\u53EF\u4EE5\u7528\u4F7F\u7528\u8005\u540D\u7A31\u8A3B\u518A\uFF0C\u6216\u6539\u7528\u793E\u4EA4\u5E33\u865F\u3002",
980
+ cancel: "\u53D6\u6D88",
981
+ experienceUnavailable: "\u5BC6\u78BC\u767B\u5165\u66AB\u4E0D\u53EF\u7528\uFF0C\u8ACB\u6539\u7528\u793E\u4EA4\u5E33\u865F\u3002",
982
+ showPassword: "\u986F\u793A\u5BC6\u78BC",
983
+ hidePassword: "\u96B1\u85CF\u5BC6\u78BC",
984
+ registerLink: "\u5EFA\u7ACB\u5E33\u865F",
985
+ loginLink: "\u5DF2\u6709\u5E33\u865F\uFF1F\u53BB\u767B\u5165",
986
+ passwordMismatch: "\u5169\u6B21\u5BC6\u78BC\u4E0D\u4E00\u81F4",
987
+ registerTitle: "\u5EFA\u7ACB\u5E33\u865F",
988
+ registerSubtitle: "\u5EFA\u7ACB LuminaryWorks \u7D71\u4E00\u5E33\u865F",
989
+ registerWithEmail: "\u96FB\u5B50\u90F5\u4EF6",
990
+ registerWithUsername: "\u4F7F\u7528\u8005\u540D\u7A31",
991
+ emailPlaceholder: "\u96FB\u5B50\u90F5\u4EF6",
992
+ verificationCodePlaceholder: "\u9A57\u8B49\u78BC",
993
+ sendCode: "\u767C\u9001\u9A57\u8B49\u78BC",
994
+ resendCode: "\u91CD\u65B0\u767C\u9001",
995
+ allowedDomainsPrefix: "\u5141\u8A31\u7684\u96FB\u5B50\u90F5\u4EF6\u7DB2\u57DF",
996
+ allowedDomainsOpen: "\u5927\u591A\u6578\u96FB\u5B50\u90F5\u4EF6\u90FD\u53EF\u4EE5\u8A3B\u518A\u3002",
997
+ allowedDomainsBlocklist: "\u5927\u591A\u6578\u96FB\u5B50\u90F5\u4EF6\u53EF\u7528\u3002\u81E8\u6642\u4FE1\u7BB1\u6703\u88AB\u62D2\u7D55\u3002",
998
+ waitingExternalBrowser: "\u8ACB\u5728\u700F\u89BD\u5668\u4E2D\u5B8C\u6210\u767B\u5165\uFF0C\u7136\u5F8C\u56DE\u5230\u672C\u61C9\u7528\u3002"
999
+ };
1000
+ var ES = {
1001
+ title: "Iniciar sesi\xF3n",
1002
+ subtitle: "Usa tu cuenta unificada de LuminaryWorks",
1003
+ identifierPlaceholder: "Correo o usuario",
1004
+ registerIdentifierPlaceholder: "Usuario",
1005
+ passwordPlaceholder: "Contrase\xF1a",
1006
+ confirmPasswordPlaceholder: "Confirmar contrase\xF1a",
1007
+ submitPassword: "Entrar con contrase\xF1a",
1008
+ submitRegister: "Crear cuenta",
1009
+ submitSso: "Continuar con la cuenta unificada",
1010
+ submitGoogle: "Google",
1011
+ submitGithub: "GitHub",
1012
+ socialDivider: "o",
1013
+ hint: "Las redes abren el proveedor directamente. La contrase\xF1a usa tu cuenta LuminaryWorks.",
1014
+ registerHint: "El registro por correo pide un c\xF3digo. Tambi\xE9n puedes usar un nombre de usuario o una red social.",
1015
+ cancel: "Cancelar",
1016
+ experienceUnavailable: "El inicio con contrase\xF1a no est\xE1 disponible. Usa una red social.",
1017
+ showPassword: "Mostrar contrase\xF1a",
1018
+ hidePassword: "Ocultar contrase\xF1a",
1019
+ registerLink: "Crear una cuenta",
1020
+ loginLink: "\xBFYa tienes cuenta? Inicia sesi\xF3n",
1021
+ passwordMismatch: "Las contrase\xF1as no coinciden",
1022
+ registerTitle: "Crear cuenta",
1023
+ registerSubtitle: "Crea tu cuenta unificada de LuminaryWorks",
1024
+ registerWithEmail: "Correo",
1025
+ registerWithUsername: "Usuario",
1026
+ emailPlaceholder: "Correo",
1027
+ verificationCodePlaceholder: "C\xF3digo",
1028
+ sendCode: "Enviar c\xF3digo",
1029
+ resendCode: "Reenviar",
1030
+ allowedDomainsPrefix: "Dominios de correo permitidos",
1031
+ allowedDomainsOpen: "El registro por correo est\xE1 abierto para la mayor\xEDa de proveedores.",
1032
+ allowedDomainsBlocklist: "Se aceptan la mayor\xEDa de correos. Se bloquean direcciones temporales.",
1033
+ waitingExternalBrowser: "Termina el inicio de sesi\xF3n en el navegador y vuelve a esta app."
1034
+ };
1035
+ var BY_LOCALE = {
1036
+ en: void 0,
1037
+ "zh-CN": ZH_CN,
1038
+ "zh-TW": ZH_TW,
1039
+ es: ES
1040
+ };
1041
+ function normalizeLoginLocale(locale) {
1042
+ const raw = (locale ?? "en").trim().toLowerCase();
1043
+ if (raw === "zh" || raw === "zh-cn" || raw === "zh-hans" || raw.startsWith("zh-cn")) {
1044
+ return "zh-CN";
1045
+ }
1046
+ if (raw === "zh-tw" || raw === "zh-hant" || raw.startsWith("zh-tw")) return "zh-TW";
1047
+ if (raw === "es" || raw.startsWith("es-")) return "es";
1048
+ if (raw === "en" || raw.startsWith("en-")) return "en";
1049
+ return "en";
1050
+ }
1051
+ function loginLabels(locale) {
1052
+ return BY_LOCALE[normalizeLoginLocale(locale)] ?? {};
1053
+ }
1054
+
672
1055
  // src/HeadlessLoginPanel.module.scss
673
1056
  var HeadlessLoginPanel_module_default = {
674
1057
  panel: "HeadlessLoginPanel_module_panel",
@@ -678,6 +1061,12 @@ var HeadlessLoginPanel_module_default = {
678
1061
  title: "HeadlessLoginPanel_module_title",
679
1062
  subtitle: "HeadlessLoginPanel_module_subtitle",
680
1063
  form: "HeadlessLoginPanel_module_form",
1064
+ channelTabs: "HeadlessLoginPanel_module_channelTabs",
1065
+ channelTab: "HeadlessLoginPanel_module_channelTab",
1066
+ channelTabActive: "HeadlessLoginPanel_module_channelTabActive",
1067
+ codeRow: "HeadlessLoginPanel_module_codeRow",
1068
+ codeInput: "HeadlessLoginPanel_module_codeInput",
1069
+ secondaryBtn: "HeadlessLoginPanel_module_secondaryBtn",
681
1070
  socialStack: "HeadlessLoginPanel_module_socialStack",
682
1071
  input: "HeadlessLoginPanel_module_input",
683
1072
  passwordField: "HeadlessLoginPanel_module_passwordField",
@@ -692,6 +1081,7 @@ var HeadlessLoginPanel_module_default = {
692
1081
  dividerLine: "HeadlessLoginPanel_module_dividerLine",
693
1082
  dividerText: "HeadlessLoginPanel_module_dividerText",
694
1083
  hint: "HeadlessLoginPanel_module_hint",
1084
+ domainsHint: "HeadlessLoginPanel_module_domainsHint",
695
1085
  switchMode: "HeadlessLoginPanel_module_switchMode",
696
1086
  switchModeBtn: "HeadlessLoginPanel_module_switchModeBtn",
697
1087
  error: "HeadlessLoginPanel_module_error",
@@ -712,7 +1102,7 @@ var defaults = {
712
1102
  submitGithub: "GitHub",
713
1103
  socialDivider: "or",
714
1104
  hint: "Social providers open directly. Password uses your LuminaryWorks account.",
715
- registerHint: "Create a username (start with a letter or _). Prefer email? Use a social provider below.",
1105
+ registerHint: "Email register needs a code (Gmail / Outlook / QQ / 163 / iCloud \u2026). Username is an alternative. Prefer one-click? Use a social provider.",
716
1106
  cancel: "Cancel",
717
1107
  experienceUnavailable: "Password sign-in is unavailable; use a social provider instead.",
718
1108
  showPassword: "Show password",
@@ -721,7 +1111,17 @@ var defaults = {
721
1111
  loginLink: "Already have an account? Sign in",
722
1112
  passwordMismatch: "Passwords do not match",
723
1113
  registerTitle: "Create account",
724
- registerSubtitle: "Create your LuminaryWorks unified account"
1114
+ registerSubtitle: "Create your LuminaryWorks unified account",
1115
+ registerWithEmail: "Email",
1116
+ registerWithUsername: "Username",
1117
+ emailPlaceholder: "Email",
1118
+ verificationCodePlaceholder: "Verification code",
1119
+ sendCode: "Send code",
1120
+ resendCode: "Resend code",
1121
+ allowedDomainsPrefix: "Allowed email domains",
1122
+ allowedDomainsOpen: "Email registration is open for most providers.",
1123
+ allowedDomainsBlocklist: "Most email providers are accepted. Temporary / disposable addresses are blocked.",
1124
+ waitingExternalBrowser: "Finish signing in with your browser, then return to this app."
725
1125
  };
726
1126
  function resolveSieBase(config) {
727
1127
  const explicit = config.experienceApiBase?.trim();
@@ -748,10 +1148,15 @@ function HeadlessLoginPanel({
748
1148
  productName,
749
1149
  logoSrc,
750
1150
  labels: labelsProp,
1151
+ locale,
751
1152
  returnUrl,
752
1153
  mode = "popup",
1154
+ openExternalUrl,
753
1155
  showSocialConnectors = true,
754
1156
  showRegister = true,
1157
+ registerEmailPolicy: registerEmailPolicyProp,
1158
+ fetchRegisterPolicy = true,
1159
+ getCaptchaToken,
755
1160
  socialProviders = "auto",
756
1161
  showCancel,
757
1162
  onCancel,
@@ -766,12 +1171,50 @@ function HeadlessLoginPanel({
766
1171
  experienceAdapterProp,
767
1172
  config.iamProvider
768
1173
  );
769
- const labels = { ...defaults, ...labelsProp };
1174
+ const [emailPolicy, setEmailPolicy] = react.useState(
1175
+ () => registerEmailPolicyProp ?? resolveRegisterEmailPolicy()
1176
+ );
1177
+ react.useEffect(() => {
1178
+ if (registerEmailPolicyProp) {
1179
+ setEmailPolicy(registerEmailPolicyProp);
1180
+ return;
1181
+ }
1182
+ if (!fetchRegisterPolicy || !showRegister) {
1183
+ setEmailPolicy(resolveRegisterEmailPolicy());
1184
+ return;
1185
+ }
1186
+ const base = config.experienceApiBase?.trim();
1187
+ if (!base) {
1188
+ setEmailPolicy(resolveRegisterEmailPolicy());
1189
+ return;
1190
+ }
1191
+ let cancelled = false;
1192
+ void fetchRegisterEmailPolicy(base).then((policy) => {
1193
+ if (!cancelled) setEmailPolicy(policy);
1194
+ });
1195
+ return () => {
1196
+ cancelled = true;
1197
+ };
1198
+ }, [
1199
+ config.experienceApiBase,
1200
+ fetchRegisterPolicy,
1201
+ registerEmailPolicyProp,
1202
+ showRegister
1203
+ ]);
1204
+ react.useEffect(() => {
1205
+ if (experienceAdapter instanceof LogtoExperienceAdapter) {
1206
+ experienceAdapter.emailPolicy = emailPolicy;
1207
+ }
1208
+ }, [emailPolicy, experienceAdapter]);
1209
+ const labels = { ...defaults, ...loginLabels(locale), ...labelsProp };
770
1210
  const configured = isIdpConfigured(config);
771
1211
  const [panelMode, setPanelMode] = react.useState("sign-in");
1212
+ const [registerChannel, setRegisterChannel] = react.useState("email");
772
1213
  const [identifier, setIdentifier] = react.useState("");
773
1214
  const [password, setPassword] = react.useState("");
774
1215
  const [confirmPassword, setConfirmPassword] = react.useState("");
1216
+ const [verificationCode, setVerificationCode] = react.useState("");
1217
+ const [emailVerificationId, setEmailVerificationId] = react.useState(null);
775
1218
  const [passwordVisible, setPasswordVisible] = react.useState(false);
776
1219
  const panelStyle = {
777
1220
  ...style,
@@ -779,13 +1222,20 @@ function HeadlessLoginPanel({
779
1222
  };
780
1223
  const [loading, setLoading] = react.useState(null);
781
1224
  const [error, setError] = react.useState("");
1225
+ const [awaitingExternal, setAwaitingExternal] = react.useState(false);
782
1226
  const [connectors, setConnectors] = react.useState([]);
783
1227
  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";
784
1228
  const passwordEnabled = experienceAdapter.capabilities.includes(LOGIN_EXPERIENCE_CAPABILITIES.passwordSignIn) && typeof experienceAdapter.experiencePasswordSignIn === "function";
785
- const registerEnabled = showRegister !== false && experienceAdapter.capabilities.includes(LOGIN_EXPERIENCE_CAPABILITIES.passwordSignUp) && typeof experienceAdapter.experiencePasswordSignUp === "function";
1229
+ 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");
1230
+ const emailRegisterEnabled = experienceAdapter.capabilities.includes(LOGIN_EXPERIENCE_CAPABILITIES.emailCodeSignUp) && typeof experienceAdapter.sendRegisterEmailCode === "function" && typeof experienceAdapter.experienceEmailPasswordSignUp === "function";
1231
+ const usernameRegisterEnabled = experienceAdapter.capabilities.includes(LOGIN_EXPERIENCE_CAPABILITIES.passwordSignUp) && typeof experienceAdapter.experiencePasswordSignUp === "function";
786
1232
  const isRegister = panelMode === "register" && registerEnabled;
1233
+ const useEmailRegister = isRegister && registerChannel === "email" && emailRegisterEnabled;
787
1234
  react.useEffect(() => {
788
- const resetBusy = () => setLoading(null);
1235
+ const resetBusy = () => {
1236
+ setLoading(null);
1237
+ setAwaitingExternal(false);
1238
+ };
789
1239
  window.addEventListener("pageshow", resetBusy);
790
1240
  window.addEventListener("popstate", resetBusy);
791
1241
  return () => {
@@ -828,6 +1278,19 @@ function HeadlessLoginPanel({
828
1278
  ]);
829
1279
  const runOidc = async (experienceRequest) => {
830
1280
  if (!configured) throw new Error("IdP not configured");
1281
+ if (mode === "external") {
1282
+ if (!openExternalUrl) {
1283
+ throw new Error('openExternalUrl is required when mode is "external"');
1284
+ }
1285
+ const url = await prepareSignInRequestUrl(config, {
1286
+ returnUrl,
1287
+ ...experienceRequest
1288
+ });
1289
+ setAwaitingExternal(true);
1290
+ await openExternalUrl(url);
1291
+ setLoading(null);
1292
+ return;
1293
+ }
831
1294
  if (mode === "redirect") {
832
1295
  await signInRedirect(config, { returnUrl, ...experienceRequest });
833
1296
  return;
@@ -851,6 +1314,7 @@ function HeadlessLoginPanel({
851
1314
  } catch (e) {
852
1315
  setError(e instanceof Error ? e.message : String(e));
853
1316
  setLoading(null);
1317
+ setAwaitingExternal(false);
854
1318
  }
855
1319
  };
856
1320
  const runHostedOidc = async () => {
@@ -861,6 +1325,7 @@ function HeadlessLoginPanel({
861
1325
  } catch (e) {
862
1326
  setError(e instanceof Error ? e.message : String(e));
863
1327
  setLoading(null);
1328
+ setAwaitingExternal(false);
864
1329
  }
865
1330
  };
866
1331
  const followExperienceRedirect = (redirectTo) => {
@@ -880,17 +1345,46 @@ function HeadlessLoginPanel({
880
1345
  if (!apiBase) {
881
1346
  throw new Error(labels.experienceUnavailable);
882
1347
  }
1348
+ const captchaToken = getCaptchaToken ? await getCaptchaToken() : void 0;
883
1349
  if (isRegister) {
884
- if (!registerEnabled || !experienceAdapter.experiencePasswordSignUp) {
885
- throw new Error(labels.experienceUnavailable);
886
- }
887
1350
  if (password !== confirmPassword) {
888
1351
  throw new Error(labels.passwordMismatch);
889
1352
  }
1353
+ if (useEmailRegister) {
1354
+ if (!experienceAdapter.experienceEmailPasswordSignUp || !emailVerificationId) {
1355
+ throw new Error("Send and enter the email verification code first");
1356
+ }
1357
+ const decision = evaluateRegisterEmail(identifier.trim(), emailPolicy);
1358
+ if (!decision.ok) throw new Error(registerEmailRejectionMessage(decision));
1359
+ const result3 = await experienceAdapter.experienceEmailPasswordSignUp({
1360
+ apiBase,
1361
+ identifier: identifier.trim(),
1362
+ email: identifier.trim(),
1363
+ password,
1364
+ code: verificationCode,
1365
+ verificationId: emailVerificationId,
1366
+ captchaToken,
1367
+ issuer: config.issuer,
1368
+ clientId: config.clientId,
1369
+ redirectUri: config.redirectUri,
1370
+ audience: config.audience,
1371
+ scopes: config.scopes,
1372
+ returnUrl
1373
+ });
1374
+ if (result3.redirectTo) {
1375
+ followExperienceRedirect(result3.redirectTo);
1376
+ return;
1377
+ }
1378
+ throw new Error("Experience API did not return redirectTo");
1379
+ }
1380
+ if (!usernameRegisterEnabled || !experienceAdapter.experiencePasswordSignUp) {
1381
+ throw new Error(labels.experienceUnavailable);
1382
+ }
890
1383
  const result2 = await experienceAdapter.experiencePasswordSignUp({
891
1384
  apiBase,
892
1385
  identifier: identifier.trim(),
893
1386
  password,
1387
+ captchaToken,
894
1388
  issuer: config.issuer,
895
1389
  clientId: config.clientId,
896
1390
  redirectUri: config.redirectUri,
@@ -930,20 +1424,59 @@ function HeadlessLoginPanel({
930
1424
  setLoading(null);
931
1425
  }
932
1426
  };
1427
+ const runSendCode = async () => {
1428
+ if (!configured || !useEmailRegister) return;
1429
+ setError("");
1430
+ setLoading("send-code");
1431
+ try {
1432
+ const apiBase = config.experienceApiBase?.trim();
1433
+ if (!apiBase || !experienceAdapter.sendRegisterEmailCode) {
1434
+ throw new Error(labels.experienceUnavailable);
1435
+ }
1436
+ const email = identifier.trim();
1437
+ const decision = evaluateRegisterEmail(email, emailPolicy);
1438
+ if (!decision.ok) throw new Error(registerEmailRejectionMessage(decision));
1439
+ const captchaToken = getCaptchaToken ? await getCaptchaToken() : void 0;
1440
+ const sent = await experienceAdapter.sendRegisterEmailCode({
1441
+ apiBase,
1442
+ identifier: email,
1443
+ email,
1444
+ captchaToken,
1445
+ issuer: config.issuer,
1446
+ clientId: config.clientId,
1447
+ redirectUri: config.redirectUri,
1448
+ audience: config.audience,
1449
+ scopes: config.scopes,
1450
+ returnUrl
1451
+ });
1452
+ setEmailVerificationId(sent.verificationId);
1453
+ } catch (e) {
1454
+ setError(e instanceof Error ? e.message : String(e));
1455
+ } finally {
1456
+ setLoading(null);
1457
+ }
1458
+ };
933
1459
  const switchPanelMode = (next) => {
934
1460
  setPanelMode(next);
935
1461
  setError("");
936
1462
  setConfirmPassword("");
1463
+ setVerificationCode("");
1464
+ setEmailVerificationId(null);
937
1465
  };
938
1466
  const busy = loading !== null;
939
1467
  const showSocial = socialEnabled && connectors.length > 0;
940
1468
  const showHint = Boolean(labelsProp?.hint) || showSocial || isRegister;
941
- const formReady = Boolean(identifier.trim()) && Boolean(password) && (!isRegister || Boolean(confirmPassword));
1469
+ const formReady = Boolean(identifier.trim()) && Boolean(password) && (!isRegister || Boolean(confirmPassword)) && (!useEmailRegister || Boolean(verificationCode.trim()) && Boolean(emailVerificationId));
942
1470
  const titleText = isRegister ? labels.registerTitle : labels.title;
943
1471
  const subtitleText = isRegister ? labels.registerSubtitle : labels.subtitle;
944
- const identifierPlaceholder = isRegister ? labels.registerIdentifierPlaceholder : labels.identifierPlaceholder;
1472
+ const identifierPlaceholder = isRegister ? useEmailRegister ? labels.emailPlaceholder : labels.registerIdentifierPlaceholder : labels.identifierPlaceholder;
945
1473
  const submitLabel = isRegister ? labels.submitRegister : labels.submitPassword;
946
1474
  const hintText = isRegister ? labels.registerHint : labels.hint;
1475
+ const domainsHint = useEmailRegister ? registerEmailDomainsHint(emailPolicy, {
1476
+ allowedDomainsPrefix: labels.allowedDomainsPrefix,
1477
+ allowedDomainsOpen: labels.allowedDomainsOpen,
1478
+ allowedDomainsBlocklist: labels.allowedDomainsBlocklist
1479
+ }) : null;
947
1480
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx(HeadlessLoginPanel_module_default.panel, className), style: panelStyle, children: [
948
1481
  /* @__PURE__ */ jsxRuntime.jsxs("header", { className: HeadlessLoginPanel_module_default.brand, children: [
949
1482
  logoSrc ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: logoSrc, alt: "", width: 48, height: 48, className: HeadlessLoginPanel_module_default.logo }) : null,
@@ -955,19 +1488,93 @@ function HeadlessLoginPanel({
955
1488
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: HeadlessLoginPanel_module_default.subtitle, children: subtitleText }),
956
1489
  configured ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
957
1490
  passwordEnabled ? /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: (e) => void runPassword(e), className: HeadlessLoginPanel_module_default.form, children: [
1491
+ isRegister && emailRegisterEnabled && usernameRegisterEnabled ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: HeadlessLoginPanel_module_default.channelTabs, role: "tablist", "aria-label": "Register method", children: [
1492
+ /* @__PURE__ */ jsxRuntime.jsx(
1493
+ "button",
1494
+ {
1495
+ type: "button",
1496
+ role: "tab",
1497
+ className: cx(
1498
+ HeadlessLoginPanel_module_default.channelTab,
1499
+ registerChannel === "email" && HeadlessLoginPanel_module_default.channelTabActive
1500
+ ),
1501
+ "aria-selected": registerChannel === "email",
1502
+ disabled: busy,
1503
+ onClick: () => {
1504
+ setRegisterChannel("email");
1505
+ setEmailVerificationId(null);
1506
+ setVerificationCode("");
1507
+ setError("");
1508
+ },
1509
+ children: labels.registerWithEmail
1510
+ }
1511
+ ),
1512
+ /* @__PURE__ */ jsxRuntime.jsx(
1513
+ "button",
1514
+ {
1515
+ type: "button",
1516
+ role: "tab",
1517
+ className: cx(
1518
+ HeadlessLoginPanel_module_default.channelTab,
1519
+ registerChannel === "username" && HeadlessLoginPanel_module_default.channelTabActive
1520
+ ),
1521
+ "aria-selected": registerChannel === "username",
1522
+ disabled: busy,
1523
+ onClick: () => {
1524
+ setRegisterChannel("username");
1525
+ setEmailVerificationId(null);
1526
+ setVerificationCode("");
1527
+ setError("");
1528
+ },
1529
+ children: labels.registerWithUsername
1530
+ }
1531
+ )
1532
+ ] }) : null,
958
1533
  /* @__PURE__ */ jsxRuntime.jsx(
959
1534
  "input",
960
1535
  {
961
- type: "text",
1536
+ type: useEmailRegister ? "email" : "text",
962
1537
  name: "identifier",
963
1538
  autoComplete: isRegister ? "username" : "username",
964
1539
  placeholder: identifierPlaceholder,
965
1540
  value: identifier,
966
- onChange: (e) => setIdentifier(e.target.value),
1541
+ onChange: (e) => {
1542
+ setIdentifier(e.target.value);
1543
+ if (useEmailRegister) {
1544
+ setEmailVerificationId(null);
1545
+ }
1546
+ },
967
1547
  className: HeadlessLoginPanel_module_default.input,
968
1548
  disabled: busy
969
1549
  }
970
1550
  ),
1551
+ domainsHint ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: HeadlessLoginPanel_module_default.domainsHint, "data-testid": "register-allowed-domains", children: domainsHint }) : null,
1552
+ useEmailRegister ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: HeadlessLoginPanel_module_default.codeRow, children: [
1553
+ /* @__PURE__ */ jsxRuntime.jsx(
1554
+ "input",
1555
+ {
1556
+ type: "text",
1557
+ name: "verificationCode",
1558
+ inputMode: "numeric",
1559
+ autoComplete: "one-time-code",
1560
+ placeholder: labels.verificationCodePlaceholder,
1561
+ value: verificationCode,
1562
+ onChange: (e) => setVerificationCode(e.target.value),
1563
+ className: cx(HeadlessLoginPanel_module_default.input, HeadlessLoginPanel_module_default.codeInput),
1564
+ disabled: busy
1565
+ }
1566
+ ),
1567
+ /* @__PURE__ */ jsxRuntime.jsx(
1568
+ "button",
1569
+ {
1570
+ type: "button",
1571
+ className: HeadlessLoginPanel_module_default.secondaryBtn,
1572
+ disabled: busy || !identifier.trim(),
1573
+ onClick: () => void runSendCode(),
1574
+ children: loading === "send-code" ? "\u2026" : emailVerificationId ? labels.resendCode : labels.sendCode
1575
+ }
1576
+ )
1577
+ ] }) : null,
971
1578
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: HeadlessLoginPanel_module_default.passwordField, children: [
972
1579
  /* @__PURE__ */ jsxRuntime.jsx(
973
1580
  "input",
@@ -1030,6 +1637,7 @@ function HeadlessLoginPanel({
1030
1637
  children: isRegister ? labels.loginLink : labels.registerLink
1031
1638
  }
1032
1639
  ) }) : null,
1640
+ awaitingExternal ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: HeadlessLoginPanel_module_default.hint, role: "status", children: labels.waitingExternalBrowser }) : null,
1033
1641
  showSocial ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: HeadlessLoginPanel_module_default.divider, "aria-hidden": true, children: [
1034
1642
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: HeadlessLoginPanel_module_default.dividerLine }),
1035
1643
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: HeadlessLoginPanel_module_default.dividerText, children: labels.socialDivider }),
@@ -1203,6 +1811,124 @@ function useAuthGateOptional() {
1203
1811
  return react.useContext(AuthGateContext);
1204
1812
  }
1205
1813
 
1814
+ // src/LoginLanguageSwitcher.module.scss
1815
+ var LoginLanguageSwitcher_module_default = {
1816
+ switch: "LoginLanguageSwitcher_module_switch",
1817
+ trigger: "LoginLanguageSwitcher_module_trigger",
1818
+ icon: "LoginLanguageSwitcher_module_icon",
1819
+ caret: "LoginLanguageSwitcher_module_caret",
1820
+ menu: "LoginLanguageSwitcher_module_menu",
1821
+ item: "LoginLanguageSwitcher_module_item",
1822
+ itemActive: "LoginLanguageSwitcher_module_itemActive",
1823
+ onDark: "LoginLanguageSwitcher_module_onDark"
1824
+ };
1825
+ function GlobeIcon() {
1826
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { className: LoginLanguageSwitcher_module_default.icon, viewBox: "0 0 24 24", "aria-hidden": "true", children: [
1827
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "9", fill: "none", stroke: "currentColor", strokeWidth: "1.6" }),
1828
+ /* @__PURE__ */ jsxRuntime.jsx("ellipse", { cx: "12", cy: "12", rx: "4", ry: "9", fill: "none", stroke: "currentColor", strokeWidth: "1.6" }),
1829
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 12h18M5 8h14M5 16h14", fill: "none", stroke: "currentColor", strokeWidth: "1.6" })
1830
+ ] });
1831
+ }
1832
+ function LoginLanguageSwitcher({
1833
+ locale,
1834
+ onChange,
1835
+ locales = DEFAULT_LOGIN_LOCALES,
1836
+ label = "Language",
1837
+ variant = "default"
1838
+ }) {
1839
+ const [open, setOpen] = react.useState(false);
1840
+ const rootRef = react.useRef(null);
1841
+ const menuId = react.useId();
1842
+ const current = locales.find((item) => item.code === locale) ?? locales.find((item) => normalizeLoginLocale(item.code) === normalizeLoginLocale(locale)) ?? locales[0];
1843
+ react.useEffect(() => {
1844
+ if (!open) return;
1845
+ const onPointer = (event) => {
1846
+ if (!rootRef.current?.contains(event.target)) setOpen(false);
1847
+ };
1848
+ const onKey = (event) => {
1849
+ if (event.key === "Escape") setOpen(false);
1850
+ };
1851
+ document.addEventListener("mousedown", onPointer);
1852
+ document.addEventListener("keydown", onKey);
1853
+ return () => {
1854
+ document.removeEventListener("mousedown", onPointer);
1855
+ document.removeEventListener("keydown", onKey);
1856
+ };
1857
+ }, [open]);
1858
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: LoginLanguageSwitcher_module_default.switch, ref: rootRef, children: [
1859
+ /* @__PURE__ */ jsxRuntime.jsxs(
1860
+ "button",
1861
+ {
1862
+ type: "button",
1863
+ className: variant === "onDark" ? `${LoginLanguageSwitcher_module_default.trigger} ${LoginLanguageSwitcher_module_default.onDark}` : LoginLanguageSwitcher_module_default.trigger,
1864
+ "aria-label": label,
1865
+ "aria-haspopup": "listbox",
1866
+ "aria-expanded": open,
1867
+ "aria-controls": menuId,
1868
+ onClick: () => setOpen((value) => !value),
1869
+ children: [
1870
+ /* @__PURE__ */ jsxRuntime.jsx(GlobeIcon, {}),
1871
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: current?.short ?? "EN" }),
1872
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { className: LoginLanguageSwitcher_module_default.caret, viewBox: "0 0 12 12", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2.5 4.5 6 8l3.5-3.5", fill: "none", stroke: "currentColor", strokeWidth: "1.4" }) })
1873
+ ]
1874
+ }
1875
+ ),
1876
+ open ? /* @__PURE__ */ jsxRuntime.jsx("ul", { className: LoginLanguageSwitcher_module_default.menu, id: menuId, role: "listbox", "aria-label": label, children: locales.map((item) => {
1877
+ const active = item.code === current?.code;
1878
+ return /* @__PURE__ */ jsxRuntime.jsx("li", { role: "presentation", children: /* @__PURE__ */ jsxRuntime.jsx(
1879
+ "button",
1880
+ {
1881
+ type: "button",
1882
+ role: "option",
1883
+ "aria-selected": active,
1884
+ className: active ? `${LoginLanguageSwitcher_module_default.item} ${LoginLanguageSwitcher_module_default.itemActive}` : LoginLanguageSwitcher_module_default.item,
1885
+ onClick: () => {
1886
+ onChange(item.code);
1887
+ setOpen(false);
1888
+ },
1889
+ children: item.label
1890
+ }
1891
+ ) }, item.code);
1892
+ }) }) : null
1893
+ ] });
1894
+ }
1895
+
1896
+ // src/LoginCanvas.module.scss
1897
+ var LoginCanvas_module_default = {
1898
+ canvas: "LoginCanvas_module_canvas",
1899
+ grid: "LoginCanvas_module_grid",
1900
+ lang: "LoginCanvas_module_lang",
1901
+ decor: "LoginCanvas_module_decor",
1902
+ slot: "LoginCanvas_module_slot",
1903
+ slotFill: "LoginCanvas_module_slotFill"
1904
+ };
1905
+ function LoginCanvas({
1906
+ tone,
1907
+ locale,
1908
+ onLocaleChange,
1909
+ locales,
1910
+ languageLabel,
1911
+ fill = false,
1912
+ decor,
1913
+ children
1914
+ }) {
1915
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: LoginCanvas_module_default.canvas, "data-tone": tone, children: [
1916
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: LoginCanvas_module_default.grid, "aria-hidden": "true" }),
1917
+ decor ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: LoginCanvas_module_default.decor, children: decor }) : null,
1918
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: LoginCanvas_module_default.lang, children: /* @__PURE__ */ jsxRuntime.jsx(
1919
+ LoginLanguageSwitcher,
1920
+ {
1921
+ locale,
1922
+ onChange: onLocaleChange,
1923
+ locales,
1924
+ label: languageLabel,
1925
+ variant: tone === "console" || tone === "data" ? "onDark" : "default"
1926
+ }
1927
+ ) }),
1928
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: fill ? `${LoginCanvas_module_default.slot} ${LoginCanvas_module_default.slotFill}` : LoginCanvas_module_default.slot, children })
1929
+ ] });
1930
+ }
1931
+
1206
1932
  // src/post-login-path.ts
1207
1933
  var DEFAULT_UNSAFE = ["/login", "/auth", "/exception"];
1208
1934
  function normalizeAppPath(path, fallback) {
@@ -1254,10 +1980,15 @@ function createPostLoginPathHelpers(options) {
1254
1980
  }
1255
1981
 
1256
1982
  exports.AuthGateProvider = AuthGateProvider;
1983
+ exports.DEFAULT_DISPOSABLE_EMAIL_BLOCKLIST = DEFAULT_DISPOSABLE_EMAIL_BLOCKLIST;
1984
+ exports.DEFAULT_LOGIN_LOCALES = DEFAULT_LOGIN_LOCALES;
1257
1985
  exports.DEFAULT_LOGIN_THEME_COLOR = DEFAULT_LOGIN_THEME_COLOR;
1986
+ exports.DEFAULT_REGISTER_EMAIL_ALLOWLIST = DEFAULT_REGISTER_EMAIL_ALLOWLIST;
1258
1987
  exports.HeadlessLoginPanel = HeadlessLoginPanel;
1259
1988
  exports.HostedOidcExperienceAdapter = HostedOidcExperienceAdapter;
1260
1989
  exports.LOGIN_EXPERIENCE_CAPABILITIES = LOGIN_EXPERIENCE_CAPABILITIES;
1990
+ exports.LoginCanvas = LoginCanvas;
1991
+ exports.LoginLanguageSwitcher = LoginLanguageSwitcher;
1261
1992
  exports.LogtoExperienceAdapter = LogtoExperienceAdapter;
1262
1993
  exports.LuminaryAuthProvider = LuminaryAuthProvider;
1263
1994
  exports.ReauthOverlay = ReauthOverlay;
@@ -1267,8 +1998,12 @@ exports.createLoginExperienceAdapter = createLoginExperienceAdapter;
1267
1998
  exports.createLogtoDirectSignInRequest = createLogtoDirectSignInRequest;
1268
1999
  exports.createPostLoginPathHelpers = createPostLoginPathHelpers;
1269
2000
  exports.createUserManager = createUserManager;
2001
+ exports.emailDomain = emailDomain;
2002
+ exports.evaluateRegisterEmail = evaluateRegisterEmail;
1270
2003
  exports.experiencePasswordSignIn = experiencePasswordSignIn;
2004
+ exports.fetchRegisterEmailPolicy = fetchRegisterEmailPolicy;
1271
2005
  exports.fetchSocialConnectors = fetchSocialConnectors;
2006
+ exports.formatAllowedEmailDomains = formatAllowedEmailDomains;
1272
2007
  exports.getCurrentUser = getCurrentUser;
1273
2008
  exports.getStoredAccessToken = getStoredAccessToken;
1274
2009
  exports.handleSignInCallback = handleSignInCallback;
@@ -1276,13 +2011,21 @@ exports.handleSignInPopupCallback = handleSignInPopupCallback;
1276
2011
  exports.isIdpConfigured = isIdpConfigured;
1277
2012
  exports.isLogtoRegisterUsername = isLogtoRegisterUsername;
1278
2013
  exports.isOidcPopupWindow = isOidcPopupWindow;
2014
+ exports.isValidEmailShape = isValidEmailShape;
2015
+ exports.loginLabels = loginLabels;
2016
+ exports.normalizeLoginLocale = normalizeLoginLocale;
2017
+ exports.normalizeRegisterEmailMode = normalizeRegisterEmailMode;
1279
2018
  exports.prepareSignInRequestUrl = prepareSignInRequestUrl;
1280
2019
  exports.readIdpConfigFromEnv = readIdpConfigFromEnv;
2020
+ exports.readRegisterEmailPolicyFromEnv = readRegisterEmailPolicyFromEnv;
2021
+ exports.registerEmailDomainsHint = registerEmailDomainsHint;
2022
+ exports.registerEmailRejectionMessage = registerEmailRejectionMessage;
1281
2023
  exports.resetUserManager = resetUserManager;
1282
2024
  exports.resolveExperienceApiBase = resolveExperienceApiBase;
1283
2025
  exports.resolveIssuer = resolveIssuer;
1284
2026
  exports.resolveLoginExperienceAdapter = resolveLoginExperienceAdapter;
1285
2027
  exports.resolveLoginExperienceProviderFromEnv = resolveLoginExperienceProviderFromEnv;
2028
+ exports.resolveRegisterEmailPolicy = resolveRegisterEmailPolicy;
1286
2029
  exports.sameOriginAuthorizeUrl = sameOriginAuthorizeUrl;
1287
2030
  exports.signInPopup = signInPopup;
1288
2031
  exports.signInRedirect = signInRedirect;