@awesome-lang-auth/node 1.10.1

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.
Files changed (262) hide show
  1. package/LICENSE +21 -0
  2. package/README.detailed.md +4059 -0
  3. package/README.md +248 -0
  4. package/dist/abstract/base-auth-strategy.abstract.d.ts +7 -0
  5. package/dist/abstract/base-auth-strategy.abstract.d.ts.map +1 -0
  6. package/dist/abstract/base-auth-strategy.abstract.js +7 -0
  7. package/dist/abstract/base-auth-strategy.abstract.js.map +1 -0
  8. package/dist/abstract/base-oauth-strategy.abstract.d.ts +27 -0
  9. package/dist/abstract/base-oauth-strategy.abstract.d.ts.map +1 -0
  10. package/dist/abstract/base-oauth-strategy.abstract.js +11 -0
  11. package/dist/abstract/base-oauth-strategy.abstract.js.map +1 -0
  12. package/dist/adapters/express.d.ts +45 -0
  13. package/dist/adapters/express.d.ts.map +1 -0
  14. package/dist/adapters/express.js +49 -0
  15. package/dist/adapters/express.js.map +1 -0
  16. package/dist/adapters/fastify.d.ts +72 -0
  17. package/dist/adapters/fastify.d.ts.map +1 -0
  18. package/dist/adapters/fastify.js +63 -0
  19. package/dist/adapters/fastify.js.map +1 -0
  20. package/dist/auth-configurator.d.ts +65 -0
  21. package/dist/auth-configurator.d.ts.map +1 -0
  22. package/dist/auth-configurator.js +127 -0
  23. package/dist/auth-configurator.js.map +1 -0
  24. package/dist/events/auth-event-bus.d.ts +68 -0
  25. package/dist/events/auth-event-bus.d.ts.map +1 -0
  26. package/dist/events/auth-event-bus.js +60 -0
  27. package/dist/events/auth-event-bus.js.map +1 -0
  28. package/dist/events/auth-event-names.d.ts +34 -0
  29. package/dist/events/auth-event-names.d.ts.map +1 -0
  30. package/dist/events/auth-event-names.js +41 -0
  31. package/dist/events/auth-event-names.js.map +1 -0
  32. package/dist/http-types.d.ts +135 -0
  33. package/dist/http-types.d.ts.map +1 -0
  34. package/dist/http-types.js +18 -0
  35. package/dist/http-types.js.map +1 -0
  36. package/dist/index.d.ts +80 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +88 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/interfaces/api-key-store.interface.d.ts +94 -0
  41. package/dist/interfaces/api-key-store.interface.d.ts.map +1 -0
  42. package/dist/interfaces/api-key-store.interface.js +3 -0
  43. package/dist/interfaces/api-key-store.interface.js.map +1 -0
  44. package/dist/interfaces/auth-strategy.interface.d.ts +6 -0
  45. package/dist/interfaces/auth-strategy.interface.d.ts.map +1 -0
  46. package/dist/interfaces/auth-strategy.interface.js +3 -0
  47. package/dist/interfaces/auth-strategy.interface.js.map +1 -0
  48. package/dist/interfaces/linked-accounts-store.interface.d.ts +74 -0
  49. package/dist/interfaces/linked-accounts-store.interface.d.ts.map +1 -0
  50. package/dist/interfaces/linked-accounts-store.interface.js +3 -0
  51. package/dist/interfaces/linked-accounts-store.interface.js.map +1 -0
  52. package/dist/interfaces/pending-link-store.interface.d.ts +77 -0
  53. package/dist/interfaces/pending-link-store.interface.d.ts.map +1 -0
  54. package/dist/interfaces/pending-link-store.interface.js +3 -0
  55. package/dist/interfaces/pending-link-store.interface.js.map +1 -0
  56. package/dist/interfaces/roles-permissions-store.interface.d.ts +129 -0
  57. package/dist/interfaces/roles-permissions-store.interface.d.ts.map +1 -0
  58. package/dist/interfaces/roles-permissions-store.interface.js +3 -0
  59. package/dist/interfaces/roles-permissions-store.interface.js.map +1 -0
  60. package/dist/interfaces/session-store.interface.d.ts +92 -0
  61. package/dist/interfaces/session-store.interface.d.ts.map +1 -0
  62. package/dist/interfaces/session-store.interface.js +3 -0
  63. package/dist/interfaces/session-store.interface.js.map +1 -0
  64. package/dist/interfaces/settings-store.interface.d.ts +91 -0
  65. package/dist/interfaces/settings-store.interface.d.ts.map +1 -0
  66. package/dist/interfaces/settings-store.interface.js +3 -0
  67. package/dist/interfaces/settings-store.interface.js.map +1 -0
  68. package/dist/interfaces/sse-distributor.interface.d.ts +25 -0
  69. package/dist/interfaces/sse-distributor.interface.d.ts.map +1 -0
  70. package/dist/interfaces/sse-distributor.interface.js +3 -0
  71. package/dist/interfaces/sse-distributor.interface.js.map +1 -0
  72. package/dist/interfaces/telemetry-store.interface.d.ts +72 -0
  73. package/dist/interfaces/telemetry-store.interface.d.ts.map +1 -0
  74. package/dist/interfaces/telemetry-store.interface.js +3 -0
  75. package/dist/interfaces/telemetry-store.interface.js.map +1 -0
  76. package/dist/interfaces/template-store.interface.d.ts +37 -0
  77. package/dist/interfaces/template-store.interface.d.ts.map +1 -0
  78. package/dist/interfaces/template-store.interface.js +3 -0
  79. package/dist/interfaces/template-store.interface.js.map +1 -0
  80. package/dist/interfaces/tenant-store.interface.d.ts +97 -0
  81. package/dist/interfaces/tenant-store.interface.d.ts.map +1 -0
  82. package/dist/interfaces/tenant-store.interface.js +3 -0
  83. package/dist/interfaces/tenant-store.interface.js.map +1 -0
  84. package/dist/interfaces/token-store.interface.d.ts +10 -0
  85. package/dist/interfaces/token-store.interface.d.ts.map +1 -0
  86. package/dist/interfaces/token-store.interface.js +3 -0
  87. package/dist/interfaces/token-store.interface.js.map +1 -0
  88. package/dist/interfaces/user-metadata-store.interface.d.ts +50 -0
  89. package/dist/interfaces/user-metadata-store.interface.d.ts.map +1 -0
  90. package/dist/interfaces/user-metadata-store.interface.js +3 -0
  91. package/dist/interfaces/user-metadata-store.interface.js.map +1 -0
  92. package/dist/interfaces/user-store.interface.d.ts +113 -0
  93. package/dist/interfaces/user-store.interface.d.ts.map +1 -0
  94. package/dist/interfaces/user-store.interface.js +3 -0
  95. package/dist/interfaces/user-store.interface.js.map +1 -0
  96. package/dist/interfaces/webhook-store.interface.d.ts +139 -0
  97. package/dist/interfaces/webhook-store.interface.d.ts.map +1 -0
  98. package/dist/interfaces/webhook-store.interface.js +3 -0
  99. package/dist/interfaces/webhook-store.interface.js.map +1 -0
  100. package/dist/middleware/api-key.middleware.d.ts +35 -0
  101. package/dist/middleware/api-key.middleware.d.ts.map +1 -0
  102. package/dist/middleware/api-key.middleware.js +45 -0
  103. package/dist/middleware/api-key.middleware.js.map +1 -0
  104. package/dist/middleware/auth.middleware.d.ts +13 -0
  105. package/dist/middleware/auth.middleware.d.ts.map +1 -0
  106. package/dist/middleware/auth.middleware.js +55 -0
  107. package/dist/middleware/auth.middleware.js.map +1 -0
  108. package/dist/middleware/jwks-auth.middleware.d.ts +18 -0
  109. package/dist/middleware/jwks-auth.middleware.d.ts.map +1 -0
  110. package/dist/middleware/jwks-auth.middleware.js +77 -0
  111. package/dist/middleware/jwks-auth.middleware.js.map +1 -0
  112. package/dist/models/api-key.model.d.ts +66 -0
  113. package/dist/models/api-key.model.d.ts.map +1 -0
  114. package/dist/models/api-key.model.js +3 -0
  115. package/dist/models/api-key.model.js.map +1 -0
  116. package/dist/models/auth-config.model.d.ts +387 -0
  117. package/dist/models/auth-config.model.d.ts.map +1 -0
  118. package/dist/models/auth-config.model.js +3 -0
  119. package/dist/models/auth-config.model.js.map +1 -0
  120. package/dist/models/errors.d.ts +7 -0
  121. package/dist/models/errors.d.ts.map +1 -0
  122. package/dist/models/errors.js +14 -0
  123. package/dist/models/errors.js.map +1 -0
  124. package/dist/models/session.model.d.ts +28 -0
  125. package/dist/models/session.model.d.ts.map +1 -0
  126. package/dist/models/session.model.js +3 -0
  127. package/dist/models/session.model.js.map +1 -0
  128. package/dist/models/tenant.model.d.ts +20 -0
  129. package/dist/models/tenant.model.d.ts.map +1 -0
  130. package/dist/models/tenant.model.js +3 -0
  131. package/dist/models/tenant.model.js.map +1 -0
  132. package/dist/models/token.model.d.ts +21 -0
  133. package/dist/models/token.model.d.ts.map +1 -0
  134. package/dist/models/token.model.js +3 -0
  135. package/dist/models/token.model.js.map +1 -0
  136. package/dist/models/user.model.d.ts +92 -0
  137. package/dist/models/user.model.d.ts.map +1 -0
  138. package/dist/models/user.model.js +3 -0
  139. package/dist/models/user.model.js.map +1 -0
  140. package/dist/router/admin.router.d.ts +188 -0
  141. package/dist/router/admin.router.d.ts.map +1 -0
  142. package/dist/router/admin.router.js +1507 -0
  143. package/dist/router/admin.router.js.map +1 -0
  144. package/dist/router/auth.router.d.ts +199 -0
  145. package/dist/router/auth.router.d.ts.map +1 -0
  146. package/dist/router/auth.router.js +1636 -0
  147. package/dist/router/auth.router.js.map +1 -0
  148. package/dist/router/openapi.d.ts +98 -0
  149. package/dist/router/openapi.d.ts.map +1 -0
  150. package/dist/router/openapi.js +1518 -0
  151. package/dist/router/openapi.js.map +1 -0
  152. package/dist/router/router-events.d.ts +38 -0
  153. package/dist/router/router-events.d.ts.map +1 -0
  154. package/dist/router/router-events.js +74 -0
  155. package/dist/router/router-events.js.map +1 -0
  156. package/dist/router/tools.router.d.ts +104 -0
  157. package/dist/router/tools.router.d.ts.map +1 -0
  158. package/dist/router/tools.router.js +227 -0
  159. package/dist/router/tools.router.js.map +1 -0
  160. package/dist/router/ui.router.d.ts +49 -0
  161. package/dist/router/ui.router.d.ts.map +1 -0
  162. package/dist/router/ui.router.js +272 -0
  163. package/dist/router/ui.router.js.map +1 -0
  164. package/dist/services/api-key.service.d.ts +53 -0
  165. package/dist/services/api-key.service.d.ts.map +1 -0
  166. package/dist/services/api-key.service.js +66 -0
  167. package/dist/services/api-key.service.js.map +1 -0
  168. package/dist/services/jwks.service.d.ts +73 -0
  169. package/dist/services/jwks.service.d.ts.map +1 -0
  170. package/dist/services/jwks.service.js +174 -0
  171. package/dist/services/jwks.service.js.map +1 -0
  172. package/dist/services/mailer.service.d.ts +30 -0
  173. package/dist/services/mailer.service.d.ts.map +1 -0
  174. package/dist/services/mailer.service.js +248 -0
  175. package/dist/services/mailer.service.js.map +1 -0
  176. package/dist/services/notification.service.d.ts +110 -0
  177. package/dist/services/notification.service.d.ts.map +1 -0
  178. package/dist/services/notification.service.js +79 -0
  179. package/dist/services/notification.service.js.map +1 -0
  180. package/dist/services/password.service.d.ts +5 -0
  181. package/dist/services/password.service.d.ts.map +1 -0
  182. package/dist/services/password.service.js +17 -0
  183. package/dist/services/password.service.js.map +1 -0
  184. package/dist/services/sms.service.d.ts +14 -0
  185. package/dist/services/sms.service.d.ts.map +1 -0
  186. package/dist/services/sms.service.js +51 -0
  187. package/dist/services/sms.service.js.map +1 -0
  188. package/dist/services/token.service.d.ts +62 -0
  189. package/dist/services/token.service.d.ts.map +1 -0
  190. package/dist/services/token.service.js +354 -0
  191. package/dist/services/token.service.js.map +1 -0
  192. package/dist/stores/memory-template.store.d.ts +12 -0
  193. package/dist/stores/memory-template.store.d.ts.map +1 -0
  194. package/dist/stores/memory-template.store.js +35 -0
  195. package/dist/stores/memory-template.store.js.map +1 -0
  196. package/dist/strategies/api-key/api-key.strategy.d.ts +72 -0
  197. package/dist/strategies/api-key/api-key.strategy.d.ts.map +1 -0
  198. package/dist/strategies/api-key/api-key.strategy.js +182 -0
  199. package/dist/strategies/api-key/api-key.strategy.js.map +1 -0
  200. package/dist/strategies/local/local.strategy.d.ts +19 -0
  201. package/dist/strategies/local/local.strategy.d.ts.map +1 -0
  202. package/dist/strategies/local/local.strategy.js +45 -0
  203. package/dist/strategies/local/local.strategy.js.map +1 -0
  204. package/dist/strategies/magic-link/magic-link.strategy.d.ts +8 -0
  205. package/dist/strategies/magic-link/magic-link.strategy.d.ts.map +1 -0
  206. package/dist/strategies/magic-link/magic-link.strategy.js +53 -0
  207. package/dist/strategies/magic-link/magic-link.strategy.js.map +1 -0
  208. package/dist/strategies/oauth/generic-oauth.strategy.d.ts +120 -0
  209. package/dist/strategies/oauth/generic-oauth.strategy.d.ts.map +1 -0
  210. package/dist/strategies/oauth/generic-oauth.strategy.js +88 -0
  211. package/dist/strategies/oauth/generic-oauth.strategy.js.map +1 -0
  212. package/dist/strategies/oauth/github.strategy.d.ts +31 -0
  213. package/dist/strategies/oauth/github.strategy.d.ts.map +1 -0
  214. package/dist/strategies/oauth/github.strategy.js +72 -0
  215. package/dist/strategies/oauth/github.strategy.js.map +1 -0
  216. package/dist/strategies/oauth/google.strategy.d.ts +31 -0
  217. package/dist/strategies/oauth/google.strategy.d.ts.map +1 -0
  218. package/dist/strategies/oauth/google.strategy.js +61 -0
  219. package/dist/strategies/oauth/google.strategy.js.map +1 -0
  220. package/dist/strategies/sms/sms.strategy.d.ts +7 -0
  221. package/dist/strategies/sms/sms.strategy.d.ts.map +1 -0
  222. package/dist/strategies/sms/sms.strategy.js +39 -0
  223. package/dist/strategies/sms/sms.strategy.js.map +1 -0
  224. package/dist/strategies/two-factor/totp.strategy.d.ts +12 -0
  225. package/dist/strategies/two-factor/totp.strategy.d.ts.map +1 -0
  226. package/dist/strategies/two-factor/totp.strategy.js +32 -0
  227. package/dist/strategies/two-factor/totp.strategy.js.map +1 -0
  228. package/dist/tools/auth-tools.d.ts +200 -0
  229. package/dist/tools/auth-tools.d.ts.map +1 -0
  230. package/dist/tools/auth-tools.js +232 -0
  231. package/dist/tools/auth-tools.js.map +1 -0
  232. package/dist/tools/sse-manager.d.ts +118 -0
  233. package/dist/tools/sse-manager.d.ts.map +1 -0
  234. package/dist/tools/sse-manager.js +163 -0
  235. package/dist/tools/sse-manager.js.map +1 -0
  236. package/dist/tools/sse-notify.decorator.d.ts +74 -0
  237. package/dist/tools/sse-notify.decorator.d.ts.map +1 -0
  238. package/dist/tools/sse-notify.decorator.js +78 -0
  239. package/dist/tools/sse-notify.decorator.js.map +1 -0
  240. package/dist/tools/webhook-action.d.ts +130 -0
  241. package/dist/tools/webhook-action.d.ts.map +1 -0
  242. package/dist/tools/webhook-action.js +144 -0
  243. package/dist/tools/webhook-action.js.map +1 -0
  244. package/dist/tools/webhook-sender.d.ts +31 -0
  245. package/dist/tools/webhook-sender.d.ts.map +1 -0
  246. package/dist/tools/webhook-sender.js +80 -0
  247. package/dist/tools/webhook-sender.js.map +1 -0
  248. package/dist/ui-assets/2fa.html +115 -0
  249. package/dist/ui-assets/account-conflict.html +108 -0
  250. package/dist/ui-assets/admin.css +263 -0
  251. package/dist/ui-assets/admin.js +1927 -0
  252. package/dist/ui-assets/auth.js +725 -0
  253. package/dist/ui-assets/base.css +219 -0
  254. package/dist/ui-assets/forgot-password.html +93 -0
  255. package/dist/ui-assets/link-verify.html +74 -0
  256. package/dist/ui-assets/login.html +135 -0
  257. package/dist/ui-assets/magic-link.html +112 -0
  258. package/dist/ui-assets/register.html +122 -0
  259. package/dist/ui-assets/reset-password.html +103 -0
  260. package/dist/ui-assets/ui-i18n-keys.json +78 -0
  261. package/dist/ui-assets/verify-email.html +66 -0
  262. package/package.json +89 -0
@@ -0,0 +1,725 @@
1
+ /**
2
+ * Universal Authentication Service & Utility Wrapper for Vanilla JS UI
3
+ * Combines global fetch interception with the internal AuthService API used by the UI.
4
+ * Extensible for single-page applications via AwesomeNodeAuth.init(options).
5
+ */
6
+ (function () {
7
+ // --- STATE ---
8
+ let refreshInProgress = null;
9
+ let isAuthenticated = false;
10
+ let isInitialized = false;
11
+ let currentUser = null;
12
+
13
+ // --- DEFAULT CONFIG ---
14
+ // apiPrefix derivato automaticamente dal pathname se incluso nelle pagine UI
15
+ let defaultPrefix = window.location.pathname.includes('/ui/')
16
+ ? window.location.pathname.split('/ui/')[0]
17
+ : '/auth';
18
+
19
+ let UI_CONFIG = {
20
+ apiPrefix: defaultPrefix,
21
+ loginUrl: defaultPrefix + '/ui/login',
22
+ homeUrl: '/',
23
+ siteName: 'Awesome Node Auth',
24
+ features: {}
25
+ };
26
+
27
+ // --- OVERRIDES ---
28
+ // Metodi overridabili tramite AwesomeNodeAuth.init()
29
+ let _overrides = {
30
+ login: null,
31
+ logout: null,
32
+ register: null,
33
+ forgotPassword: null,
34
+ resetPassword: null,
35
+ changePassword: null,
36
+ setPassword: null,
37
+ sendMagicLink: null,
38
+ verifyMagicLink: null,
39
+ setup2fa: null,
40
+ verify2faSetup: null,
41
+ validate2fa: null,
42
+ sendSmsLogin: null,
43
+ verifySmsLogin: null,
44
+ validateSms: null,
45
+ resendVerificationEmail: null,
46
+ verifyEmail: null,
47
+ requestEmailChange: null,
48
+ confirmEmailChange: null,
49
+ requestLinkingEmail: null,
50
+ verifyLinkingToken: null,
51
+ verifyConflictLinkingToken: null,
52
+ getLinkedAccounts: null,
53
+ unlinkAccount: null,
54
+ deleteAccount: null,
55
+ // hooks lifecycle
56
+ onLogout: null,
57
+ onSessionExpired: null,
58
+ onRefreshSuccess: null,
59
+ onRefreshFail: null,
60
+ };
61
+
62
+ // --- CSRF ---
63
+ function getCookie(name) {
64
+ const match = document.cookie.match(new RegExp('(^|;\\s*)' + name + '=([^;]*)'));
65
+ return match ? decodeURIComponent(match[2]) : null;
66
+ }
67
+
68
+ function addCsrfHeader(headers = {}) {
69
+ const token = getCookie('__Host-csrf-token') || getCookie('__Secure-csrf-token') || getCookie('csrf-token');
70
+ if (token) headers['X-CSRF-Token'] = token;
71
+ return headers;
72
+ }
73
+
74
+ // --- UTIL ---
75
+ function isAuthEndpoint(url) {
76
+ if (typeof url !== 'string') return false;
77
+ return [
78
+ `${UI_CONFIG.apiPrefix}/login`,
79
+ `${UI_CONFIG.apiPrefix}/logout`,
80
+ `${UI_CONFIG.apiPrefix}/refresh`,
81
+ `${UI_CONFIG.apiPrefix}/me`
82
+ ].some(e => url.includes(e));
83
+ }
84
+
85
+ function getLoginUrl() {
86
+ return UI_CONFIG.loginUrl || `${UI_CONFIG.apiPrefix}/ui/login`;
87
+ }
88
+
89
+ // --- REFRESH ---
90
+ function refreshToken() {
91
+ if (refreshInProgress) return refreshInProgress;
92
+ refreshInProgress = originalFetch(`${UI_CONFIG.apiPrefix}/refresh`, {
93
+ method: 'POST',
94
+ credentials: 'include',
95
+ headers: addCsrfHeader({ 'Content-Type': 'application/json' })
96
+ })
97
+ .then(r => r.json())
98
+ .finally(() => { refreshInProgress = null; });
99
+ return refreshInProgress;
100
+ }
101
+
102
+ // --- FETCH INTERCEPTOR ---
103
+ const originalFetch = window.fetch;
104
+ window.fetch = async function (input, init = {}) {
105
+ const url = typeof input === 'string' ? input : input.url;
106
+
107
+ // Derive the auth backend origin so that every request to the same
108
+ // domain as the auth server gets credentials/CSRF headers — not just
109
+ // requests whose path starts with apiPrefix. This covers routes like
110
+ // /mcp on the same host as /auth (cross-domain headless deployments).
111
+ //
112
+ // We use window.location.href as the base for resolving relative URLs.
113
+ // Try/catch guards against malformed URLs; on failure both origins stay
114
+ // null and only the isAuthEndpoint() path-based fallback is used.
115
+ let backendOrigin = null;
116
+ let requestOrigin = null;
117
+ try {
118
+ const pageBase = window.location?.href || '';
119
+ backendOrigin = UI_CONFIG.apiPrefix.startsWith('http')
120
+ ? new URL(UI_CONFIG.apiPrefix).origin
121
+ : new URL(pageBase).origin;
122
+ requestOrigin = new URL(url, pageBase).origin;
123
+ } catch (_) { /* malformed URL — isAuthEndpoint() below handles known endpoints */ }
124
+
125
+ const isBackendRequest = backendOrigin !== null && backendOrigin === requestOrigin;
126
+ const isAuthRequest = isBackendRequest || isAuthEndpoint(url);
127
+
128
+ if (isAuthRequest) {
129
+ if (!init.headers) init.headers = {};
130
+ if (init.headers instanceof Headers) {
131
+ const token = getCookie('__Host-csrf-token') || getCookie('__Secure-csrf-token') || getCookie('csrf-token');
132
+ if (token) init.headers.set('X-CSRF-Token', token);
133
+ } else {
134
+ init.headers = addCsrfHeader(init.headers);
135
+ }
136
+ init.credentials = init.credentials || 'include';
137
+ }
138
+
139
+ let response = await originalFetch(input, init);
140
+
141
+ if ((response.status === 401 || response.status === 403) && !isAuthEndpoint(url)) {
142
+ // Peek at the response body to detect a permanent SESSION_REVOKED error.
143
+ // We must clone() before reading so the original response stays consumable.
144
+ let errBody = null;
145
+ try { errBody = await response.clone().json(); } catch (_) {}
146
+
147
+ // If the server already told us the session is permanently revoked,
148
+ // skip the refresh entirely — retrying would only loop forever.
149
+ const isRevoked = errBody && errBody.code === 'SESSION_REVOKED';
150
+
151
+ if (!isRevoked) {
152
+ try {
153
+ const refreshResult = await refreshToken();
154
+ // Guard against SESSION_REVOKED coming back from the refresh
155
+ // endpoint (e.g. when checkOn:'refresh') — it has no `success`
156
+ // field, so the old `!== false` check would incorrectly treat
157
+ // it as a success and re-issue the original request, looping.
158
+ const refreshRevoked = refreshResult && refreshResult.code === 'SESSION_REVOKED';
159
+ if (!refreshRevoked && refreshResult && refreshResult.success !== false) {
160
+ if (_overrides.onRefreshSuccess) _overrides.onRefreshSuccess(refreshResult);
161
+ if (!(init.headers instanceof Headers)) {
162
+ init.headers = addCsrfHeader(init.headers);
163
+ }
164
+ return originalFetch(input, init);
165
+ }
166
+ } catch (e) {
167
+ console.error('[AwesomeNodeAuth] Auto-refresh failed', e);
168
+ }
169
+ }
170
+
171
+ // Refresh fallito
172
+ if (_overrides.onRefreshFail) {
173
+ _overrides.onRefreshFail();
174
+ } else {
175
+ try {
176
+ await originalFetch(`${UI_CONFIG.apiPrefix}/logout`, {
177
+ method: 'POST',
178
+ credentials: 'include'
179
+ });
180
+ } catch (e) { }
181
+ }
182
+
183
+ isAuthenticated = false;
184
+ currentUser = null;
185
+ if (window.AuthService) window.AuthService.user = null;
186
+
187
+ if (_overrides.onSessionExpired) {
188
+ _overrides.onSessionExpired();
189
+ } else if (!window.location.pathname.includes('/login')) {
190
+ window.location.href = getLoginUrl();
191
+ }
192
+ }
193
+
194
+ return response;
195
+ };
196
+
197
+ // --- HEADLESS MODE HELPER ---
198
+ /**
199
+ * Auto-installs no-op lifecycle handlers when the server is running in
200
+ * headless UI mode (authConfig.ui.headless === true).
201
+ *
202
+ * In headless mode the backend does not serve login/register HTML pages — a
203
+ * remote SPA (e.g. a Docusaurus wiki) loads auth.js via a <script> tag and
204
+ * handles authentication in its own UI. Without this guard, auth.js would
205
+ * redirect window.location to its own (missing) login page whenever the session
206
+ * expires or a refresh fails, breaking the SPA navigation entirely.
207
+ *
208
+ * The function is idempotent: if the caller has already registered a custom
209
+ * handler it is left untouched (explicit beats implicit).
210
+ */
211
+ function _applyHeadlessIfNeeded() {
212
+ if (!UI_CONFIG.headless) return;
213
+ if (!_overrides.onSessionExpired) _overrides.onSessionExpired = function () { };
214
+ if (!_overrides.onRefreshFail) _overrides.onRefreshFail = function () { };
215
+ if (!_overrides.onLogout) _overrides.onLogout = function () { };
216
+ }
217
+
218
+ // --- INTERNAL AUTH SERVICE (pagine statiche della libreria) ---
219
+ window.AuthService = {
220
+ config: UI_CONFIG,
221
+ user: null,
222
+
223
+ async init() {
224
+ try {
225
+ // SERVER-SIDE RENDERING (SSR) Fast-Path
226
+ // If __AUTH_CONFIG__ was injected into the HTML (e.g. via ui.router.ts),
227
+ // skip fetching so the UI initialization is purely synchronous and FOUC-free.
228
+ if (window.__AUTH_CONFIG__) {
229
+ UI_CONFIG = { ...UI_CONFIG, ...window.__AUTH_CONFIG__ };
230
+ } else {
231
+ const res = await fetch(`${UI_CONFIG.apiPrefix}/ui/config`);
232
+ if (res.ok) {
233
+ const dynamicConfig = await res.json();
234
+ UI_CONFIG = { ...UI_CONFIG, ...dynamicConfig };
235
+ }
236
+ }
237
+
238
+ this.config = UI_CONFIG;
239
+ window.AwesomeNodeAuth.config = UI_CONFIG;
240
+
241
+ // Headless mode: the server reported that HTML pages are not served
242
+ // (a remote SPA is hosting the login UI). Auto-install no-op lifecycle
243
+ // handlers so auth.js never redirects window.location away from the SPA.
244
+ _applyHeadlessIfNeeded();
245
+ } catch (e) { console.warn('[AwesomeNodeAuth] Failed to load UI config', e); }
246
+
247
+ // Theme is mostly handled via SSR HTML injection now, but we keep this
248
+ // as an absolute fallback or if manual API loading occurred.
249
+ if (UI_CONFIG.ui && !window.__AUTH_CONFIG__) {
250
+ const root = document.documentElement;
251
+ if (UI_CONFIG.ui.primaryColor) {
252
+ root.style.setProperty('--primary-color', UI_CONFIG.ui.primaryColor);
253
+ root.style.setProperty('--input-focus', UI_CONFIG.ui.primaryColor);
254
+ }
255
+ if (UI_CONFIG.ui.secondaryColor) root.style.setProperty('--secondary-color', UI_CONFIG.ui.secondaryColor);
256
+ if (UI_CONFIG.ui.bgColor) root.style.setProperty('--bg-color', UI_CONFIG.ui.bgColor);
257
+ if (UI_CONFIG.ui.cardBg) root.style.setProperty('--card-bg', UI_CONFIG.ui.cardBg);
258
+ if (UI_CONFIG.ui.bgImage) {
259
+ const safeUrl = UI_CONFIG.ui.bgImage.replace(/['"\\]/g, m => encodeURIComponent(m));
260
+ root.style.setProperty('--bg-image', 'url("' + safeUrl + '")');
261
+ }
262
+ if (UI_CONFIG.ui.logoUrl) {
263
+ document.querySelectorAll('.logo').forEach(el => {
264
+ el.src = UI_CONFIG.ui.logoUrl;
265
+ el.classList.remove('hidden');
266
+ });
267
+ }
268
+ if (UI_CONFIG.ui.siteName) {
269
+ document.title = UI_CONFIG.ui.siteName;
270
+ document.querySelectorAll('.site-name').forEach(el => {
271
+ el.textContent = UI_CONFIG.ui.siteName;
272
+ });
273
+ }
274
+ if (UI_CONFIG.ui.customCss) {
275
+ const style = document.createElement('style');
276
+ style.textContent = UI_CONFIG.ui.customCss;
277
+ document.head.appendChild(style);
278
+ }
279
+ }
280
+
281
+ await window.AwesomeNodeAuth.checkSession();
282
+ this.user = currentUser;
283
+ this.applyTranslations();
284
+ },
285
+
286
+ applyTranslations() {
287
+ const translations = this.config.translations || {};
288
+ if (Object.keys(translations).length === 0) return;
289
+
290
+ document.querySelectorAll('[data-i18n]').forEach(el => {
291
+ const key = el.getAttribute('data-i18n');
292
+ if (key && translations[key]) {
293
+ // If it's an input/textarea placeholder
294
+ if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
295
+ el.placeholder = translations[key];
296
+ } else {
297
+ el.textContent = translations[key];
298
+ }
299
+ }
300
+ });
301
+ },
302
+
303
+ async apiCall(endpoint, method = 'POST', body = null) {
304
+ const options = {
305
+ method,
306
+ headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }
307
+ };
308
+ if (body && ['POST', 'PUT', 'PATCH'].includes(method.toUpperCase())) {
309
+ options.body = JSON.stringify(body);
310
+ }
311
+ const res = await fetch(`${UI_CONFIG.apiPrefix}${endpoint}`, options);
312
+ const data = await res.json().catch(() => ({}));
313
+ if (!res.ok) {
314
+ data.success = false;
315
+ if (!data.error) data.error = 'Request failed (' + res.status + ')';
316
+ }
317
+ return data;
318
+ }
319
+ };
320
+
321
+ // --- HELPERS INTERNI ---
322
+ async function _checkSessionInternal() {
323
+ try {
324
+ const res = await originalFetch(`${UI_CONFIG.apiPrefix}/me`, {
325
+ credentials: 'include',
326
+ headers: { 'Accept': 'application/json' }
327
+ });
328
+ isAuthenticated = res.ok;
329
+ currentUser = res.ok ? await res.json() : null;
330
+ } catch (e) {
331
+ isAuthenticated = false;
332
+ currentUser = null;
333
+ }
334
+ if (window.AuthService) window.AuthService.user = currentUser;
335
+ isInitialized = true;
336
+ return isAuthenticated;
337
+ }
338
+
339
+ async function _logoutInternal() {
340
+ await window.AuthService.apiCall('/logout', 'POST');
341
+ isAuthenticated = false;
342
+ currentUser = null;
343
+ if (window.AuthService) window.AuthService.user = null;
344
+ if (_overrides.onLogout) {
345
+ _overrides.onLogout();
346
+ } else {
347
+ window.location.href = getLoginUrl();
348
+ }
349
+ }
350
+
351
+ // --- PUBLIC API ---
352
+ window.AwesomeNodeAuth = {
353
+ config: UI_CONFIG,
354
+
355
+ /**
356
+ * Configurazione opzionale. Se non chiamato, funziona con i default.
357
+ *
358
+ * @param {object} options
359
+ * @param {string} [options.apiPrefix] - Base path del backend. Default: derivato dal pathname
360
+ * @param {string} [options.loginUrl] - URL pagina login. Default: {apiPrefix}/ui/login
361
+ * @param {string} [options.homeUrl] - URL home dopo login. Default: '/'
362
+ * @param {boolean} [options.headless] - Headless mode: installs no-op onSessionExpired /
363
+ * onLogout / onRefreshFail handlers immediately so
364
+ * auth.js never redirects window.location. Useful
365
+ * when loading auth.js from a remote SPA (e.g.
366
+ * Docusaurus) that manages its own navigation.
367
+ * @param {Function} [options.login] - Override metodo login
368
+ * @param {Function} [options.logout] - Override metodo logout
369
+ * @param {Function} [options.register] - Override metodo register
370
+ * @param {Function} [options.onLogout] - Callback post-logout (sostituisce redirect automatico)
371
+ * @param {Function} [options.onSessionExpired] - Callback sessione scaduta (sostituisce redirect automatico)
372
+ * @param {Function} [options.onRefreshSuccess] - Callback refresh riuscito
373
+ * @param {Function} [options.onRefreshFail] - Callback refresh fallito
374
+ *
375
+ * @example
376
+ * // Zero config
377
+ * // <script src="/auth/ui/assets/auth.js"></script>
378
+ *
379
+ * // Con config base
380
+ * AwesomeNodeAuth.init({ apiPrefix: '/api/auth', loginUrl: '/login' });
381
+ *
382
+ * // Con override metodo
383
+ * AwesomeNodeAuth.init({
384
+ * apiPrefix: '/api/auth',
385
+ * login: async (email, password) => {
386
+ * console.log('custom login');
387
+ * return window.AuthService.apiCall('/login', 'POST', { email, password });
388
+ * },
389
+ * onSessionExpired: () => myRouter.navigate('/login')
390
+ * });
391
+ */
392
+ init(options = {}) {
393
+ const {
394
+ apiPrefix, loginUrl, homeUrl, siteName, headless,
395
+ onLogout, onSessionExpired, onRefreshSuccess, onRefreshFail,
396
+ ...methodOverrides
397
+ } = options;
398
+
399
+ // Aggiorna config
400
+ if (apiPrefix) UI_CONFIG.apiPrefix = apiPrefix;
401
+ if (loginUrl) UI_CONFIG.loginUrl = loginUrl;
402
+ if (homeUrl) UI_CONFIG.homeUrl = homeUrl;
403
+ if (siteName) UI_CONFIG.siteName = siteName;
404
+ // headless: true → mark config immediately so _applyHeadlessIfNeeded() fires
405
+ if (headless) UI_CONFIG.headless = true;
406
+ this.config = UI_CONFIG;
407
+ if (window.AuthService) window.AuthService.config = UI_CONFIG;
408
+
409
+ // Registra hooks lifecycle
410
+ if (onLogout) _overrides.onLogout = onLogout;
411
+ if (onSessionExpired) _overrides.onSessionExpired = onSessionExpired;
412
+ if (onRefreshSuccess) _overrides.onRefreshSuccess = onRefreshSuccess;
413
+ if (onRefreshFail) _overrides.onRefreshFail = onRefreshFail;
414
+
415
+ // Registra override metodi
416
+ const overridableMetods = [
417
+ 'login', 'logout', 'register', 'forgotPassword', 'resetPassword',
418
+ 'changePassword', 'setPassword', 'sendMagicLink', 'verifyMagicLink',
419
+ 'setup2fa', 'verify2faSetup', 'validate2fa', 'sendSmsLogin',
420
+ 'verifySmsLogin', 'validateSms', 'resendVerificationEmail',
421
+ 'verifyEmail', 'requestEmailChange', 'confirmEmailChange',
422
+ 'requestLinkingEmail', 'verifyLinkingToken', 'verifyConflictLinkingToken',
423
+ 'getLinkedAccounts', 'unlinkAccount', 'deleteAccount'
424
+ ];
425
+ overridableMetods.forEach(method => {
426
+ if (typeof methodOverrides[method] === 'function') {
427
+ _overrides[method] = methodOverrides[method];
428
+ }
429
+ });
430
+
431
+ // Apply headless no-op handlers if the config reports headless mode.
432
+ // This covers the case where init() is called from a Docusaurus <head>
433
+ // before AuthService.init() has had a chance to fetch /ui/config.
434
+ _applyHeadlessIfNeeded();
435
+ },
436
+
437
+ // --- STATE ---
438
+ isAuthenticated: () => isAuthenticated,
439
+ isInitialized: () => isInitialized,
440
+ getUser: () => currentUser,
441
+
442
+ // --- REFRESH ---
443
+
444
+ /**
445
+ * Trigger a token refresh using the shared in-flight singleton.
446
+ * Safe to call concurrently — multiple callers receive the same Promise.
447
+ * Exposed so that external scripts (e.g. Docusaurus wiki components) can
448
+ * delegate their own refresh calls here instead of duplicating the logic,
449
+ * ensuring a single HTTP request is made even across module boundaries.
450
+ *
451
+ * @returns {Promise<boolean>} true if the refresh succeeded, false otherwise
452
+ */
453
+ async refresh() {
454
+ const result = await refreshToken().catch(() => null);
455
+ // SESSION_REVOKED is a permanent failure — never treat it as success
456
+ if (result && result.code === 'SESSION_REVOKED') return false;
457
+ // Succeeds if result.success is explicitly true OR if result is simply an object (e.g. {accessToken: "..."})
458
+ return !!(result && (result.success !== false));
459
+ },
460
+
461
+ // --- SESSION ---
462
+
463
+ async checkSession() {
464
+ return _checkSessionInternal();
465
+ },
466
+
467
+ /**
468
+ * Fetch all active sessions for the currently authenticated user.
469
+ * Requires ISessionStore on the server with getSessionsForUser implemented.
470
+ * @returns {Promise<{sessions: Array, error?: string}>}
471
+ */
472
+ async getActiveSessions() {
473
+ const data = await window.AuthService.apiCall('/sessions', 'GET');
474
+ return { sessions: data.sessions || [], error: data.error };
475
+ },
476
+
477
+ /**
478
+ * Revoke a specific session by its handle.
479
+ * @param {string} sessionHandle
480
+ * @returns {Promise<{success: boolean, error?: string}>}
481
+ */
482
+ async revokeSession(sessionHandle) {
483
+ const data = await window.AuthService.apiCall('/sessions/' + encodeURIComponent(sessionHandle), 'DELETE');
484
+ return { success: !!data.success, error: data.error };
485
+ },
486
+
487
+ async guardPage(customLoginUrl) {
488
+ const ok = await _checkSessionInternal();
489
+ if (!ok && !window.location.pathname.includes('/login')) {
490
+ window.location.href = customLoginUrl || getLoginUrl();
491
+ }
492
+ },
493
+
494
+ async guardRole(role, customLoginUrl) {
495
+ const ok = await _checkSessionInternal();
496
+ const hasRole = currentUser && (
497
+ currentUser.role === role ||
498
+ (Array.isArray(currentUser.roles) && currentUser.roles.includes(role))
499
+ );
500
+ if (!ok || !hasRole) {
501
+ window.location.href = customLoginUrl || getLoginUrl();
502
+ }
503
+ },
504
+
505
+ // --- AUTH ---
506
+
507
+ async login(email, password) {
508
+ if (_overrides.login) return _overrides.login(email, password);
509
+ const data = await window.AuthService.apiCall('/login', 'POST', { email, password });
510
+ if (data.requiresTwoFactor) {
511
+ return {
512
+ success: true,
513
+ requires2fa: true,
514
+ tempToken: data.tempToken,
515
+ availableMethods: data.available2faMethods || []
516
+ };
517
+ }
518
+ if (data.requires2FASetup) {
519
+ return {
520
+ success: false,
521
+ requires2FASetup: true,
522
+ tempToken: data.tempToken,
523
+ error: data.error || '2FA setup required'
524
+ };
525
+ }
526
+ if (data.success) await _checkSessionInternal();
527
+ return { success: !!data.success, error: data.error };
528
+ },
529
+
530
+ async logout() {
531
+ if (_overrides.logout) return _overrides.logout();
532
+ return _logoutInternal();
533
+ },
534
+
535
+ async register(email, password, firstName, lastName) {
536
+ if (_overrides.register) return _overrides.register(email, password, firstName, lastName);
537
+ const data = await window.AuthService.apiCall('/register', 'POST', {
538
+ email, password, firstName, lastName
539
+ });
540
+ if (data.success) await _checkSessionInternal();
541
+ return { success: !!data.success, error: data.error };
542
+ },
543
+
544
+ // --- PASSWORD ---
545
+
546
+ async forgotPassword(email) {
547
+ if (_overrides.forgotPassword) return _overrides.forgotPassword(email);
548
+ return window.AuthService.apiCall('/forgot-password', 'POST', { email });
549
+ },
550
+
551
+ async resetPassword(token, password) {
552
+ if (_overrides.resetPassword) return _overrides.resetPassword(token, password);
553
+ return window.AuthService.apiCall('/reset-password', 'POST', { token, password });
554
+ },
555
+
556
+ async changePassword(currentPassword, newPassword) {
557
+ if (_overrides.changePassword) return _overrides.changePassword(currentPassword, newPassword);
558
+ return window.AuthService.apiCall('/change-password', 'POST', {
559
+ currentPassword, newPassword
560
+ });
561
+ },
562
+
563
+ /**
564
+ * Imposta la password per la prima volta su un account OAuth senza password.
565
+ * Equivale a changePassword con currentPassword vuota — gestito lato backend.
566
+ */
567
+ async setPassword(newPassword) {
568
+ if (_overrides.setPassword) return _overrides.setPassword(newPassword);
569
+ return this.changePassword('', newPassword);
570
+ },
571
+
572
+ // --- MAGIC LINK ---
573
+
574
+ async sendMagicLink(email) {
575
+ if (_overrides.sendMagicLink) return _overrides.sendMagicLink(email);
576
+ return window.AuthService.apiCall('/magic-link/send', 'POST', {
577
+ email, mode: 'login'
578
+ });
579
+ },
580
+
581
+ async verifyMagicLink(token) {
582
+ if (_overrides.verifyMagicLink) return _overrides.verifyMagicLink(token);
583
+ const data = await window.AuthService.apiCall('/magic-link/verify', 'POST', {
584
+ token, mode: 'login'
585
+ });
586
+ if (data.success) await _checkSessionInternal();
587
+ return { success: !!data.success, error: data.error };
588
+ },
589
+
590
+ // --- 2FA ---
591
+
592
+ async setup2fa() {
593
+ if (_overrides.setup2fa) return _overrides.setup2fa();
594
+ return window.AuthService.apiCall('/2fa/setup', 'POST');
595
+ },
596
+
597
+ async verify2faSetup(code, secret) {
598
+ if (_overrides.verify2faSetup) return _overrides.verify2faSetup(code, secret);
599
+ return window.AuthService.apiCall('/2fa/verify-setup', 'POST', {
600
+ token: code, secret
601
+ });
602
+ },
603
+
604
+ async validate2fa(tempToken, code) {
605
+ if (_overrides.validate2fa) return _overrides.validate2fa(tempToken, code);
606
+ const data = await window.AuthService.apiCall('/2fa/verify', 'POST', {
607
+ tempToken, totpCode: code
608
+ });
609
+ if (data.success) await _checkSessionInternal();
610
+ return { success: !!data.success, error: data.error };
611
+ },
612
+
613
+ // --- SMS ---
614
+
615
+ async sendSmsLogin(email) {
616
+ if (_overrides.sendSmsLogin) return _overrides.sendSmsLogin(email);
617
+ return window.AuthService.apiCall('/sms/send', 'POST', {
618
+ email, mode: 'login'
619
+ });
620
+ },
621
+
622
+ async verifySmsLogin(userId, code) {
623
+ if (_overrides.verifySmsLogin) return _overrides.verifySmsLogin(userId, code);
624
+ const data = await window.AuthService.apiCall('/sms/verify', 'POST', {
625
+ userId, code, mode: 'login'
626
+ });
627
+ if (data.success) await _checkSessionInternal();
628
+ return { success: !!data.success, error: data.error };
629
+ },
630
+
631
+ async validateSms(tempToken, code) {
632
+ if (_overrides.validateSms) return _overrides.validateSms(tempToken, code);
633
+ const data = await window.AuthService.apiCall('/sms/verify', 'POST', {
634
+ tempToken, code, mode: '2fa'
635
+ });
636
+ if (data.success) await _checkSessionInternal();
637
+ return { success: !!data.success, error: data.error };
638
+ },
639
+
640
+ // --- EMAIL VERIFICATION ---
641
+
642
+ async resendVerificationEmail() {
643
+ if (_overrides.resendVerificationEmail) return _overrides.resendVerificationEmail();
644
+ return window.AuthService.apiCall('/send-verification-email', 'POST');
645
+ },
646
+
647
+ async verifyEmail(token) {
648
+ if (_overrides.verifyEmail) return _overrides.verifyEmail(token);
649
+ const data = await window.AuthService.apiCall(
650
+ `/verify-email?token=${token}`, 'GET'
651
+ );
652
+ if (data.success) await _checkSessionInternal();
653
+ return { success: !!data.success, error: data.error };
654
+ },
655
+
656
+ // --- EMAIL CHANGE ---
657
+
658
+ async requestEmailChange(newEmail) {
659
+ if (_overrides.requestEmailChange) return _overrides.requestEmailChange(newEmail);
660
+ return window.AuthService.apiCall('/change-email/request', 'POST', { newEmail });
661
+ },
662
+
663
+ async confirmEmailChange(token) {
664
+ if (_overrides.confirmEmailChange) return _overrides.confirmEmailChange(token);
665
+ const data = await window.AuthService.apiCall('/change-email/confirm', 'POST', { token });
666
+ if (data.success) await _checkSessionInternal();
667
+ return { success: !!data.success, error: data.error };
668
+ },
669
+
670
+ // --- ACCOUNT LINKING ---
671
+
672
+ async requestLinkingEmail(email, provider) {
673
+ if (_overrides.requestLinkingEmail) return _overrides.requestLinkingEmail(email, provider);
674
+ return window.AuthService.apiCall('/link-request', 'POST', { email, provider });
675
+ },
676
+
677
+ async verifyLinkingToken(token, provider) {
678
+ if (_overrides.verifyLinkingToken) return _overrides.verifyLinkingToken(token, provider);
679
+ const data = await window.AuthService.apiCall('/link-verify', 'POST', { token, provider });
680
+ if (data.success) await _checkSessionInternal();
681
+ return { success: !!data.success, error: data.error };
682
+ },
683
+
684
+ async verifyConflictLinkingToken(token) {
685
+ if (_overrides.verifyConflictLinkingToken) return _overrides.verifyConflictLinkingToken(token);
686
+ const data = await window.AuthService.apiCall('/link-verify', 'POST', {
687
+ token, loginAfterLinking: true
688
+ });
689
+ if (data.success) await _checkSessionInternal();
690
+ return { success: !!data.success, error: data.error };
691
+ },
692
+
693
+ async getLinkedAccounts() {
694
+ if (_overrides.getLinkedAccounts) return _overrides.getLinkedAccounts();
695
+ const data = await window.AuthService.apiCall('/linked-accounts', 'GET');
696
+ return data.linkedAccounts || [];
697
+ },
698
+
699
+ async unlinkAccount(provider, providerAccountId) {
700
+ if (_overrides.unlinkAccount) return _overrides.unlinkAccount(provider, providerAccountId);
701
+ return window.AuthService.apiCall(
702
+ `/linked-accounts/${provider}/${providerAccountId}`, 'DELETE'
703
+ );
704
+ },
705
+
706
+ // --- ACCOUNT ---
707
+
708
+ async deleteAccount() {
709
+ if (_overrides.deleteAccount) return _overrides.deleteAccount();
710
+ const data = await window.AuthService.apiCall('/account', 'DELETE');
711
+ if (data.success) {
712
+ isAuthenticated = false;
713
+ currentUser = null;
714
+ if (window.AuthService) window.AuthService.user = null;
715
+ if (_overrides.onLogout) {
716
+ _overrides.onLogout();
717
+ } else {
718
+ window.location.href = getLoginUrl();
719
+ }
720
+ }
721
+ return { success: !!data.success, error: data.error };
722
+ },
723
+ };
724
+
725
+ })();