@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.
- package/LICENSE +21 -0
- package/README.detailed.md +4059 -0
- package/README.md +248 -0
- package/dist/abstract/base-auth-strategy.abstract.d.ts +7 -0
- package/dist/abstract/base-auth-strategy.abstract.d.ts.map +1 -0
- package/dist/abstract/base-auth-strategy.abstract.js +7 -0
- package/dist/abstract/base-auth-strategy.abstract.js.map +1 -0
- package/dist/abstract/base-oauth-strategy.abstract.d.ts +27 -0
- package/dist/abstract/base-oauth-strategy.abstract.d.ts.map +1 -0
- package/dist/abstract/base-oauth-strategy.abstract.js +11 -0
- package/dist/abstract/base-oauth-strategy.abstract.js.map +1 -0
- package/dist/adapters/express.d.ts +45 -0
- package/dist/adapters/express.d.ts.map +1 -0
- package/dist/adapters/express.js +49 -0
- package/dist/adapters/express.js.map +1 -0
- package/dist/adapters/fastify.d.ts +72 -0
- package/dist/adapters/fastify.d.ts.map +1 -0
- package/dist/adapters/fastify.js +63 -0
- package/dist/adapters/fastify.js.map +1 -0
- package/dist/auth-configurator.d.ts +65 -0
- package/dist/auth-configurator.d.ts.map +1 -0
- package/dist/auth-configurator.js +127 -0
- package/dist/auth-configurator.js.map +1 -0
- package/dist/events/auth-event-bus.d.ts +68 -0
- package/dist/events/auth-event-bus.d.ts.map +1 -0
- package/dist/events/auth-event-bus.js +60 -0
- package/dist/events/auth-event-bus.js.map +1 -0
- package/dist/events/auth-event-names.d.ts +34 -0
- package/dist/events/auth-event-names.d.ts.map +1 -0
- package/dist/events/auth-event-names.js +41 -0
- package/dist/events/auth-event-names.js.map +1 -0
- package/dist/http-types.d.ts +135 -0
- package/dist/http-types.d.ts.map +1 -0
- package/dist/http-types.js +18 -0
- package/dist/http-types.js.map +1 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +88 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/api-key-store.interface.d.ts +94 -0
- package/dist/interfaces/api-key-store.interface.d.ts.map +1 -0
- package/dist/interfaces/api-key-store.interface.js +3 -0
- package/dist/interfaces/api-key-store.interface.js.map +1 -0
- package/dist/interfaces/auth-strategy.interface.d.ts +6 -0
- package/dist/interfaces/auth-strategy.interface.d.ts.map +1 -0
- package/dist/interfaces/auth-strategy.interface.js +3 -0
- package/dist/interfaces/auth-strategy.interface.js.map +1 -0
- package/dist/interfaces/linked-accounts-store.interface.d.ts +74 -0
- package/dist/interfaces/linked-accounts-store.interface.d.ts.map +1 -0
- package/dist/interfaces/linked-accounts-store.interface.js +3 -0
- package/dist/interfaces/linked-accounts-store.interface.js.map +1 -0
- package/dist/interfaces/pending-link-store.interface.d.ts +77 -0
- package/dist/interfaces/pending-link-store.interface.d.ts.map +1 -0
- package/dist/interfaces/pending-link-store.interface.js +3 -0
- package/dist/interfaces/pending-link-store.interface.js.map +1 -0
- package/dist/interfaces/roles-permissions-store.interface.d.ts +129 -0
- package/dist/interfaces/roles-permissions-store.interface.d.ts.map +1 -0
- package/dist/interfaces/roles-permissions-store.interface.js +3 -0
- package/dist/interfaces/roles-permissions-store.interface.js.map +1 -0
- package/dist/interfaces/session-store.interface.d.ts +92 -0
- package/dist/interfaces/session-store.interface.d.ts.map +1 -0
- package/dist/interfaces/session-store.interface.js +3 -0
- package/dist/interfaces/session-store.interface.js.map +1 -0
- package/dist/interfaces/settings-store.interface.d.ts +91 -0
- package/dist/interfaces/settings-store.interface.d.ts.map +1 -0
- package/dist/interfaces/settings-store.interface.js +3 -0
- package/dist/interfaces/settings-store.interface.js.map +1 -0
- package/dist/interfaces/sse-distributor.interface.d.ts +25 -0
- package/dist/interfaces/sse-distributor.interface.d.ts.map +1 -0
- package/dist/interfaces/sse-distributor.interface.js +3 -0
- package/dist/interfaces/sse-distributor.interface.js.map +1 -0
- package/dist/interfaces/telemetry-store.interface.d.ts +72 -0
- package/dist/interfaces/telemetry-store.interface.d.ts.map +1 -0
- package/dist/interfaces/telemetry-store.interface.js +3 -0
- package/dist/interfaces/telemetry-store.interface.js.map +1 -0
- package/dist/interfaces/template-store.interface.d.ts +37 -0
- package/dist/interfaces/template-store.interface.d.ts.map +1 -0
- package/dist/interfaces/template-store.interface.js +3 -0
- package/dist/interfaces/template-store.interface.js.map +1 -0
- package/dist/interfaces/tenant-store.interface.d.ts +97 -0
- package/dist/interfaces/tenant-store.interface.d.ts.map +1 -0
- package/dist/interfaces/tenant-store.interface.js +3 -0
- package/dist/interfaces/tenant-store.interface.js.map +1 -0
- package/dist/interfaces/token-store.interface.d.ts +10 -0
- package/dist/interfaces/token-store.interface.d.ts.map +1 -0
- package/dist/interfaces/token-store.interface.js +3 -0
- package/dist/interfaces/token-store.interface.js.map +1 -0
- package/dist/interfaces/user-metadata-store.interface.d.ts +50 -0
- package/dist/interfaces/user-metadata-store.interface.d.ts.map +1 -0
- package/dist/interfaces/user-metadata-store.interface.js +3 -0
- package/dist/interfaces/user-metadata-store.interface.js.map +1 -0
- package/dist/interfaces/user-store.interface.d.ts +113 -0
- package/dist/interfaces/user-store.interface.d.ts.map +1 -0
- package/dist/interfaces/user-store.interface.js +3 -0
- package/dist/interfaces/user-store.interface.js.map +1 -0
- package/dist/interfaces/webhook-store.interface.d.ts +139 -0
- package/dist/interfaces/webhook-store.interface.d.ts.map +1 -0
- package/dist/interfaces/webhook-store.interface.js +3 -0
- package/dist/interfaces/webhook-store.interface.js.map +1 -0
- package/dist/middleware/api-key.middleware.d.ts +35 -0
- package/dist/middleware/api-key.middleware.d.ts.map +1 -0
- package/dist/middleware/api-key.middleware.js +45 -0
- package/dist/middleware/api-key.middleware.js.map +1 -0
- package/dist/middleware/auth.middleware.d.ts +13 -0
- package/dist/middleware/auth.middleware.d.ts.map +1 -0
- package/dist/middleware/auth.middleware.js +55 -0
- package/dist/middleware/auth.middleware.js.map +1 -0
- package/dist/middleware/jwks-auth.middleware.d.ts +18 -0
- package/dist/middleware/jwks-auth.middleware.d.ts.map +1 -0
- package/dist/middleware/jwks-auth.middleware.js +77 -0
- package/dist/middleware/jwks-auth.middleware.js.map +1 -0
- package/dist/models/api-key.model.d.ts +66 -0
- package/dist/models/api-key.model.d.ts.map +1 -0
- package/dist/models/api-key.model.js +3 -0
- package/dist/models/api-key.model.js.map +1 -0
- package/dist/models/auth-config.model.d.ts +387 -0
- package/dist/models/auth-config.model.d.ts.map +1 -0
- package/dist/models/auth-config.model.js +3 -0
- package/dist/models/auth-config.model.js.map +1 -0
- package/dist/models/errors.d.ts +7 -0
- package/dist/models/errors.d.ts.map +1 -0
- package/dist/models/errors.js +14 -0
- package/dist/models/errors.js.map +1 -0
- package/dist/models/session.model.d.ts +28 -0
- package/dist/models/session.model.d.ts.map +1 -0
- package/dist/models/session.model.js +3 -0
- package/dist/models/session.model.js.map +1 -0
- package/dist/models/tenant.model.d.ts +20 -0
- package/dist/models/tenant.model.d.ts.map +1 -0
- package/dist/models/tenant.model.js +3 -0
- package/dist/models/tenant.model.js.map +1 -0
- package/dist/models/token.model.d.ts +21 -0
- package/dist/models/token.model.d.ts.map +1 -0
- package/dist/models/token.model.js +3 -0
- package/dist/models/token.model.js.map +1 -0
- package/dist/models/user.model.d.ts +92 -0
- package/dist/models/user.model.d.ts.map +1 -0
- package/dist/models/user.model.js +3 -0
- package/dist/models/user.model.js.map +1 -0
- package/dist/router/admin.router.d.ts +188 -0
- package/dist/router/admin.router.d.ts.map +1 -0
- package/dist/router/admin.router.js +1507 -0
- package/dist/router/admin.router.js.map +1 -0
- package/dist/router/auth.router.d.ts +199 -0
- package/dist/router/auth.router.d.ts.map +1 -0
- package/dist/router/auth.router.js +1636 -0
- package/dist/router/auth.router.js.map +1 -0
- package/dist/router/openapi.d.ts +98 -0
- package/dist/router/openapi.d.ts.map +1 -0
- package/dist/router/openapi.js +1518 -0
- package/dist/router/openapi.js.map +1 -0
- package/dist/router/router-events.d.ts +38 -0
- package/dist/router/router-events.d.ts.map +1 -0
- package/dist/router/router-events.js +74 -0
- package/dist/router/router-events.js.map +1 -0
- package/dist/router/tools.router.d.ts +104 -0
- package/dist/router/tools.router.d.ts.map +1 -0
- package/dist/router/tools.router.js +227 -0
- package/dist/router/tools.router.js.map +1 -0
- package/dist/router/ui.router.d.ts +49 -0
- package/dist/router/ui.router.d.ts.map +1 -0
- package/dist/router/ui.router.js +272 -0
- package/dist/router/ui.router.js.map +1 -0
- package/dist/services/api-key.service.d.ts +53 -0
- package/dist/services/api-key.service.d.ts.map +1 -0
- package/dist/services/api-key.service.js +66 -0
- package/dist/services/api-key.service.js.map +1 -0
- package/dist/services/jwks.service.d.ts +73 -0
- package/dist/services/jwks.service.d.ts.map +1 -0
- package/dist/services/jwks.service.js +174 -0
- package/dist/services/jwks.service.js.map +1 -0
- package/dist/services/mailer.service.d.ts +30 -0
- package/dist/services/mailer.service.d.ts.map +1 -0
- package/dist/services/mailer.service.js +248 -0
- package/dist/services/mailer.service.js.map +1 -0
- package/dist/services/notification.service.d.ts +110 -0
- package/dist/services/notification.service.d.ts.map +1 -0
- package/dist/services/notification.service.js +79 -0
- package/dist/services/notification.service.js.map +1 -0
- package/dist/services/password.service.d.ts +5 -0
- package/dist/services/password.service.d.ts.map +1 -0
- package/dist/services/password.service.js +17 -0
- package/dist/services/password.service.js.map +1 -0
- package/dist/services/sms.service.d.ts +14 -0
- package/dist/services/sms.service.d.ts.map +1 -0
- package/dist/services/sms.service.js +51 -0
- package/dist/services/sms.service.js.map +1 -0
- package/dist/services/token.service.d.ts +62 -0
- package/dist/services/token.service.d.ts.map +1 -0
- package/dist/services/token.service.js +354 -0
- package/dist/services/token.service.js.map +1 -0
- package/dist/stores/memory-template.store.d.ts +12 -0
- package/dist/stores/memory-template.store.d.ts.map +1 -0
- package/dist/stores/memory-template.store.js +35 -0
- package/dist/stores/memory-template.store.js.map +1 -0
- package/dist/strategies/api-key/api-key.strategy.d.ts +72 -0
- package/dist/strategies/api-key/api-key.strategy.d.ts.map +1 -0
- package/dist/strategies/api-key/api-key.strategy.js +182 -0
- package/dist/strategies/api-key/api-key.strategy.js.map +1 -0
- package/dist/strategies/local/local.strategy.d.ts +19 -0
- package/dist/strategies/local/local.strategy.d.ts.map +1 -0
- package/dist/strategies/local/local.strategy.js +45 -0
- package/dist/strategies/local/local.strategy.js.map +1 -0
- package/dist/strategies/magic-link/magic-link.strategy.d.ts +8 -0
- package/dist/strategies/magic-link/magic-link.strategy.d.ts.map +1 -0
- package/dist/strategies/magic-link/magic-link.strategy.js +53 -0
- package/dist/strategies/magic-link/magic-link.strategy.js.map +1 -0
- package/dist/strategies/oauth/generic-oauth.strategy.d.ts +120 -0
- package/dist/strategies/oauth/generic-oauth.strategy.d.ts.map +1 -0
- package/dist/strategies/oauth/generic-oauth.strategy.js +88 -0
- package/dist/strategies/oauth/generic-oauth.strategy.js.map +1 -0
- package/dist/strategies/oauth/github.strategy.d.ts +31 -0
- package/dist/strategies/oauth/github.strategy.d.ts.map +1 -0
- package/dist/strategies/oauth/github.strategy.js +72 -0
- package/dist/strategies/oauth/github.strategy.js.map +1 -0
- package/dist/strategies/oauth/google.strategy.d.ts +31 -0
- package/dist/strategies/oauth/google.strategy.d.ts.map +1 -0
- package/dist/strategies/oauth/google.strategy.js +61 -0
- package/dist/strategies/oauth/google.strategy.js.map +1 -0
- package/dist/strategies/sms/sms.strategy.d.ts +7 -0
- package/dist/strategies/sms/sms.strategy.d.ts.map +1 -0
- package/dist/strategies/sms/sms.strategy.js +39 -0
- package/dist/strategies/sms/sms.strategy.js.map +1 -0
- package/dist/strategies/two-factor/totp.strategy.d.ts +12 -0
- package/dist/strategies/two-factor/totp.strategy.d.ts.map +1 -0
- package/dist/strategies/two-factor/totp.strategy.js +32 -0
- package/dist/strategies/two-factor/totp.strategy.js.map +1 -0
- package/dist/tools/auth-tools.d.ts +200 -0
- package/dist/tools/auth-tools.d.ts.map +1 -0
- package/dist/tools/auth-tools.js +232 -0
- package/dist/tools/auth-tools.js.map +1 -0
- package/dist/tools/sse-manager.d.ts +118 -0
- package/dist/tools/sse-manager.d.ts.map +1 -0
- package/dist/tools/sse-manager.js +163 -0
- package/dist/tools/sse-manager.js.map +1 -0
- package/dist/tools/sse-notify.decorator.d.ts +74 -0
- package/dist/tools/sse-notify.decorator.d.ts.map +1 -0
- package/dist/tools/sse-notify.decorator.js +78 -0
- package/dist/tools/sse-notify.decorator.js.map +1 -0
- package/dist/tools/webhook-action.d.ts +130 -0
- package/dist/tools/webhook-action.d.ts.map +1 -0
- package/dist/tools/webhook-action.js +144 -0
- package/dist/tools/webhook-action.js.map +1 -0
- package/dist/tools/webhook-sender.d.ts +31 -0
- package/dist/tools/webhook-sender.d.ts.map +1 -0
- package/dist/tools/webhook-sender.js +80 -0
- package/dist/tools/webhook-sender.js.map +1 -0
- package/dist/ui-assets/2fa.html +115 -0
- package/dist/ui-assets/account-conflict.html +108 -0
- package/dist/ui-assets/admin.css +263 -0
- package/dist/ui-assets/admin.js +1927 -0
- package/dist/ui-assets/auth.js +725 -0
- package/dist/ui-assets/base.css +219 -0
- package/dist/ui-assets/forgot-password.html +93 -0
- package/dist/ui-assets/link-verify.html +74 -0
- package/dist/ui-assets/login.html +135 -0
- package/dist/ui-assets/magic-link.html +112 -0
- package/dist/ui-assets/register.html +122 -0
- package/dist/ui-assets/reset-password.html +103 -0
- package/dist/ui-assets/ui-i18n-keys.json +78 -0
- package/dist/ui-assets/verify-email.html +66 -0
- 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
|
+
})();
|