@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/dist/hanko-auth.esm.js +555 -574
- package/dist/hanko-auth.iife.js +17 -17
- package/dist/hanko-auth.umd.js +17 -17
- package/package.json +2 -2
- package/src/hanko-auth.ts +47 -127
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hotosm/hanko-auth",
|
|
3
|
-
"version": "0.
|
|
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": "^
|
|
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
|
-
|
|
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("
|
|
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
|
-
"
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
502
|
+
this.log("Hanko session expired event received");
|
|
525
503
|
this.handleSessionExpired();
|
|
526
504
|
});
|
|
527
505
|
|
|
528
506
|
this._hanko.onUserLoggedOut(() => {
|
|
529
|
-
this.log("
|
|
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("
|
|
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.
|
|
613
|
+
const user = await this._hanko.getCurrentUser();
|
|
629
614
|
this.user = {
|
|
630
|
-
id: user.
|
|
631
|
-
email: user.
|
|
632
|
-
username: user.username,
|
|
633
|
-
emailVerified: user.
|
|
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
|
-
|
|
901
|
-
|
|
902
|
-
|
|
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("
|
|
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("
|
|
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.
|
|
1019
|
+
this._hanko.getCurrentUser(),
|
|
1038
1020
|
timeoutPromise,
|
|
1039
1021
|
])) as any;
|
|
1040
1022
|
this.user = {
|
|
1041
|
-
id: user.
|
|
1042
|
-
email: user.
|
|
1043
|
-
username: user.username,
|
|
1044
|
-
emailVerified: user.
|
|
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
|
-
|
|
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.
|
|
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
|
-
"
|
|
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
|
-
//
|
|
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
|
-
"
|
|
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
|
-
//
|
|
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("
|
|
1673
|
+
this.log("Login URL base:", baseUrl);
|
|
1736
1674
|
|
|
1737
|
-
// Use custom loginUrl
|
|
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}"
|