@authon/js 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -149,10 +149,16 @@ var ModalRenderer = class {
149
149
  selectedWallet = "";
150
150
  overlayEmail = "";
151
151
  overlayError = "";
152
+ // Turnstile CAPTCHA
153
+ captchaSiteKey = "";
154
+ turnstileWidgetId = null;
155
+ turnstileToken = "";
156
+ turnstileWrapper = null;
152
157
  constructor(options) {
153
158
  this.mode = options.mode;
154
159
  this.theme = options.theme || "auto";
155
160
  this.branding = { ...DEFAULT_BRANDING, ...options.branding };
161
+ this.captchaSiteKey = options.captchaSiteKey || "";
156
162
  this.onProviderClick = options.onProviderClick;
157
163
  this.onEmailSubmit = options.onEmailSubmit;
158
164
  this.onClose = options.onClose;
@@ -190,6 +196,15 @@ var ModalRenderer = class {
190
196
  document.removeEventListener("keydown", this.escHandler);
191
197
  this.escHandler = null;
192
198
  }
199
+ if (this.turnstileWidgetId !== null) {
200
+ window.turnstile?.remove(this.turnstileWidgetId);
201
+ this.turnstileWidgetId = null;
202
+ this.turnstileToken = "";
203
+ }
204
+ if (this.turnstileWrapper) {
205
+ this.turnstileWrapper.remove();
206
+ this.turnstileWrapper = null;
207
+ }
193
208
  if (this.hostElement) {
194
209
  this.hostElement.remove();
195
210
  this.hostElement = null;
@@ -200,6 +215,15 @@ var ModalRenderer = class {
200
215
  }
201
216
  this.currentOverlay = "none";
202
217
  }
218
+ getTurnstileToken() {
219
+ return this.turnstileToken;
220
+ }
221
+ resetTurnstile() {
222
+ if (this.turnstileWidgetId !== null) {
223
+ window.turnstile?.reset(this.turnstileWidgetId);
224
+ this.turnstileToken = "";
225
+ }
226
+ }
203
227
  /** Update theme at runtime without destroying form state */
204
228
  setTheme(theme) {
205
229
  this.theme = theme;
@@ -398,10 +422,12 @@ var ModalRenderer = class {
398
422
  </button>`;
399
423
  }).join("") : "";
400
424
  const divider = showProviders && b.showDivider !== false && b.showEmailPassword !== false ? `<div class="divider"><span>or</span></div>` : "";
425
+ const captchaContainer = this.captchaSiteKey ? '<div id="turnstile-container" style="display:flex;justify-content:center;margin:4px 0"></div>' : "";
401
426
  const emailForm = b.showEmailPassword !== false ? `<form class="email-form" id="email-form">
402
427
  <input type="email" placeholder="Email address" name="email" required class="input" autocomplete="email" />
403
428
  <input type="password" placeholder="Password" name="password" required class="input" autocomplete="${isSignUp ? "new-password" : "current-password"}" />
404
429
  ${isSignUp ? '<p class="password-hint">Must contain uppercase, lowercase, and a number (min 8 chars)</p>' : ""}
430
+ ${captchaContainer}
405
431
  <button type="submit" class="submit-btn">${isSignUp ? "Sign up" : "Sign in"}</button>
406
432
  </form>` : "";
407
433
  const hasMethodAbove = showProviders && this.enabledProviders.length > 0 || b.showEmailPassword !== false;
@@ -458,6 +484,58 @@ var ModalRenderer = class {
458
484
  ${b.showSecuredBy !== false ? `<div class="secured-by">Secured by <a href="https://authon.dev" target="_blank" rel="noopener noreferrer" class="secured-link">Authon</a></div>` : ""}
459
485
  `;
460
486
  }
487
+ renderTurnstile() {
488
+ if (!this.captchaSiteKey || !this.shadowRoot) return;
489
+ const anchor = this.shadowRoot.getElementById("turnstile-container");
490
+ if (!anchor) return;
491
+ const w = window;
492
+ const positionWrapper = () => {
493
+ if (!this.turnstileWrapper || !anchor.isConnected) return;
494
+ const rect = anchor.getBoundingClientRect();
495
+ Object.assign(this.turnstileWrapper.style, {
496
+ position: "fixed",
497
+ top: `${rect.top}px`,
498
+ left: `${rect.left}px`,
499
+ width: `${rect.width}px`,
500
+ zIndex: "2147483647",
501
+ display: "flex",
502
+ justifyContent: "center"
503
+ });
504
+ };
505
+ const tryRender = () => {
506
+ if (!w.turnstile || !anchor.isConnected) return;
507
+ this.turnstileWrapper = document.createElement("div");
508
+ document.body.appendChild(this.turnstileWrapper);
509
+ positionWrapper();
510
+ this.turnstileWidgetId = w.turnstile.render(this.turnstileWrapper, {
511
+ sitekey: this.captchaSiteKey,
512
+ callback: (token) => {
513
+ this.turnstileToken = token;
514
+ },
515
+ "expired-callback": () => {
516
+ this.turnstileToken = "";
517
+ },
518
+ "error-callback": () => {
519
+ this.turnstileToken = "";
520
+ },
521
+ theme: this.isDark() ? "dark" : "light",
522
+ size: "flexible"
523
+ });
524
+ window.addEventListener("scroll", positionWrapper, { passive: true });
525
+ window.addEventListener("resize", positionWrapper, { passive: true });
526
+ };
527
+ if (w.turnstile) {
528
+ tryRender();
529
+ } else {
530
+ const interval = setInterval(() => {
531
+ if (w.turnstile) {
532
+ clearInterval(interval);
533
+ tryRender();
534
+ }
535
+ }, 200);
536
+ setTimeout(() => clearInterval(interval), 1e4);
537
+ }
538
+ }
461
539
  isDark() {
462
540
  if (this.theme === "dark") return true;
463
541
  if (this.theme === "light") return false;
@@ -1040,6 +1118,7 @@ var ModalRenderer = class {
1040
1118
  );
1041
1119
  });
1042
1120
  }
1121
+ this.renderTurnstile();
1043
1122
  const backBtn = this.shadowRoot.getElementById("back-btn");
1044
1123
  if (backBtn) {
1045
1124
  backBtn.addEventListener("click", () => {
@@ -1567,6 +1646,8 @@ var Authon = class {
1567
1646
  providers = [];
1568
1647
  providerFlowModes = {};
1569
1648
  initialized = false;
1649
+ captchaEnabled = false;
1650
+ turnstileSiteKey = "";
1570
1651
  constructor(publishableKey, config) {
1571
1652
  this.publishableKey = publishableKey;
1572
1653
  this.config = {
@@ -1601,10 +1682,12 @@ var Authon = class {
1601
1682
  await this.ensureInitialized();
1602
1683
  await this.startOAuthFlow(provider, options);
1603
1684
  }
1604
- async signInWithEmail(email, password) {
1685
+ async signInWithEmail(email, password, turnstileToken) {
1686
+ const body = { email, password };
1687
+ if (turnstileToken) body.turnstileToken = turnstileToken;
1605
1688
  const res = await this.apiPost(
1606
1689
  "/v1/auth/signin",
1607
- { email, password }
1690
+ body
1608
1691
  );
1609
1692
  if (res.mfaRequired && res.mfaToken) {
1610
1693
  this.emit("mfaRequired", res.mfaToken);
@@ -1898,6 +1981,14 @@ var Authon = class {
1898
1981
  this.listeners.clear();
1899
1982
  }
1900
1983
  // ── Internal ──
1984
+ loadTurnstileScript() {
1985
+ if (typeof document === "undefined") return;
1986
+ if (document.querySelector('script[src*="challenges.cloudflare.com/turnstile"]')) return;
1987
+ const script = document.createElement("script");
1988
+ script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
1989
+ script.async = true;
1990
+ document.head.appendChild(script);
1991
+ }
1901
1992
  emit(event, ...args) {
1902
1993
  this.listeners.get(event)?.forEach((fn) => fn(...args));
1903
1994
  }
@@ -1909,6 +2000,11 @@ var Authon = class {
1909
2000
  this.apiGet("/v1/auth/providers")
1910
2001
  ]);
1911
2002
  this.branding = { ...branding, ...this.config.appearance };
2003
+ this.captchaEnabled = !!branding.captchaEnabled;
2004
+ this.turnstileSiteKey = branding.turnstileSiteKey || "";
2005
+ if (this.captchaEnabled && this.turnstileSiteKey) {
2006
+ this.loadTurnstileScript();
2007
+ }
1912
2008
  this.providers = providersRes.providers;
1913
2009
  this.providerFlowModes = {};
1914
2010
  for (const provider of this.providers) {
@@ -1929,11 +2025,14 @@ var Authon = class {
1929
2025
  theme: this.config.theme,
1930
2026
  containerId: this.config.containerId,
1931
2027
  branding: this.branding || void 0,
2028
+ captchaSiteKey: this.captchaEnabled ? this.turnstileSiteKey : void 0,
1932
2029
  onProviderClick: (provider) => this.startOAuthFlow(provider),
1933
2030
  onEmailSubmit: (email, password, isSignUp) => {
1934
2031
  this.modal?.clearError();
1935
- const promise = isSignUp ? this.signUpWithEmail(email, password) : this.signInWithEmail(email, password);
2032
+ const turnstileToken = this.modal?.getTurnstileToken?.() || void 0;
2033
+ const promise = isSignUp ? this.signUpWithEmail(email, password, { turnstileToken }) : this.signInWithEmail(email, password, turnstileToken);
1936
2034
  promise.then(() => this.modal?.close()).catch((err) => {
2035
+ this.modal?.resetTurnstile?.();
1937
2036
  const msg = err instanceof Error ? err.message : String(err);
1938
2037
  this.modal?.showError(msg || "Authentication failed");
1939
2038
  this.emit("error", err instanceof Error ? err : new Error(msg));