@hotosm/hanko-auth 0.4.9 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotosm/hanko-auth",
3
- "version": "0.4.9",
3
+ "version": "0.5.0",
4
4
  "description": "Web component for HOTOSM SSO authentication with Hanko and OSM integration",
5
5
  "type": "module",
6
6
  "main": "dist/hanko-auth.umd.js",
@@ -46,7 +46,7 @@
46
46
  "url": "https://github.com/hotosm/login/issues"
47
47
  },
48
48
  "dependencies": {
49
- "@teamhanko/hanko-elements": "^1.0.0",
49
+ "@teamhanko/hanko-elements": "^2.5.0",
50
50
  "lit": "^3.3.1"
51
51
  },
52
52
  "devDependencies": {
package/src/hanko-auth.ts CHANGED
@@ -1,15 +1,6 @@
1
- /**
2
- * @hotosm/hanko-auth Web Component (Lit Version)
3
- *
4
- * Smart authentication component that handles:
5
- * - Hanko SSO (Google, GitHub, Email)
6
- * - Optional OSM connection
7
- * - Session management
8
- * - Event dispatching
9
- * - URL fallback chain for production builds
10
- */
11
-
12
- import { LitElement, html, css } from "lit";
1
+ /** HOTOSM Hanko auth web component. */
2
+
3
+ import { LitElement, html } from "lit";
13
4
  import { customElement, property, state } from "lit/decorators.js";
14
5
  import { keyed } from "lit/directives/keyed.js";
15
6
  import { register } from "@teamhanko/hanko-elements";
@@ -203,7 +194,6 @@ export class HankoAuth extends LitElement {
203
194
  }
204
195
 
205
196
  // Private fields
206
- private _trailingSlashCache: Record<string, boolean> = {};
207
197
  private _debugMode = false;
208
198
  private _lastSessionId: string | null = null;
209
199
  private _hanko: any = null;
@@ -234,28 +224,28 @@ export class HankoAuth extends LitElement {
234
224
  if (metaTag) {
235
225
  const content = metaTag.getAttribute("content");
236
226
  if (content) {
237
- this.log("🔍 hanko-url auto-detected from <meta> tag:", content);
227
+ this.log("hanko-url auto-detected from <meta> tag:", content);
238
228
  return content;
239
229
  }
240
230
  }
241
231
 
242
232
  if ((window as any).HANKO_URL) {
243
233
  this.log(
244
- "🔍 hanko-url auto-detected from window.HANKO_URL:",
234
+ "hanko-url auto-detected from window.HANKO_URL:",
245
235
  (window as any).HANKO_URL,
246
236
  );
247
237
  return (window as any).HANKO_URL;
248
238
  }
249
239
 
250
240
  const origin = window.location.origin;
251
- this.log("🔍 hanko-url auto-detected from window.location.origin:", origin);
241
+ this.log("hanko-url auto-detected from window.location.origin:", origin);
252
242
  return origin;
253
243
  }
254
244
 
255
245
  connectedCallback() {
256
246
  super.connectedCallback();
257
247
  this._debugMode = this._checkDebugMode();
258
- this.log("🔌 hanko-auth connectedCallback called");
248
+ this.log("hanko-auth connectedCallback called");
259
249
 
260
250
  // Inject Hot styles early, before any Hanko elements render
261
251
  this.injectHotStyles();
@@ -273,7 +263,7 @@ export class HankoAuth extends LitElement {
273
263
 
274
264
  // Use firstUpdated instead of connectedCallback to ensure React props are set
275
265
  firstUpdated() {
276
- this.log("🔌 hanko-auth firstUpdated called");
266
+ this.log("hanko-auth firstUpdated called");
277
267
  this.log(" hankoUrl:", this.hankoUrl);
278
268
  this.log(" basePath:", this.basePath);
279
269
 
@@ -375,7 +365,7 @@ export class HankoAuth extends LitElement {
375
365
  if (!this.showProfile && !this.user) {
376
366
  // Window focused, we're in header mode, and no user is logged in
377
367
  // Re-check session in case user logged in
378
- this.log("🎯 Window focused, re-checking session...");
368
+ this.log("Window focused, re-checking session...");
379
369
  this.checkSession();
380
370
  }
381
371
  };
@@ -387,7 +377,7 @@ export class HankoAuth extends LitElement {
387
377
  const customEvent = event as CustomEvent;
388
378
  if (!this.showProfile && !this.user && customEvent.detail?.user) {
389
379
  // Another component (e.g., login page) logged in
390
- this.log("🔔 External login detected, updating user state...");
380
+ this.log("External login detected, updating user state...");
391
381
  this.user = customEvent.detail.user;
392
382
  this._broadcastState();
393
383
  // Also re-check OSM connection (only if required)
@@ -426,10 +416,6 @@ export class HankoAuth extends LitElement {
426
416
  return langTranslations[key] || translations.en[key] || key;
427
417
  }
428
418
 
429
- private warn(...args: any[]) {
430
- console.warn(...args);
431
- }
432
-
433
419
  private logError(...args: any[]) {
434
420
  console.error(...args);
435
421
  }
@@ -437,24 +423,16 @@ export class HankoAuth extends LitElement {
437
423
  private getBasePath(): string {
438
424
  // Use basePath property directly (works with both attribute and React props)
439
425
  if (this.basePath) {
440
- this.log("🔍 getBasePath() using basePath:", this.basePath);
426
+ this.log("getBasePath() using basePath:", this.basePath);
441
427
  return this.basePath;
442
428
  }
443
429
 
444
430
  // For single-page apps (like Portal), default to empty base path
445
431
  // The authPath already contains the full API path
446
- this.log("🔍 getBasePath() using default: empty string");
432
+ this.log("getBasePath() using default: empty string");
447
433
  return "";
448
434
  }
449
435
 
450
- private addTrailingSlash(path: string, basePath: string): string {
451
- const needsSlash = this._trailingSlashCache[basePath];
452
- if (needsSlash !== undefined && needsSlash && !path.endsWith("/")) {
453
- return path + "/";
454
- }
455
- return path;
456
- }
457
-
458
436
  // styles injected to ensure global availability
459
437
  private injectHotStyles() {
460
438
  const stylesheets = [
@@ -482,7 +460,7 @@ export class HankoAuth extends LitElement {
482
460
  private async init() {
483
461
  // Only primary instance should initialize
484
462
  if (!this._isPrimary) {
485
- this.log("⏭️ Not primary, skipping init...");
463
+ this.log("Not primary, skipping init...");
486
464
  return;
487
465
  }
488
466
 
@@ -521,12 +499,12 @@ export class HankoAuth extends LitElement {
521
499
 
522
500
  // Set up session lifecycle event listeners (these persist across the component lifecycle)
523
501
  this._hanko.onSessionExpired(() => {
524
- this.log("🕒 Hanko session expired event received");
502
+ this.log("Hanko session expired event received");
525
503
  this.handleSessionExpired();
526
504
  });
527
505
 
528
506
  this._hanko.onUserLoggedOut(() => {
529
- this.log("🚪 Hanko user logged out event received");
507
+ this.log("Hanko user logged out event received");
530
508
  this.handleUserLoggedOut();
531
509
  });
532
510
 
@@ -553,7 +531,7 @@ export class HankoAuth extends LitElement {
553
531
  }
554
532
 
555
533
  private async checkSession() {
556
- this.log("🔍 Checking for existing Hanko session...");
534
+ this.log("Checking for existing Hanko session...");
557
535
 
558
536
  if (!this._hanko) {
559
537
  this.log("Hanko instance not initialized yet");
@@ -585,6 +563,13 @@ export class HankoAuth extends LitElement {
585
563
  this.log(
586
564
  "Session validation returned is_valid:false - no valid session",
587
565
  );
566
+ if (this.user) {
567
+ this.user = null;
568
+ this.profilePictureUrl = "";
569
+ this.dispatchEvent(
570
+ new CustomEvent("logout", { bubbles: true, composed: true }),
571
+ );
572
+ }
588
573
  return;
589
574
  }
590
575
 
@@ -625,12 +610,12 @@ export class HankoAuth extends LitElement {
625
610
  if (needsSdkFallback) {
626
611
  this.log("Using SDK to get user with email");
627
612
  // Fallback to SDK method which has email
628
- const user = await this._hanko.user.getCurrent();
613
+ const user = await this._hanko.getCurrentUser();
629
614
  this.user = {
630
- id: user.id,
631
- email: user.email,
632
- username: user.username,
633
- emailVerified: user.email_verified || false,
615
+ id: user.user_id,
616
+ email: user.emails?.[0]?.address || null,
617
+ username: user.username?.username || null,
618
+ emailVerified: user.emails?.[0]?.is_verified || false,
634
619
  };
635
620
  }
636
621
  } catch (userError) {
@@ -897,12 +882,9 @@ export class HankoAuth extends LitElement {
897
882
  this.log("Display name set to:", this.profileDisplayName);
898
883
  }
899
884
 
900
- // picture_url is always set by the backend (Gravatar fallback); osm_avatar_url as secondary
901
- const picUrl = profile.picture_url || profile.osm_avatar_url;
902
- if (picUrl) {
903
- this.profilePictureUrl = picUrl;
904
- this.log("Profile picture set to:", this.profilePictureUrl);
905
- }
885
+ const picUrl = profile.osm_avatar_url || profile.picture_url;
886
+ this.profilePictureUrl = picUrl || "";
887
+ this.log("Profile picture set to:", this.profilePictureUrl);
906
888
 
907
889
  // Set language from user profile if available
908
890
  if (profile.language) {
@@ -927,7 +909,7 @@ export class HankoAuth extends LitElement {
927
909
  this.user === null &&
928
910
  this.showProfile
929
911
  ) {
930
- this.log("🔄 User logged out, re-attaching event listeners...");
912
+ this.log("User logged out, re-attaching event listeners...");
931
913
  this._currentHankoAuthElement = null;
932
914
  this.setupEventListeners();
933
915
  }
@@ -946,7 +928,7 @@ export class HankoAuth extends LitElement {
946
928
 
947
929
  if (hankoAuth) {
948
930
  this._currentHankoAuthElement = hankoAuth;
949
- this.log("🎯 Attaching event listeners to hanko-auth element");
931
+ this.log("Attaching event listeners to hanko-auth element");
950
932
 
951
933
  hankoAuth.addEventListener("onSessionCreated", (e: any) => {
952
934
  this.log(`Hanko event: onSessionCreated`, e.detail);
@@ -1034,14 +1016,14 @@ export class HankoAuth extends LitElement {
1034
1016
  setTimeout(() => reject(new Error("SDK timeout")), 5000),
1035
1017
  );
1036
1018
  const user = (await Promise.race([
1037
- this._hanko.user.getCurrent(),
1019
+ this._hanko.getCurrentUser(),
1038
1020
  timeoutPromise,
1039
1021
  ])) as any;
1040
1022
  this.user = {
1041
- id: user.id,
1042
- email: user.email,
1043
- username: user.username,
1044
- emailVerified: user.email_verified || false,
1023
+ id: user.user_id,
1024
+ email: user.emails?.[0]?.address || null,
1025
+ username: user.username?.username || null,
1026
+ emailVerified: user.emails?.[0]?.is_verified || false,
1045
1027
  };
1046
1028
  userInfoRetrieved = true;
1047
1029
  this.log("User info retrieved via SDK fallback");
@@ -1168,7 +1150,7 @@ export class HankoAuth extends LitElement {
1168
1150
  // This is a redirect response
1169
1151
  const redirectUrl = response.headers.get("Location") || response.url;
1170
1152
  this.log("Got redirect URL:", redirectUrl);
1171
- window.location.href = redirectUrl;
1153
+ window.location.href = redirectUrl;
1172
1154
  } else if (response.status >= 300 && response.status < 400) {
1173
1155
  const redirectUrl = response.headers.get("Location");
1174
1156
  this.log("Got redirect URL from header:", redirectUrl);
@@ -1219,7 +1201,7 @@ export class HankoAuth extends LitElement {
1219
1201
 
1220
1202
  if (this._hanko) {
1221
1203
  try {
1222
- await this._hanko.user.logout();
1204
+ await this._hanko.logout();
1223
1205
  this.log("Hanko logout successful");
1224
1206
  } catch (error) {
1225
1207
  this.logError("Hanko logout failed:", error);
@@ -1386,47 +1368,9 @@ export class HankoAuth extends LitElement {
1386
1368
  // Close dropdown after selection
1387
1369
  this.closeDropdown();
1388
1370
  }
1389
- private oldHandleDropdownSelect(event: CustomEvent) {
1390
- const selectedValue = event.detail.item.value;
1391
- this.log("🎯 Dropdown item selected:", selectedValue);
1392
-
1393
- if (selectedValue === "profile") {
1394
- // Profile page: standalone apps have their own, others use central login service
1395
- // loginUrl already includes /app, hankoUrl doesn't
1396
- const returnTo = this.redirectAfterLogin || window.location.origin;
1397
- const profileUrl = this.loginUrl
1398
- ? `${this.loginUrl}/profile`
1399
- : `${this.hankoUrl}/app/profile`;
1400
- window.location.href = `${profileUrl}?return_to=${encodeURIComponent(returnTo)}`;
1401
- } else if (selectedValue === "connect-osm") {
1402
- // Smart return_to: if already on a login page, redirect to home instead
1403
- const currentPath = window.location.pathname;
1404
- const isOnLoginPage = currentPath.includes("/app");
1405
- const returnTo = isOnLoginPage
1406
- ? window.location.origin
1407
- : window.location.href;
1408
-
1409
- // Use the getter which handles all fallbacks correctly
1410
- const baseUrl = this.hankoUrl;
1411
- window.location.href = `${baseUrl}/app?return_to=${encodeURIComponent(
1412
- returnTo,
1413
- )}&osm_required=true`;
1414
- } else if (selectedValue === "logout") {
1415
- this.handleLogout();
1416
- }
1417
- }
1418
-
1419
- private handleSkipOSM() {
1420
- this.dispatchEvent(new CustomEvent("osm-skipped"));
1421
- this.dispatchEvent(new CustomEvent("auth-complete"));
1422
- if (this.redirectAfterLogin) {
1423
- window.location.href = this.redirectAfterLogin;
1424
- }
1425
- }
1426
-
1427
1371
  render() {
1428
1372
  this.log(
1429
- "🎨 RENDER - showProfile:",
1373
+ "RENDER - showProfile:",
1430
1374
  this.showProfile,
1431
1375
  "user:",
1432
1376
  !!this.user,
@@ -1464,8 +1408,6 @@ export class HankoAuth extends LitElement {
1464
1408
  const initial = displayName ? displayName[0].toUpperCase() : "U";
1465
1409
 
1466
1410
  if (this.showProfile) {
1467
- // Show full profile view
1468
- // TODO check use cases
1469
1411
  return html`
1470
1412
  <div class="container">
1471
1413
  <div class="profile">
@@ -1672,13 +1614,12 @@ export class HankoAuth extends LitElement {
1672
1614
  `;
1673
1615
  }
1674
1616
  } else {
1675
- // Not logged in
1617
+ // Not logged in.
1676
1618
  if (this.showProfile) {
1677
- // On login page - show full Hanko auth form
1678
- // Don't render until Hanko is registered to prevent 404 errors
1619
+ // Login page mode: render full Hanko form after registration is ready.
1679
1620
  if (!this.hankoReady) {
1680
1621
  this.log(
1681
- "Waiting for Hanko registration before rendering form...",
1622
+ "Waiting for Hanko registration before rendering form...",
1682
1623
  );
1683
1624
  return html`<span class="loading-placeholder"
1684
1625
  ><span class="loading-placeholder-text">${this.t("logIn")}</span
@@ -1717,9 +1658,7 @@ export class HankoAuth extends LitElement {
1717
1658
  </div>
1718
1659
  `;
1719
1660
  } else {
1720
- // In header - show login link
1721
- // Use redirectAfterLogin if set, otherwise use current URL
1722
- // Smart return_to: if already on a login page, redirect to home instead
1661
+ // Header mode: render login link with a safe return_to target.
1723
1662
  const currentPath = window.location.pathname;
1724
1663
  const isOnLoginPage = currentPath.includes("/app");
1725
1664
  const returnTo =
@@ -1730,34 +1669,15 @@ export class HankoAuth extends LitElement {
1730
1669
  const autoConnectParam =
1731
1670
  urlParams.get("auto_connect") === "true" ? "&auto_connect=true" : "";
1732
1671
 
1733
- // Use the getter which handles all fallbacks correctly
1734
1672
  const baseUrl = this.hankoUrl;
1735
- this.log("🔗 Login URL base:", baseUrl);
1673
+ this.log("Login URL base:", baseUrl);
1736
1674
 
1737
- // Use custom loginUrl if provided (for standalone mode), otherwise use ${hankoUrl}/app
1675
+ // Use custom loginUrl when provided; fallback to {hankoUrl}/app.
1738
1676
  const loginBase = this.loginUrl || `${baseUrl}/app`;
1739
1677
  const loginUrl = `${loginBase}?return_to=${encodeURIComponent(
1740
1678
  returnTo,
1741
1679
  )}${this.osmRequired ? "&osm_required=true" : ""}${autoConnectParam}&lang=${this.lang}`;
1742
1680
 
1743
- /* if (this.display === "bar") {
1744
- return html`<a
1745
- class="bar-trigger login-link ${this.buttonVariant} ${this.buttonColor}"
1746
- href="${loginUrl}"
1747
- @click=${(e: Event) => {
1748
- e.preventDefault();
1749
- window.location.href = loginUrl;
1750
- }}
1751
- >
1752
- <span class="bar-email">${this.t("logIn")}</span>
1753
- <img
1754
- src="${chevronDownIcon}"
1755
- class="bar-chevron"
1756
- alt=""
1757
- />
1758
- </a>`;
1759
- } */
1760
-
1761
1681
  return html`<a
1762
1682
  class="login-link ${this.buttonVariant} ${this.buttonColor}"
1763
1683
  href="${loginUrl}"