@luminaryworks/auth-react 0.4.4 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/dist/index.css +75 -0
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +157 -7
- package/dist/index.js +567 -16
- package/dist/index.js.map +1 -1
- package/dist/style.css +75 -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_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,9 @@ var HeadlessLoginPanel_module_default = {
|
|
|
692
949
|
dividerLine: "HeadlessLoginPanel_module_dividerLine",
|
|
693
950
|
dividerText: "HeadlessLoginPanel_module_dividerText",
|
|
694
951
|
hint: "HeadlessLoginPanel_module_hint",
|
|
952
|
+
domainsHint: "HeadlessLoginPanel_module_domainsHint",
|
|
953
|
+
switchMode: "HeadlessLoginPanel_module_switchMode",
|
|
954
|
+
switchModeBtn: "HeadlessLoginPanel_module_switchModeBtn",
|
|
695
955
|
error: "HeadlessLoginPanel_module_error",
|
|
696
956
|
footer: "HeadlessLoginPanel_module_footer"
|
|
697
957
|
};
|
|
@@ -710,14 +970,26 @@ var defaults = {
|
|
|
710
970
|
submitGithub: "GitHub",
|
|
711
971
|
socialDivider: "or",
|
|
712
972
|
hint: "Social providers open directly. Password uses your LuminaryWorks account.",
|
|
713
|
-
registerHint: "
|
|
973
|
+
registerHint: "Email register needs a code (Gmail / Outlook / QQ / 163 / iCloud \u2026). Username is an alternative. Prefer one-click? Use a social provider.",
|
|
714
974
|
cancel: "Cancel",
|
|
715
975
|
experienceUnavailable: "Password sign-in is unavailable; use a social provider instead.",
|
|
716
976
|
showPassword: "Show password",
|
|
717
977
|
hidePassword: "Hide password",
|
|
718
978
|
registerLink: "Create an account",
|
|
719
979
|
loginLink: "Already have an account? Sign in",
|
|
720
|
-
passwordMismatch: "Passwords do not match"
|
|
980
|
+
passwordMismatch: "Passwords do not match",
|
|
981
|
+
registerTitle: "Create account",
|
|
982
|
+
registerSubtitle: "Create your LuminaryWorks unified account",
|
|
983
|
+
registerWithEmail: "Email",
|
|
984
|
+
registerWithUsername: "Username",
|
|
985
|
+
emailPlaceholder: "Email",
|
|
986
|
+
verificationCodePlaceholder: "Verification code",
|
|
987
|
+
sendCode: "Send code",
|
|
988
|
+
resendCode: "Resend code",
|
|
989
|
+
allowedDomainsPrefix: "Allowed email domains",
|
|
990
|
+
allowedDomainsOpen: "Email registration is open for most providers.",
|
|
991
|
+
allowedDomainsBlocklist: "Most email providers are accepted. Temporary / disposable addresses are blocked.",
|
|
992
|
+
waitingExternalBrowser: "Finish signing in with your browser, then return to this app."
|
|
721
993
|
};
|
|
722
994
|
function resolveSieBase(config) {
|
|
723
995
|
const explicit = config.experienceApiBase?.trim();
|
|
@@ -746,8 +1018,12 @@ function HeadlessLoginPanel({
|
|
|
746
1018
|
labels: labelsProp,
|
|
747
1019
|
returnUrl,
|
|
748
1020
|
mode = "popup",
|
|
1021
|
+
openExternalUrl,
|
|
749
1022
|
showSocialConnectors = true,
|
|
750
1023
|
showRegister = true,
|
|
1024
|
+
registerEmailPolicy: registerEmailPolicyProp,
|
|
1025
|
+
fetchRegisterPolicy = true,
|
|
1026
|
+
getCaptchaToken,
|
|
751
1027
|
socialProviders = "auto",
|
|
752
1028
|
showCancel,
|
|
753
1029
|
onCancel,
|
|
@@ -762,12 +1038,50 @@ function HeadlessLoginPanel({
|
|
|
762
1038
|
experienceAdapterProp,
|
|
763
1039
|
config.iamProvider
|
|
764
1040
|
);
|
|
1041
|
+
const [emailPolicy, setEmailPolicy] = react.useState(
|
|
1042
|
+
() => registerEmailPolicyProp ?? resolveRegisterEmailPolicy()
|
|
1043
|
+
);
|
|
1044
|
+
react.useEffect(() => {
|
|
1045
|
+
if (registerEmailPolicyProp) {
|
|
1046
|
+
setEmailPolicy(registerEmailPolicyProp);
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
if (!fetchRegisterPolicy || !showRegister) {
|
|
1050
|
+
setEmailPolicy(resolveRegisterEmailPolicy());
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
const base = config.experienceApiBase?.trim();
|
|
1054
|
+
if (!base) {
|
|
1055
|
+
setEmailPolicy(resolveRegisterEmailPolicy());
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
let cancelled = false;
|
|
1059
|
+
void fetchRegisterEmailPolicy(base).then((policy) => {
|
|
1060
|
+
if (!cancelled) setEmailPolicy(policy);
|
|
1061
|
+
});
|
|
1062
|
+
return () => {
|
|
1063
|
+
cancelled = true;
|
|
1064
|
+
};
|
|
1065
|
+
}, [
|
|
1066
|
+
config.experienceApiBase,
|
|
1067
|
+
fetchRegisterPolicy,
|
|
1068
|
+
registerEmailPolicyProp,
|
|
1069
|
+
showRegister
|
|
1070
|
+
]);
|
|
1071
|
+
react.useEffect(() => {
|
|
1072
|
+
if (experienceAdapter instanceof LogtoExperienceAdapter) {
|
|
1073
|
+
experienceAdapter.emailPolicy = emailPolicy;
|
|
1074
|
+
}
|
|
1075
|
+
}, [emailPolicy, experienceAdapter]);
|
|
765
1076
|
const labels = { ...defaults, ...labelsProp };
|
|
766
1077
|
const configured = isIdpConfigured(config);
|
|
767
1078
|
const [panelMode, setPanelMode] = react.useState("sign-in");
|
|
1079
|
+
const [registerChannel, setRegisterChannel] = react.useState("email");
|
|
768
1080
|
const [identifier, setIdentifier] = react.useState("");
|
|
769
1081
|
const [password, setPassword] = react.useState("");
|
|
770
1082
|
const [confirmPassword, setConfirmPassword] = react.useState("");
|
|
1083
|
+
const [verificationCode, setVerificationCode] = react.useState("");
|
|
1084
|
+
const [emailVerificationId, setEmailVerificationId] = react.useState(null);
|
|
771
1085
|
const [passwordVisible, setPasswordVisible] = react.useState(false);
|
|
772
1086
|
const panelStyle = {
|
|
773
1087
|
...style,
|
|
@@ -775,12 +1089,20 @@ function HeadlessLoginPanel({
|
|
|
775
1089
|
};
|
|
776
1090
|
const [loading, setLoading] = react.useState(null);
|
|
777
1091
|
const [error, setError] = react.useState("");
|
|
1092
|
+
const [awaitingExternal, setAwaitingExternal] = react.useState(false);
|
|
778
1093
|
const [connectors, setConnectors] = react.useState([]);
|
|
779
1094
|
const socialEnabled = showSocialConnectors !== false && !(Array.isArray(socialProviders) && socialProviders.length === 0) && experienceAdapter.capabilities.includes(LOGIN_EXPERIENCE_CAPABILITIES.socialConnectors) && experienceAdapter.capabilities.includes(LOGIN_EXPERIENCE_CAPABILITIES.socialDirectSignIn) && typeof experienceAdapter.fetchSocialConnectors === "function" && typeof experienceAdapter.createSocialSignInRequest === "function";
|
|
780
1095
|
const passwordEnabled = experienceAdapter.capabilities.includes(LOGIN_EXPERIENCE_CAPABILITIES.passwordSignIn) && typeof experienceAdapter.experiencePasswordSignIn === "function";
|
|
781
|
-
showRegister !== false && experienceAdapter.capabilities.includes(LOGIN_EXPERIENCE_CAPABILITIES.passwordSignUp) && typeof experienceAdapter.experiencePasswordSignUp === "function";
|
|
1096
|
+
const registerEnabled = showRegister !== false && (experienceAdapter.capabilities.includes(LOGIN_EXPERIENCE_CAPABILITIES.passwordSignUp) && typeof experienceAdapter.experiencePasswordSignUp === "function" || experienceAdapter.capabilities.includes(LOGIN_EXPERIENCE_CAPABILITIES.emailCodeSignUp) && typeof experienceAdapter.sendRegisterEmailCode === "function" && typeof experienceAdapter.experienceEmailPasswordSignUp === "function");
|
|
1097
|
+
const emailRegisterEnabled = experienceAdapter.capabilities.includes(LOGIN_EXPERIENCE_CAPABILITIES.emailCodeSignUp) && typeof experienceAdapter.sendRegisterEmailCode === "function" && typeof experienceAdapter.experienceEmailPasswordSignUp === "function";
|
|
1098
|
+
const usernameRegisterEnabled = experienceAdapter.capabilities.includes(LOGIN_EXPERIENCE_CAPABILITIES.passwordSignUp) && typeof experienceAdapter.experiencePasswordSignUp === "function";
|
|
1099
|
+
const isRegister = panelMode === "register" && registerEnabled;
|
|
1100
|
+
const useEmailRegister = isRegister && registerChannel === "email" && emailRegisterEnabled;
|
|
782
1101
|
react.useEffect(() => {
|
|
783
|
-
const resetBusy = () =>
|
|
1102
|
+
const resetBusy = () => {
|
|
1103
|
+
setLoading(null);
|
|
1104
|
+
setAwaitingExternal(false);
|
|
1105
|
+
};
|
|
784
1106
|
window.addEventListener("pageshow", resetBusy);
|
|
785
1107
|
window.addEventListener("popstate", resetBusy);
|
|
786
1108
|
return () => {
|
|
@@ -823,6 +1145,19 @@ function HeadlessLoginPanel({
|
|
|
823
1145
|
]);
|
|
824
1146
|
const runOidc = async (experienceRequest) => {
|
|
825
1147
|
if (!configured) throw new Error("IdP not configured");
|
|
1148
|
+
if (mode === "external") {
|
|
1149
|
+
if (!openExternalUrl) {
|
|
1150
|
+
throw new Error('openExternalUrl is required when mode is "external"');
|
|
1151
|
+
}
|
|
1152
|
+
const url = await prepareSignInRequestUrl(config, {
|
|
1153
|
+
returnUrl,
|
|
1154
|
+
...experienceRequest
|
|
1155
|
+
});
|
|
1156
|
+
setAwaitingExternal(true);
|
|
1157
|
+
await openExternalUrl(url);
|
|
1158
|
+
setLoading(null);
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
826
1161
|
if (mode === "redirect") {
|
|
827
1162
|
await signInRedirect(config, { returnUrl, ...experienceRequest });
|
|
828
1163
|
return;
|
|
@@ -846,6 +1181,7 @@ function HeadlessLoginPanel({
|
|
|
846
1181
|
} catch (e) {
|
|
847
1182
|
setError(e instanceof Error ? e.message : String(e));
|
|
848
1183
|
setLoading(null);
|
|
1184
|
+
setAwaitingExternal(false);
|
|
849
1185
|
}
|
|
850
1186
|
};
|
|
851
1187
|
const runHostedOidc = async () => {
|
|
@@ -856,6 +1192,7 @@ function HeadlessLoginPanel({
|
|
|
856
1192
|
} catch (e) {
|
|
857
1193
|
setError(e instanceof Error ? e.message : String(e));
|
|
858
1194
|
setLoading(null);
|
|
1195
|
+
setAwaitingExternal(false);
|
|
859
1196
|
}
|
|
860
1197
|
};
|
|
861
1198
|
const followExperienceRedirect = (redirectTo) => {
|
|
@@ -875,6 +1212,59 @@ function HeadlessLoginPanel({
|
|
|
875
1212
|
if (!apiBase) {
|
|
876
1213
|
throw new Error(labels.experienceUnavailable);
|
|
877
1214
|
}
|
|
1215
|
+
const captchaToken = getCaptchaToken ? await getCaptchaToken() : void 0;
|
|
1216
|
+
if (isRegister) {
|
|
1217
|
+
if (password !== confirmPassword) {
|
|
1218
|
+
throw new Error(labels.passwordMismatch);
|
|
1219
|
+
}
|
|
1220
|
+
if (useEmailRegister) {
|
|
1221
|
+
if (!experienceAdapter.experienceEmailPasswordSignUp || !emailVerificationId) {
|
|
1222
|
+
throw new Error("Send and enter the email verification code first");
|
|
1223
|
+
}
|
|
1224
|
+
const decision = evaluateRegisterEmail(identifier.trim(), emailPolicy);
|
|
1225
|
+
if (!decision.ok) throw new Error(registerEmailRejectionMessage(decision));
|
|
1226
|
+
const result3 = await experienceAdapter.experienceEmailPasswordSignUp({
|
|
1227
|
+
apiBase,
|
|
1228
|
+
identifier: identifier.trim(),
|
|
1229
|
+
email: identifier.trim(),
|
|
1230
|
+
password,
|
|
1231
|
+
code: verificationCode,
|
|
1232
|
+
verificationId: emailVerificationId,
|
|
1233
|
+
captchaToken,
|
|
1234
|
+
issuer: config.issuer,
|
|
1235
|
+
clientId: config.clientId,
|
|
1236
|
+
redirectUri: config.redirectUri,
|
|
1237
|
+
audience: config.audience,
|
|
1238
|
+
scopes: config.scopes,
|
|
1239
|
+
returnUrl
|
|
1240
|
+
});
|
|
1241
|
+
if (result3.redirectTo) {
|
|
1242
|
+
followExperienceRedirect(result3.redirectTo);
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
throw new Error("Experience API did not return redirectTo");
|
|
1246
|
+
}
|
|
1247
|
+
if (!usernameRegisterEnabled || !experienceAdapter.experiencePasswordSignUp) {
|
|
1248
|
+
throw new Error(labels.experienceUnavailable);
|
|
1249
|
+
}
|
|
1250
|
+
const result2 = await experienceAdapter.experiencePasswordSignUp({
|
|
1251
|
+
apiBase,
|
|
1252
|
+
identifier: identifier.trim(),
|
|
1253
|
+
password,
|
|
1254
|
+
captchaToken,
|
|
1255
|
+
issuer: config.issuer,
|
|
1256
|
+
clientId: config.clientId,
|
|
1257
|
+
redirectUri: config.redirectUri,
|
|
1258
|
+
audience: config.audience,
|
|
1259
|
+
scopes: config.scopes,
|
|
1260
|
+
returnUrl
|
|
1261
|
+
});
|
|
1262
|
+
if (result2.redirectTo) {
|
|
1263
|
+
followExperienceRedirect(result2.redirectTo);
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
throw new Error("Experience API did not return redirectTo");
|
|
1267
|
+
}
|
|
878
1268
|
if (!passwordEnabled || !experienceAdapter.capabilities.includes(
|
|
879
1269
|
LOGIN_EXPERIENCE_CAPABILITIES.passwordSignIn
|
|
880
1270
|
) || !experienceAdapter.experiencePasswordSignIn) {
|
|
@@ -901,40 +1291,164 @@ function HeadlessLoginPanel({
|
|
|
901
1291
|
setLoading(null);
|
|
902
1292
|
}
|
|
903
1293
|
};
|
|
1294
|
+
const runSendCode = async () => {
|
|
1295
|
+
if (!configured || !useEmailRegister) return;
|
|
1296
|
+
setError("");
|
|
1297
|
+
setLoading("send-code");
|
|
1298
|
+
try {
|
|
1299
|
+
const apiBase = config.experienceApiBase?.trim();
|
|
1300
|
+
if (!apiBase || !experienceAdapter.sendRegisterEmailCode) {
|
|
1301
|
+
throw new Error(labels.experienceUnavailable);
|
|
1302
|
+
}
|
|
1303
|
+
const email = identifier.trim();
|
|
1304
|
+
const decision = evaluateRegisterEmail(email, emailPolicy);
|
|
1305
|
+
if (!decision.ok) throw new Error(registerEmailRejectionMessage(decision));
|
|
1306
|
+
const captchaToken = getCaptchaToken ? await getCaptchaToken() : void 0;
|
|
1307
|
+
const sent = await experienceAdapter.sendRegisterEmailCode({
|
|
1308
|
+
apiBase,
|
|
1309
|
+
identifier: email,
|
|
1310
|
+
email,
|
|
1311
|
+
captchaToken,
|
|
1312
|
+
issuer: config.issuer,
|
|
1313
|
+
clientId: config.clientId,
|
|
1314
|
+
redirectUri: config.redirectUri,
|
|
1315
|
+
audience: config.audience,
|
|
1316
|
+
scopes: config.scopes,
|
|
1317
|
+
returnUrl
|
|
1318
|
+
});
|
|
1319
|
+
setEmailVerificationId(sent.verificationId);
|
|
1320
|
+
} catch (e) {
|
|
1321
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
1322
|
+
} finally {
|
|
1323
|
+
setLoading(null);
|
|
1324
|
+
}
|
|
1325
|
+
};
|
|
1326
|
+
const switchPanelMode = (next) => {
|
|
1327
|
+
setPanelMode(next);
|
|
1328
|
+
setError("");
|
|
1329
|
+
setConfirmPassword("");
|
|
1330
|
+
setVerificationCode("");
|
|
1331
|
+
setEmailVerificationId(null);
|
|
1332
|
+
};
|
|
904
1333
|
const busy = loading !== null;
|
|
905
1334
|
const showSocial = socialEnabled && connectors.length > 0;
|
|
906
|
-
const showHint = Boolean(labelsProp?.hint) || showSocial;
|
|
1335
|
+
const showHint = Boolean(labelsProp?.hint) || showSocial || isRegister;
|
|
1336
|
+
const formReady = Boolean(identifier.trim()) && Boolean(password) && (!isRegister || Boolean(confirmPassword)) && (!useEmailRegister || Boolean(verificationCode.trim()) && Boolean(emailVerificationId));
|
|
1337
|
+
const titleText = isRegister ? labels.registerTitle : labels.title;
|
|
1338
|
+
const subtitleText = isRegister ? labels.registerSubtitle : labels.subtitle;
|
|
1339
|
+
const identifierPlaceholder = isRegister ? useEmailRegister ? labels.emailPlaceholder : labels.registerIdentifierPlaceholder : labels.identifierPlaceholder;
|
|
1340
|
+
const submitLabel = isRegister ? labels.submitRegister : labels.submitPassword;
|
|
1341
|
+
const hintText = isRegister ? labels.registerHint : labels.hint;
|
|
1342
|
+
const domainsHint = useEmailRegister ? registerEmailDomainsHint(emailPolicy, {
|
|
1343
|
+
allowedDomainsPrefix: labels.allowedDomainsPrefix,
|
|
1344
|
+
allowedDomainsOpen: labels.allowedDomainsOpen,
|
|
1345
|
+
allowedDomainsBlocklist: labels.allowedDomainsBlocklist
|
|
1346
|
+
}) : null;
|
|
907
1347
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx(HeadlessLoginPanel_module_default.panel, className), style: panelStyle, children: [
|
|
908
1348
|
/* @__PURE__ */ jsxRuntime.jsxs("header", { className: HeadlessLoginPanel_module_default.brand, children: [
|
|
909
1349
|
logoSrc ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: logoSrc, alt: "", width: 48, height: 48, className: HeadlessLoginPanel_module_default.logo }) : null,
|
|
910
1350
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
911
1351
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: HeadlessLoginPanel_module_default.product, children: productName }),
|
|
912
|
-
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: HeadlessLoginPanel_module_default.title, children:
|
|
1352
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: HeadlessLoginPanel_module_default.title, children: titleText })
|
|
913
1353
|
] })
|
|
914
1354
|
] }),
|
|
915
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: HeadlessLoginPanel_module_default.subtitle, children:
|
|
1355
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: HeadlessLoginPanel_module_default.subtitle, children: subtitleText }),
|
|
916
1356
|
configured ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
917
1357
|
passwordEnabled ? /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: (e) => void runPassword(e), className: HeadlessLoginPanel_module_default.form, children: [
|
|
1358
|
+
isRegister && emailRegisterEnabled && usernameRegisterEnabled ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: HeadlessLoginPanel_module_default.channelTabs, role: "tablist", "aria-label": "Register method", children: [
|
|
1359
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1360
|
+
"button",
|
|
1361
|
+
{
|
|
1362
|
+
type: "button",
|
|
1363
|
+
role: "tab",
|
|
1364
|
+
className: cx(
|
|
1365
|
+
HeadlessLoginPanel_module_default.channelTab,
|
|
1366
|
+
registerChannel === "email" && HeadlessLoginPanel_module_default.channelTabActive
|
|
1367
|
+
),
|
|
1368
|
+
"aria-selected": registerChannel === "email",
|
|
1369
|
+
disabled: busy,
|
|
1370
|
+
onClick: () => {
|
|
1371
|
+
setRegisterChannel("email");
|
|
1372
|
+
setEmailVerificationId(null);
|
|
1373
|
+
setVerificationCode("");
|
|
1374
|
+
setError("");
|
|
1375
|
+
},
|
|
1376
|
+
children: labels.registerWithEmail
|
|
1377
|
+
}
|
|
1378
|
+
),
|
|
1379
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1380
|
+
"button",
|
|
1381
|
+
{
|
|
1382
|
+
type: "button",
|
|
1383
|
+
role: "tab",
|
|
1384
|
+
className: cx(
|
|
1385
|
+
HeadlessLoginPanel_module_default.channelTab,
|
|
1386
|
+
registerChannel === "username" && HeadlessLoginPanel_module_default.channelTabActive
|
|
1387
|
+
),
|
|
1388
|
+
"aria-selected": registerChannel === "username",
|
|
1389
|
+
disabled: busy,
|
|
1390
|
+
onClick: () => {
|
|
1391
|
+
setRegisterChannel("username");
|
|
1392
|
+
setEmailVerificationId(null);
|
|
1393
|
+
setVerificationCode("");
|
|
1394
|
+
setError("");
|
|
1395
|
+
},
|
|
1396
|
+
children: labels.registerWithUsername
|
|
1397
|
+
}
|
|
1398
|
+
)
|
|
1399
|
+
] }) : null,
|
|
918
1400
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
919
1401
|
"input",
|
|
920
1402
|
{
|
|
921
|
-
type: "text",
|
|
1403
|
+
type: useEmailRegister ? "email" : "text",
|
|
922
1404
|
name: "identifier",
|
|
923
|
-
autoComplete: "username",
|
|
924
|
-
placeholder:
|
|
1405
|
+
autoComplete: isRegister ? "username" : "username",
|
|
1406
|
+
placeholder: identifierPlaceholder,
|
|
925
1407
|
value: identifier,
|
|
926
|
-
onChange: (e) =>
|
|
1408
|
+
onChange: (e) => {
|
|
1409
|
+
setIdentifier(e.target.value);
|
|
1410
|
+
if (useEmailRegister) {
|
|
1411
|
+
setEmailVerificationId(null);
|
|
1412
|
+
}
|
|
1413
|
+
},
|
|
927
1414
|
className: HeadlessLoginPanel_module_default.input,
|
|
928
1415
|
disabled: busy
|
|
929
1416
|
}
|
|
930
1417
|
),
|
|
1418
|
+
domainsHint ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: HeadlessLoginPanel_module_default.domainsHint, "data-testid": "register-allowed-domains", children: domainsHint }) : null,
|
|
1419
|
+
useEmailRegister ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: HeadlessLoginPanel_module_default.codeRow, children: [
|
|
1420
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1421
|
+
"input",
|
|
1422
|
+
{
|
|
1423
|
+
type: "text",
|
|
1424
|
+
name: "verificationCode",
|
|
1425
|
+
inputMode: "numeric",
|
|
1426
|
+
autoComplete: "one-time-code",
|
|
1427
|
+
placeholder: labels.verificationCodePlaceholder,
|
|
1428
|
+
value: verificationCode,
|
|
1429
|
+
onChange: (e) => setVerificationCode(e.target.value),
|
|
1430
|
+
className: cx(HeadlessLoginPanel_module_default.input, HeadlessLoginPanel_module_default.codeInput),
|
|
1431
|
+
disabled: busy
|
|
1432
|
+
}
|
|
1433
|
+
),
|
|
1434
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1435
|
+
"button",
|
|
1436
|
+
{
|
|
1437
|
+
type: "button",
|
|
1438
|
+
className: HeadlessLoginPanel_module_default.secondaryBtn,
|
|
1439
|
+
disabled: busy || !identifier.trim(),
|
|
1440
|
+
onClick: () => void runSendCode(),
|
|
1441
|
+
children: loading === "send-code" ? "\u2026" : emailVerificationId ? labels.resendCode : labels.sendCode
|
|
1442
|
+
}
|
|
1443
|
+
)
|
|
1444
|
+
] }) : null,
|
|
931
1445
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: HeadlessLoginPanel_module_default.passwordField, children: [
|
|
932
1446
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
933
1447
|
"input",
|
|
934
1448
|
{
|
|
935
1449
|
type: passwordVisible ? "text" : "password",
|
|
936
1450
|
name: "password",
|
|
937
|
-
autoComplete: "current-password",
|
|
1451
|
+
autoComplete: isRegister ? "new-password" : "current-password",
|
|
938
1452
|
placeholder: labels.passwordPlaceholder,
|
|
939
1453
|
value: password,
|
|
940
1454
|
onChange: (e) => setPassword(e.target.value),
|
|
@@ -955,7 +1469,20 @@ function HeadlessLoginPanel({
|
|
|
955
1469
|
}
|
|
956
1470
|
)
|
|
957
1471
|
] }),
|
|
958
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
1472
|
+
isRegister ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: HeadlessLoginPanel_module_default.passwordField, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1473
|
+
"input",
|
|
1474
|
+
{
|
|
1475
|
+
type: passwordVisible ? "text" : "password",
|
|
1476
|
+
name: "confirmPassword",
|
|
1477
|
+
autoComplete: "new-password",
|
|
1478
|
+
placeholder: labels.confirmPasswordPlaceholder,
|
|
1479
|
+
value: confirmPassword,
|
|
1480
|
+
onChange: (e) => setConfirmPassword(e.target.value),
|
|
1481
|
+
className: cx(HeadlessLoginPanel_module_default.input, HeadlessLoginPanel_module_default.passwordInput),
|
|
1482
|
+
disabled: busy
|
|
1483
|
+
}
|
|
1484
|
+
) }) : null,
|
|
1485
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", className: HeadlessLoginPanel_module_default.primaryBtn, disabled: busy || !formReady, children: loading === "password" ? "\u2026" : submitLabel })
|
|
959
1486
|
] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: HeadlessLoginPanel_module_default.form, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
960
1487
|
"button",
|
|
961
1488
|
{
|
|
@@ -966,7 +1493,18 @@ function HeadlessLoginPanel({
|
|
|
966
1493
|
children: loading === "sso" ? "\u2026" : labels.submitSso
|
|
967
1494
|
}
|
|
968
1495
|
) }),
|
|
969
|
-
showHint ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: HeadlessLoginPanel_module_default.hint, children:
|
|
1496
|
+
showHint ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: HeadlessLoginPanel_module_default.hint, children: hintText }) : null,
|
|
1497
|
+
registerEnabled && passwordEnabled ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: HeadlessLoginPanel_module_default.switchMode, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1498
|
+
"button",
|
|
1499
|
+
{
|
|
1500
|
+
type: "button",
|
|
1501
|
+
className: HeadlessLoginPanel_module_default.switchModeBtn,
|
|
1502
|
+
disabled: busy,
|
|
1503
|
+
onClick: () => switchPanelMode(isRegister ? "sign-in" : "register"),
|
|
1504
|
+
children: isRegister ? labels.loginLink : labels.registerLink
|
|
1505
|
+
}
|
|
1506
|
+
) }) : null,
|
|
1507
|
+
awaitingExternal ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: HeadlessLoginPanel_module_default.hint, role: "status", children: labels.waitingExternalBrowser }) : null,
|
|
970
1508
|
showSocial ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: HeadlessLoginPanel_module_default.divider, "aria-hidden": true, children: [
|
|
971
1509
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: HeadlessLoginPanel_module_default.dividerLine }),
|
|
972
1510
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: HeadlessLoginPanel_module_default.dividerText, children: labels.socialDivider }),
|
|
@@ -1060,6 +1598,7 @@ function ReauthOverlay({
|
|
|
1060
1598
|
},
|
|
1061
1599
|
returnUrl: resolvedReturnUrl,
|
|
1062
1600
|
mode: "popup",
|
|
1601
|
+
showRegister: false,
|
|
1063
1602
|
showCancel: dismissible,
|
|
1064
1603
|
onCancel: handleCancel,
|
|
1065
1604
|
onOidcSession: handleSession
|
|
@@ -1190,7 +1729,9 @@ function createPostLoginPathHelpers(options) {
|
|
|
1190
1729
|
}
|
|
1191
1730
|
|
|
1192
1731
|
exports.AuthGateProvider = AuthGateProvider;
|
|
1732
|
+
exports.DEFAULT_DISPOSABLE_EMAIL_BLOCKLIST = DEFAULT_DISPOSABLE_EMAIL_BLOCKLIST;
|
|
1193
1733
|
exports.DEFAULT_LOGIN_THEME_COLOR = DEFAULT_LOGIN_THEME_COLOR;
|
|
1734
|
+
exports.DEFAULT_REGISTER_EMAIL_ALLOWLIST = DEFAULT_REGISTER_EMAIL_ALLOWLIST;
|
|
1194
1735
|
exports.HeadlessLoginPanel = HeadlessLoginPanel;
|
|
1195
1736
|
exports.HostedOidcExperienceAdapter = HostedOidcExperienceAdapter;
|
|
1196
1737
|
exports.LOGIN_EXPERIENCE_CAPABILITIES = LOGIN_EXPERIENCE_CAPABILITIES;
|
|
@@ -1203,8 +1744,12 @@ exports.createLoginExperienceAdapter = createLoginExperienceAdapter;
|
|
|
1203
1744
|
exports.createLogtoDirectSignInRequest = createLogtoDirectSignInRequest;
|
|
1204
1745
|
exports.createPostLoginPathHelpers = createPostLoginPathHelpers;
|
|
1205
1746
|
exports.createUserManager = createUserManager;
|
|
1747
|
+
exports.emailDomain = emailDomain;
|
|
1748
|
+
exports.evaluateRegisterEmail = evaluateRegisterEmail;
|
|
1206
1749
|
exports.experiencePasswordSignIn = experiencePasswordSignIn;
|
|
1750
|
+
exports.fetchRegisterEmailPolicy = fetchRegisterEmailPolicy;
|
|
1207
1751
|
exports.fetchSocialConnectors = fetchSocialConnectors;
|
|
1752
|
+
exports.formatAllowedEmailDomains = formatAllowedEmailDomains;
|
|
1208
1753
|
exports.getCurrentUser = getCurrentUser;
|
|
1209
1754
|
exports.getStoredAccessToken = getStoredAccessToken;
|
|
1210
1755
|
exports.handleSignInCallback = handleSignInCallback;
|
|
@@ -1212,13 +1757,19 @@ exports.handleSignInPopupCallback = handleSignInPopupCallback;
|
|
|
1212
1757
|
exports.isIdpConfigured = isIdpConfigured;
|
|
1213
1758
|
exports.isLogtoRegisterUsername = isLogtoRegisterUsername;
|
|
1214
1759
|
exports.isOidcPopupWindow = isOidcPopupWindow;
|
|
1760
|
+
exports.isValidEmailShape = isValidEmailShape;
|
|
1761
|
+
exports.normalizeRegisterEmailMode = normalizeRegisterEmailMode;
|
|
1215
1762
|
exports.prepareSignInRequestUrl = prepareSignInRequestUrl;
|
|
1216
1763
|
exports.readIdpConfigFromEnv = readIdpConfigFromEnv;
|
|
1764
|
+
exports.readRegisterEmailPolicyFromEnv = readRegisterEmailPolicyFromEnv;
|
|
1765
|
+
exports.registerEmailDomainsHint = registerEmailDomainsHint;
|
|
1766
|
+
exports.registerEmailRejectionMessage = registerEmailRejectionMessage;
|
|
1217
1767
|
exports.resetUserManager = resetUserManager;
|
|
1218
1768
|
exports.resolveExperienceApiBase = resolveExperienceApiBase;
|
|
1219
1769
|
exports.resolveIssuer = resolveIssuer;
|
|
1220
1770
|
exports.resolveLoginExperienceAdapter = resolveLoginExperienceAdapter;
|
|
1221
1771
|
exports.resolveLoginExperienceProviderFromEnv = resolveLoginExperienceProviderFromEnv;
|
|
1772
|
+
exports.resolveRegisterEmailPolicy = resolveRegisterEmailPolicy;
|
|
1222
1773
|
exports.sameOriginAuthorizeUrl = sameOriginAuthorizeUrl;
|
|
1223
1774
|
exports.signInPopup = signInPopup;
|
|
1224
1775
|
exports.signInRedirect = signInRedirect;
|