@luminaryworks/auth-react 0.4.5 → 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/README.md +14 -0
- package/dist/index.css +56 -0
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +155 -7
- package/dist/index.js +500 -13
- package/dist/index.js.map +1 -1
- package/dist/style.css +56 -0
- package/dist/style.css.map +1 -1
- package/package.json +1 -1
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: 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({
|
|
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,7 @@ 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",
|
|
695
953
|
switchMode: "HeadlessLoginPanel_module_switchMode",
|
|
696
954
|
switchModeBtn: "HeadlessLoginPanel_module_switchModeBtn",
|
|
697
955
|
error: "HeadlessLoginPanel_module_error",
|
|
@@ -712,7 +970,7 @@ var defaults = {
|
|
|
712
970
|
submitGithub: "GitHub",
|
|
713
971
|
socialDivider: "or",
|
|
714
972
|
hint: "Social providers open directly. Password uses your LuminaryWorks account.",
|
|
715
|
-
registerHint: "
|
|
973
|
+
registerHint: "Email register needs a code (Gmail / Outlook / QQ / 163 / iCloud \u2026). Username is an alternative. Prefer one-click? Use a social provider.",
|
|
716
974
|
cancel: "Cancel",
|
|
717
975
|
experienceUnavailable: "Password sign-in is unavailable; use a social provider instead.",
|
|
718
976
|
showPassword: "Show password",
|
|
@@ -721,7 +979,17 @@ var defaults = {
|
|
|
721
979
|
loginLink: "Already have an account? Sign in",
|
|
722
980
|
passwordMismatch: "Passwords do not match",
|
|
723
981
|
registerTitle: "Create account",
|
|
724
|
-
registerSubtitle: "Create your LuminaryWorks unified 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."
|
|
725
993
|
};
|
|
726
994
|
function resolveSieBase(config) {
|
|
727
995
|
const explicit = config.experienceApiBase?.trim();
|
|
@@ -750,8 +1018,12 @@ function HeadlessLoginPanel({
|
|
|
750
1018
|
labels: labelsProp,
|
|
751
1019
|
returnUrl,
|
|
752
1020
|
mode = "popup",
|
|
1021
|
+
openExternalUrl,
|
|
753
1022
|
showSocialConnectors = true,
|
|
754
1023
|
showRegister = true,
|
|
1024
|
+
registerEmailPolicy: registerEmailPolicyProp,
|
|
1025
|
+
fetchRegisterPolicy = true,
|
|
1026
|
+
getCaptchaToken,
|
|
755
1027
|
socialProviders = "auto",
|
|
756
1028
|
showCancel,
|
|
757
1029
|
onCancel,
|
|
@@ -766,12 +1038,50 @@ function HeadlessLoginPanel({
|
|
|
766
1038
|
experienceAdapterProp,
|
|
767
1039
|
config.iamProvider
|
|
768
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]);
|
|
769
1076
|
const labels = { ...defaults, ...labelsProp };
|
|
770
1077
|
const configured = isIdpConfigured(config);
|
|
771
1078
|
const [panelMode, setPanelMode] = react.useState("sign-in");
|
|
1079
|
+
const [registerChannel, setRegisterChannel] = react.useState("email");
|
|
772
1080
|
const [identifier, setIdentifier] = react.useState("");
|
|
773
1081
|
const [password, setPassword] = react.useState("");
|
|
774
1082
|
const [confirmPassword, setConfirmPassword] = react.useState("");
|
|
1083
|
+
const [verificationCode, setVerificationCode] = react.useState("");
|
|
1084
|
+
const [emailVerificationId, setEmailVerificationId] = react.useState(null);
|
|
775
1085
|
const [passwordVisible, setPasswordVisible] = react.useState(false);
|
|
776
1086
|
const panelStyle = {
|
|
777
1087
|
...style,
|
|
@@ -779,13 +1089,20 @@ function HeadlessLoginPanel({
|
|
|
779
1089
|
};
|
|
780
1090
|
const [loading, setLoading] = react.useState(null);
|
|
781
1091
|
const [error, setError] = react.useState("");
|
|
1092
|
+
const [awaitingExternal, setAwaitingExternal] = react.useState(false);
|
|
782
1093
|
const [connectors, setConnectors] = react.useState([]);
|
|
783
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";
|
|
784
1095
|
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";
|
|
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";
|
|
786
1099
|
const isRegister = panelMode === "register" && registerEnabled;
|
|
1100
|
+
const useEmailRegister = isRegister && registerChannel === "email" && emailRegisterEnabled;
|
|
787
1101
|
react.useEffect(() => {
|
|
788
|
-
const resetBusy = () =>
|
|
1102
|
+
const resetBusy = () => {
|
|
1103
|
+
setLoading(null);
|
|
1104
|
+
setAwaitingExternal(false);
|
|
1105
|
+
};
|
|
789
1106
|
window.addEventListener("pageshow", resetBusy);
|
|
790
1107
|
window.addEventListener("popstate", resetBusy);
|
|
791
1108
|
return () => {
|
|
@@ -828,6 +1145,19 @@ function HeadlessLoginPanel({
|
|
|
828
1145
|
]);
|
|
829
1146
|
const runOidc = async (experienceRequest) => {
|
|
830
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
|
+
}
|
|
831
1161
|
if (mode === "redirect") {
|
|
832
1162
|
await signInRedirect(config, { returnUrl, ...experienceRequest });
|
|
833
1163
|
return;
|
|
@@ -851,6 +1181,7 @@ function HeadlessLoginPanel({
|
|
|
851
1181
|
} catch (e) {
|
|
852
1182
|
setError(e instanceof Error ? e.message : String(e));
|
|
853
1183
|
setLoading(null);
|
|
1184
|
+
setAwaitingExternal(false);
|
|
854
1185
|
}
|
|
855
1186
|
};
|
|
856
1187
|
const runHostedOidc = async () => {
|
|
@@ -861,6 +1192,7 @@ function HeadlessLoginPanel({
|
|
|
861
1192
|
} catch (e) {
|
|
862
1193
|
setError(e instanceof Error ? e.message : String(e));
|
|
863
1194
|
setLoading(null);
|
|
1195
|
+
setAwaitingExternal(false);
|
|
864
1196
|
}
|
|
865
1197
|
};
|
|
866
1198
|
const followExperienceRedirect = (redirectTo) => {
|
|
@@ -880,17 +1212,46 @@ function HeadlessLoginPanel({
|
|
|
880
1212
|
if (!apiBase) {
|
|
881
1213
|
throw new Error(labels.experienceUnavailable);
|
|
882
1214
|
}
|
|
1215
|
+
const captchaToken = getCaptchaToken ? await getCaptchaToken() : void 0;
|
|
883
1216
|
if (isRegister) {
|
|
884
|
-
if (!registerEnabled || !experienceAdapter.experiencePasswordSignUp) {
|
|
885
|
-
throw new Error(labels.experienceUnavailable);
|
|
886
|
-
}
|
|
887
1217
|
if (password !== confirmPassword) {
|
|
888
1218
|
throw new Error(labels.passwordMismatch);
|
|
889
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
|
+
}
|
|
890
1250
|
const result2 = await experienceAdapter.experiencePasswordSignUp({
|
|
891
1251
|
apiBase,
|
|
892
1252
|
identifier: identifier.trim(),
|
|
893
1253
|
password,
|
|
1254
|
+
captchaToken,
|
|
894
1255
|
issuer: config.issuer,
|
|
895
1256
|
clientId: config.clientId,
|
|
896
1257
|
redirectUri: config.redirectUri,
|
|
@@ -930,20 +1291,59 @@ function HeadlessLoginPanel({
|
|
|
930
1291
|
setLoading(null);
|
|
931
1292
|
}
|
|
932
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
|
+
};
|
|
933
1326
|
const switchPanelMode = (next) => {
|
|
934
1327
|
setPanelMode(next);
|
|
935
1328
|
setError("");
|
|
936
1329
|
setConfirmPassword("");
|
|
1330
|
+
setVerificationCode("");
|
|
1331
|
+
setEmailVerificationId(null);
|
|
937
1332
|
};
|
|
938
1333
|
const busy = loading !== null;
|
|
939
1334
|
const showSocial = socialEnabled && connectors.length > 0;
|
|
940
1335
|
const showHint = Boolean(labelsProp?.hint) || showSocial || isRegister;
|
|
941
|
-
const formReady = Boolean(identifier.trim()) && Boolean(password) && (!isRegister || Boolean(confirmPassword));
|
|
1336
|
+
const formReady = Boolean(identifier.trim()) && Boolean(password) && (!isRegister || Boolean(confirmPassword)) && (!useEmailRegister || Boolean(verificationCode.trim()) && Boolean(emailVerificationId));
|
|
942
1337
|
const titleText = isRegister ? labels.registerTitle : labels.title;
|
|
943
1338
|
const subtitleText = isRegister ? labels.registerSubtitle : labels.subtitle;
|
|
944
|
-
const identifierPlaceholder = isRegister ? labels.registerIdentifierPlaceholder : labels.identifierPlaceholder;
|
|
1339
|
+
const identifierPlaceholder = isRegister ? useEmailRegister ? labels.emailPlaceholder : labels.registerIdentifierPlaceholder : labels.identifierPlaceholder;
|
|
945
1340
|
const submitLabel = isRegister ? labels.submitRegister : labels.submitPassword;
|
|
946
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;
|
|
947
1347
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx(HeadlessLoginPanel_module_default.panel, className), style: panelStyle, children: [
|
|
948
1348
|
/* @__PURE__ */ jsxRuntime.jsxs("header", { className: HeadlessLoginPanel_module_default.brand, children: [
|
|
949
1349
|
logoSrc ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: logoSrc, alt: "", width: 48, height: 48, className: HeadlessLoginPanel_module_default.logo }) : null,
|
|
@@ -955,19 +1355,93 @@ function HeadlessLoginPanel({
|
|
|
955
1355
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: HeadlessLoginPanel_module_default.subtitle, children: subtitleText }),
|
|
956
1356
|
configured ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
957
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,
|
|
958
1400
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
959
1401
|
"input",
|
|
960
1402
|
{
|
|
961
|
-
type: "text",
|
|
1403
|
+
type: useEmailRegister ? "email" : "text",
|
|
962
1404
|
name: "identifier",
|
|
963
1405
|
autoComplete: isRegister ? "username" : "username",
|
|
964
1406
|
placeholder: identifierPlaceholder,
|
|
965
1407
|
value: identifier,
|
|
966
|
-
onChange: (e) =>
|
|
1408
|
+
onChange: (e) => {
|
|
1409
|
+
setIdentifier(e.target.value);
|
|
1410
|
+
if (useEmailRegister) {
|
|
1411
|
+
setEmailVerificationId(null);
|
|
1412
|
+
}
|
|
1413
|
+
},
|
|
967
1414
|
className: HeadlessLoginPanel_module_default.input,
|
|
968
1415
|
disabled: busy
|
|
969
1416
|
}
|
|
970
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,
|
|
971
1445
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: HeadlessLoginPanel_module_default.passwordField, children: [
|
|
972
1446
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
973
1447
|
"input",
|
|
@@ -1030,6 +1504,7 @@ function HeadlessLoginPanel({
|
|
|
1030
1504
|
children: isRegister ? labels.loginLink : labels.registerLink
|
|
1031
1505
|
}
|
|
1032
1506
|
) }) : null,
|
|
1507
|
+
awaitingExternal ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: HeadlessLoginPanel_module_default.hint, role: "status", children: labels.waitingExternalBrowser }) : null,
|
|
1033
1508
|
showSocial ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: HeadlessLoginPanel_module_default.divider, "aria-hidden": true, children: [
|
|
1034
1509
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: HeadlessLoginPanel_module_default.dividerLine }),
|
|
1035
1510
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: HeadlessLoginPanel_module_default.dividerText, children: labels.socialDivider }),
|
|
@@ -1254,7 +1729,9 @@ function createPostLoginPathHelpers(options) {
|
|
|
1254
1729
|
}
|
|
1255
1730
|
|
|
1256
1731
|
exports.AuthGateProvider = AuthGateProvider;
|
|
1732
|
+
exports.DEFAULT_DISPOSABLE_EMAIL_BLOCKLIST = DEFAULT_DISPOSABLE_EMAIL_BLOCKLIST;
|
|
1257
1733
|
exports.DEFAULT_LOGIN_THEME_COLOR = DEFAULT_LOGIN_THEME_COLOR;
|
|
1734
|
+
exports.DEFAULT_REGISTER_EMAIL_ALLOWLIST = DEFAULT_REGISTER_EMAIL_ALLOWLIST;
|
|
1258
1735
|
exports.HeadlessLoginPanel = HeadlessLoginPanel;
|
|
1259
1736
|
exports.HostedOidcExperienceAdapter = HostedOidcExperienceAdapter;
|
|
1260
1737
|
exports.LOGIN_EXPERIENCE_CAPABILITIES = LOGIN_EXPERIENCE_CAPABILITIES;
|
|
@@ -1267,8 +1744,12 @@ exports.createLoginExperienceAdapter = createLoginExperienceAdapter;
|
|
|
1267
1744
|
exports.createLogtoDirectSignInRequest = createLogtoDirectSignInRequest;
|
|
1268
1745
|
exports.createPostLoginPathHelpers = createPostLoginPathHelpers;
|
|
1269
1746
|
exports.createUserManager = createUserManager;
|
|
1747
|
+
exports.emailDomain = emailDomain;
|
|
1748
|
+
exports.evaluateRegisterEmail = evaluateRegisterEmail;
|
|
1270
1749
|
exports.experiencePasswordSignIn = experiencePasswordSignIn;
|
|
1750
|
+
exports.fetchRegisterEmailPolicy = fetchRegisterEmailPolicy;
|
|
1271
1751
|
exports.fetchSocialConnectors = fetchSocialConnectors;
|
|
1752
|
+
exports.formatAllowedEmailDomains = formatAllowedEmailDomains;
|
|
1272
1753
|
exports.getCurrentUser = getCurrentUser;
|
|
1273
1754
|
exports.getStoredAccessToken = getStoredAccessToken;
|
|
1274
1755
|
exports.handleSignInCallback = handleSignInCallback;
|
|
@@ -1276,13 +1757,19 @@ exports.handleSignInPopupCallback = handleSignInPopupCallback;
|
|
|
1276
1757
|
exports.isIdpConfigured = isIdpConfigured;
|
|
1277
1758
|
exports.isLogtoRegisterUsername = isLogtoRegisterUsername;
|
|
1278
1759
|
exports.isOidcPopupWindow = isOidcPopupWindow;
|
|
1760
|
+
exports.isValidEmailShape = isValidEmailShape;
|
|
1761
|
+
exports.normalizeRegisterEmailMode = normalizeRegisterEmailMode;
|
|
1279
1762
|
exports.prepareSignInRequestUrl = prepareSignInRequestUrl;
|
|
1280
1763
|
exports.readIdpConfigFromEnv = readIdpConfigFromEnv;
|
|
1764
|
+
exports.readRegisterEmailPolicyFromEnv = readRegisterEmailPolicyFromEnv;
|
|
1765
|
+
exports.registerEmailDomainsHint = registerEmailDomainsHint;
|
|
1766
|
+
exports.registerEmailRejectionMessage = registerEmailRejectionMessage;
|
|
1281
1767
|
exports.resetUserManager = resetUserManager;
|
|
1282
1768
|
exports.resolveExperienceApiBase = resolveExperienceApiBase;
|
|
1283
1769
|
exports.resolveIssuer = resolveIssuer;
|
|
1284
1770
|
exports.resolveLoginExperienceAdapter = resolveLoginExperienceAdapter;
|
|
1285
1771
|
exports.resolveLoginExperienceProviderFromEnv = resolveLoginExperienceProviderFromEnv;
|
|
1772
|
+
exports.resolveRegisterEmailPolicy = resolveRegisterEmailPolicy;
|
|
1286
1773
|
exports.sameOriginAuthorizeUrl = sameOriginAuthorizeUrl;
|
|
1287
1774
|
exports.signInPopup = signInPopup;
|
|
1288
1775
|
exports.signInRedirect = signInRedirect;
|