@oxyhq/core 3.4.0 → 3.4.2
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/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/AuthManager.js +91 -319
- package/dist/cjs/CrossDomainAuth.js +19 -106
- package/dist/cjs/HttpService.js +49 -73
- package/dist/cjs/OxyServices.base.js +2 -2
- package/dist/cjs/i18n/index.js +7 -1
- package/dist/cjs/i18n/locales/ar-SA.json +18 -2
- package/dist/cjs/i18n/locales/ca-ES.json +18 -2
- package/dist/cjs/i18n/locales/de-DE.json +18 -2
- package/dist/cjs/i18n/locales/en-US.json +16 -2
- package/dist/cjs/i18n/locales/es-ES.json +16 -2
- package/dist/cjs/i18n/locales/fr-FR.json +18 -2
- package/dist/cjs/i18n/locales/it-IT.json +18 -2
- package/dist/cjs/i18n/locales/ja-JP.json +18 -2
- package/dist/cjs/i18n/locales/ko-KR.json +18 -2
- package/dist/cjs/i18n/locales/locales/ar-SA.json +18 -2
- package/dist/cjs/i18n/locales/locales/ca-ES.json +18 -2
- package/dist/cjs/i18n/locales/locales/de-DE.json +18 -2
- package/dist/cjs/i18n/locales/locales/en-US.json +17 -3
- package/dist/cjs/i18n/locales/locales/es-ES.json +16 -2
- package/dist/cjs/i18n/locales/locales/fr-FR.json +18 -2
- package/dist/cjs/i18n/locales/locales/it-IT.json +18 -2
- package/dist/cjs/i18n/locales/locales/ja-JP.json +18 -2
- package/dist/cjs/i18n/locales/locales/ko-KR.json +18 -2
- package/dist/cjs/i18n/locales/locales/pt-PT.json +18 -2
- package/dist/cjs/i18n/locales/locales/zh-CN.json +18 -2
- package/dist/cjs/i18n/locales/pt-PT.json +18 -2
- package/dist/cjs/i18n/locales/zh-CN.json +18 -2
- package/dist/cjs/mixins/OxyServices.auth.js +20 -63
- package/dist/cjs/mixins/OxyServices.fedcm.js +10 -12
- package/dist/cjs/mixins/OxyServices.popup.js +50 -299
- package/dist/cjs/mixins/OxyServices.redirect.js +84 -348
- package/dist/cjs/mixins/OxyServices.silent.js +204 -0
- package/dist/cjs/mixins/OxyServices.sso.js +4 -5
- package/dist/cjs/mixins/OxyServices.utility.js +6 -15
- package/dist/cjs/mixins/index.js +5 -6
- package/dist/cjs/server/index.js +21 -0
- package/dist/cjs/server/rateLimit.js +77 -0
- package/dist/cjs/shared/utils/debugUtils.js +1 -1
- package/dist/cjs/utils/accountUtils.js +4 -4
- package/dist/cjs/utils/authHelpers.js +21 -15
- package/dist/cjs/utils/coldBoot.js +3 -3
- package/dist/cjs/utils/fapiAutoDetect.js +1 -1
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/AuthManager.js +91 -319
- package/dist/esm/CrossDomainAuth.js +19 -106
- package/dist/esm/HttpService.js +49 -73
- package/dist/esm/OxyServices.base.js +2 -2
- package/dist/esm/i18n/index.js +7 -1
- package/dist/esm/i18n/locales/ar-SA.json +18 -2
- package/dist/esm/i18n/locales/ca-ES.json +18 -2
- package/dist/esm/i18n/locales/de-DE.json +18 -2
- package/dist/esm/i18n/locales/en-US.json +16 -2
- package/dist/esm/i18n/locales/es-ES.json +16 -2
- package/dist/esm/i18n/locales/fr-FR.json +18 -2
- package/dist/esm/i18n/locales/it-IT.json +18 -2
- package/dist/esm/i18n/locales/ja-JP.json +18 -2
- package/dist/esm/i18n/locales/ko-KR.json +18 -2
- package/dist/esm/i18n/locales/locales/ar-SA.json +18 -2
- package/dist/esm/i18n/locales/locales/ca-ES.json +18 -2
- package/dist/esm/i18n/locales/locales/de-DE.json +18 -2
- package/dist/esm/i18n/locales/locales/en-US.json +17 -3
- package/dist/esm/i18n/locales/locales/es-ES.json +16 -2
- package/dist/esm/i18n/locales/locales/fr-FR.json +18 -2
- package/dist/esm/i18n/locales/locales/it-IT.json +18 -2
- package/dist/esm/i18n/locales/locales/ja-JP.json +18 -2
- package/dist/esm/i18n/locales/locales/ko-KR.json +18 -2
- package/dist/esm/i18n/locales/locales/pt-PT.json +18 -2
- package/dist/esm/i18n/locales/locales/zh-CN.json +18 -2
- package/dist/esm/i18n/locales/pt-PT.json +18 -2
- package/dist/esm/i18n/locales/zh-CN.json +18 -2
- package/dist/esm/mixins/OxyServices.auth.js +20 -63
- package/dist/esm/mixins/OxyServices.fedcm.js +10 -12
- package/dist/esm/mixins/OxyServices.popup.js +52 -301
- package/dist/esm/mixins/OxyServices.redirect.js +84 -349
- package/dist/esm/mixins/OxyServices.silent.js +202 -0
- package/dist/esm/mixins/OxyServices.sso.js +4 -5
- package/dist/esm/mixins/OxyServices.utility.js +6 -15
- package/dist/esm/mixins/index.js +5 -6
- package/dist/esm/server/index.js +17 -0
- package/dist/esm/server/rateLimit.js +71 -0
- package/dist/esm/shared/utils/debugUtils.js +1 -1
- package/dist/esm/utils/accountUtils.js +4 -4
- package/dist/esm/utils/authHelpers.js +21 -15
- package/dist/esm/utils/coldBoot.js +3 -3
- package/dist/esm/utils/fapiAutoDetect.js +1 -1
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/AuthManager.d.ts +26 -53
- package/dist/types/AuthManagerTypes.d.ts +5 -9
- package/dist/types/CrossDomainAuth.d.ts +13 -52
- package/dist/types/HttpService.d.ts +9 -8
- package/dist/types/OxyServices.base.d.ts +1 -1
- package/dist/types/OxyServices.d.ts +4 -10
- package/dist/types/index.d.ts +1 -1
- package/dist/types/mixins/OxyServices.analytics.d.ts +1 -1
- package/dist/types/mixins/OxyServices.appData.d.ts +1 -1
- package/dist/types/mixins/OxyServices.applications.d.ts +1 -1
- package/dist/types/mixins/OxyServices.assets.d.ts +1 -1
- package/dist/types/mixins/OxyServices.auth.d.ts +10 -31
- package/dist/types/mixins/OxyServices.contacts.d.ts +1 -1
- package/dist/types/mixins/OxyServices.devices.d.ts +1 -1
- package/dist/types/mixins/OxyServices.features.d.ts +1 -1
- package/dist/types/mixins/OxyServices.fedcm.d.ts +5 -5
- package/dist/types/mixins/OxyServices.language.d.ts +1 -1
- package/dist/types/mixins/OxyServices.location.d.ts +1 -1
- package/dist/types/mixins/OxyServices.managedAccounts.d.ts +1 -1
- package/dist/types/mixins/OxyServices.payment.d.ts +1 -1
- package/dist/types/mixins/OxyServices.popup.d.ts +18 -120
- package/dist/types/mixins/OxyServices.privacy.d.ts +1 -1
- package/dist/types/mixins/OxyServices.redirect.d.ts +13 -174
- package/dist/types/mixins/OxyServices.reputation.d.ts +1 -1
- package/dist/types/mixins/OxyServices.security.d.ts +1 -1
- package/dist/types/mixins/OxyServices.silent.d.ts +131 -0
- package/dist/types/mixins/OxyServices.sso.d.ts +4 -5
- package/dist/types/mixins/OxyServices.topics.d.ts +1 -1
- package/dist/types/mixins/OxyServices.user.d.ts +1 -1
- package/dist/types/mixins/OxyServices.utility.d.ts +3 -8
- package/dist/types/mixins/OxyServices.workspaces.d.ts +1 -1
- package/dist/types/mixins/index.d.ts +3 -3
- package/dist/types/models/interfaces.d.ts +5 -16
- package/dist/types/models/session.d.ts +0 -2
- package/dist/types/server/index.d.ts +18 -0
- package/dist/types/server/rateLimit.d.ts +40 -0
- package/dist/types/shared/utils/debugUtils.d.ts +1 -1
- package/dist/types/utils/authHelpers.d.ts +4 -3
- package/dist/types/utils/coldBoot.d.ts +2 -2
- package/dist/types/utils/fapiAutoDetect.d.ts +1 -1
- package/package.json +25 -3
- package/src/AuthManager.ts +100 -370
- package/src/AuthManagerTypes.ts +5 -9
- package/src/CrossDomainAuth.ts +22 -129
- package/src/HttpService.ts +55 -73
- package/src/OxyServices.base.ts +2 -3
- package/src/OxyServices.ts +9 -11
- package/src/__tests__/authManager.cookiePath.test.ts +19 -17
- package/src/__tests__/authManager.security.test.ts +7 -3
- package/src/__tests__/crossDomainAuth.test.ts +26 -118
- package/src/i18n/index.ts +7 -1
- package/src/i18n/locales/ar-SA.json +18 -2
- package/src/i18n/locales/ca-ES.json +18 -2
- package/src/i18n/locales/de-DE.json +18 -2
- package/src/i18n/locales/en-US.json +17 -3
- package/src/i18n/locales/es-ES.json +16 -2
- package/src/i18n/locales/fr-FR.json +18 -2
- package/src/i18n/locales/it-IT.json +18 -2
- package/src/i18n/locales/ja-JP.json +18 -2
- package/src/i18n/locales/ko-KR.json +18 -2
- package/src/i18n/locales/pt-PT.json +18 -2
- package/src/i18n/locales/zh-CN.json +18 -2
- package/src/index.ts +1 -1
- package/src/mixins/OxyServices.auth.ts +23 -75
- package/src/mixins/OxyServices.fedcm.ts +10 -12
- package/src/mixins/OxyServices.redirect.ts +82 -371
- package/src/mixins/OxyServices.silent.ts +272 -0
- package/src/mixins/OxyServices.sso.ts +5 -6
- package/src/mixins/OxyServices.utility.ts +9 -22
- package/src/mixins/__tests__/appData.test.ts +1 -1
- package/src/mixins/__tests__/onTokensChanged.test.ts +1 -1
- package/src/mixins/__tests__/reputation.test.ts +1 -1
- package/src/mixins/__tests__/serviceAuth.test.ts +7 -5
- package/src/mixins/__tests__/silent.test.ts +102 -0
- package/src/mixins/__tests__/verifyChallenge.test.ts +9 -14
- package/src/mixins/index.ts +6 -8
- package/src/models/interfaces.ts +5 -16
- package/src/models/session.ts +1 -3
- package/src/server/index.ts +19 -0
- package/src/server/rateLimit.ts +170 -0
- package/src/shared/utils/debugUtils.ts +1 -1
- package/src/utils/accountUtils.ts +4 -4
- package/src/utils/authHelpers.ts +23 -15
- package/src/utils/coldBoot.ts +4 -4
- package/src/utils/fapiAutoDetect.ts +1 -1
- package/src/mixins/OxyServices.popup.ts +0 -631
- package/src/mixins/__tests__/popup.test.ts +0 -374
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { OxyServicesBase } from '../OxyServices.base';
|
|
2
2
|
import { OxyAuthenticationError } from '../OxyServices.errors';
|
|
3
3
|
import type { SessionLoginResponse } from '../models/session';
|
|
4
|
+
import {
|
|
5
|
+
buildSsoBounceUrl,
|
|
6
|
+
ssoAttemptedKey,
|
|
7
|
+
ssoDestKey,
|
|
8
|
+
ssoGuardKey,
|
|
9
|
+
ssoStateKey,
|
|
10
|
+
} from '../utils/ssoBounce';
|
|
4
11
|
|
|
5
12
|
export interface RedirectAuthOptions {
|
|
6
13
|
redirectUri?: string;
|
|
@@ -9,403 +16,107 @@ export interface RedirectAuthOptions {
|
|
|
9
16
|
}
|
|
10
17
|
|
|
11
18
|
/**
|
|
12
|
-
* Redirect-based
|
|
19
|
+
* Redirect-based authentication without bearer tokens in URLs.
|
|
13
20
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* 1. Save current URL
|
|
19
|
-
* 2. Redirect to auth.oxy.so/login
|
|
20
|
-
* 3. User signs in
|
|
21
|
-
* 4. Redirect back with token in URL
|
|
22
|
-
* 5. Extract token, restore session, clean URL
|
|
23
|
-
*
|
|
24
|
-
* Features:
|
|
25
|
-
* - Works on all browsers (including old mobile browsers)
|
|
26
|
-
* - Automatic URL cleanup after auth
|
|
27
|
-
* - State preservation option
|
|
28
|
-
* - CSRF protection via state parameter
|
|
29
|
-
*
|
|
30
|
-
* Trade-offs:
|
|
31
|
-
* - Loses JavaScript app state (full page navigation)
|
|
32
|
-
* - Visible redirect (user sees navigation)
|
|
33
|
-
* - Slower perceived performance
|
|
21
|
+
* The redirect fallback now uses the same central SSO code-return contract as
|
|
22
|
+
* cold boot: the RP stores CSRF/destination state in sessionStorage, navigates
|
|
23
|
+
* to the central IdP, receives only an opaque one-time code in the URL fragment,
|
|
24
|
+
* and the provider's `sso-return` step exchanges that code for the real session.
|
|
34
25
|
*/
|
|
35
26
|
export function OxyServicesRedirectAuthMixin<T extends typeof OxyServicesBase>(Base: T) {
|
|
36
27
|
return class extends Base {
|
|
37
28
|
constructor(...args: any[]) {
|
|
38
29
|
super(...(args as [any]));
|
|
39
30
|
}
|
|
40
|
-
public static readonly DEFAULT_AUTH_URL = 'https://auth.oxy.so';
|
|
41
|
-
public static readonly TOKEN_STORAGE_KEY = 'oxy_access_token';
|
|
42
|
-
public static readonly SESSION_STORAGE_KEY = 'oxy_session_id';
|
|
43
|
-
public static readonly STATE_STORAGE_KEY = 'oxy_auth_state';
|
|
44
|
-
public static readonly PRE_AUTH_URL_KEY = 'oxy_pre_auth_url';
|
|
45
|
-
public static readonly NONCE_STORAGE_KEY = 'oxy_auth_nonce';
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Sign in using full page redirect
|
|
49
|
-
*
|
|
50
|
-
* Redirects the user to auth.oxy.so for authentication. After successful
|
|
51
|
-
* sign-in, the user will be redirected back to the current page (or custom
|
|
52
|
-
* redirect URI) with authentication tokens in the URL.
|
|
53
|
-
*
|
|
54
|
-
* Call handleAuthCallback() on app startup to complete the flow.
|
|
55
|
-
*
|
|
56
|
-
* @param options - Redirect configuration options
|
|
57
|
-
*
|
|
58
|
-
* @example
|
|
59
|
-
* ```typescript
|
|
60
|
-
* // Initiate sign-in
|
|
61
|
-
* const handleSignIn = () => {
|
|
62
|
-
* oxyServices.signInWithRedirect();
|
|
63
|
-
* };
|
|
64
|
-
*
|
|
65
|
-
* // Handle callback on app startup
|
|
66
|
-
* useEffect(() => {
|
|
67
|
-
* const session = oxyServices.handleAuthCallback();
|
|
68
|
-
* if (session) {
|
|
69
|
-
* setUser(session.user);
|
|
70
|
-
* }
|
|
71
|
-
* }, []);
|
|
72
|
-
* ```
|
|
73
|
-
*/
|
|
74
|
-
signInWithRedirect(options: RedirectAuthOptions = {}): void {
|
|
75
|
-
if (typeof window === 'undefined') {
|
|
76
|
-
throw new OxyAuthenticationError('Redirect authentication requires browser environment');
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const redirectUri = options.redirectUri || window.location.href;
|
|
80
|
-
const mode = options.mode || 'login';
|
|
81
|
-
const state = this.generateState();
|
|
82
|
-
const nonce = this.generateNonce();
|
|
83
|
-
|
|
84
|
-
// Store state for CSRF protection
|
|
85
|
-
this.storeAuthState(state, nonce);
|
|
86
|
-
|
|
87
|
-
// Save current URL to restore after auth (optional)
|
|
88
|
-
if (options.preserveUrl !== false) {
|
|
89
|
-
this.savePreAuthUrl(window.location.href);
|
|
90
|
-
}
|
|
91
31
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Start a full-page redirect through the central SSO flow.
|
|
34
|
+
*
|
|
35
|
+
* No access token, refresh token, or session id is ever put in the URL or
|
|
36
|
+
* localStorage. The caller's provider must run `consumeSsoReturn` on startup
|
|
37
|
+
* to complete the code exchange and commit the session.
|
|
38
|
+
*/
|
|
39
|
+
signInWithRedirect(options: RedirectAuthOptions = {}): void {
|
|
40
|
+
if (typeof window === 'undefined' || typeof window.sessionStorage === 'undefined') {
|
|
41
|
+
throw new OxyAuthenticationError('Redirect authentication requires browser sessionStorage');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const origin = window.location.origin;
|
|
45
|
+
const state = this.generateState();
|
|
46
|
+
const destination = options.preserveUrl === false
|
|
47
|
+
? (options.redirectUri || origin)
|
|
48
|
+
: (options.redirectUri || window.location.href);
|
|
49
|
+
|
|
50
|
+
window.sessionStorage.setItem(ssoStateKey(origin), state);
|
|
51
|
+
window.sessionStorage.setItem(ssoGuardKey(origin), String(Date.now()));
|
|
52
|
+
window.sessionStorage.setItem(ssoDestKey(origin), destination);
|
|
53
|
+
window.sessionStorage.setItem(ssoAttemptedKey(origin), '1');
|
|
54
|
+
|
|
55
|
+
window.location.assign(buildSsoBounceUrl(origin, state, this.config.authWebUrl));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
signUpWithRedirect(options: RedirectAuthOptions = {}): void {
|
|
59
|
+
this.signInWithRedirect({ ...options, mode: 'signup' });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Legacy token-query callbacks are intentionally rejected. Modern providers
|
|
64
|
+
* complete redirect auth through `consumeSsoReturn`, which accepts only
|
|
65
|
+
* `#oxy_sso=ok&code=...`.
|
|
66
|
+
*/
|
|
67
|
+
handleAuthCallback(): SessionLoginResponse | null {
|
|
68
|
+
if (typeof window === 'undefined') {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const url = new URL(window.location.href);
|
|
73
|
+
if (url.searchParams.has('access_token') || url.searchParams.has('session_id')) {
|
|
74
|
+
this.cleanAuthCallbackUrl(url);
|
|
75
|
+
throw new OxyAuthenticationError('Legacy access-token redirect callbacks are no longer accepted.');
|
|
76
|
+
}
|
|
99
77
|
|
|
100
|
-
// Perform redirect
|
|
101
|
-
window.location.href = authUrl;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Sign up using full page redirect
|
|
106
|
-
*
|
|
107
|
-
* Same as signInWithRedirect but opens the signup page by default.
|
|
108
|
-
*/
|
|
109
|
-
signUpWithRedirect(options: RedirectAuthOptions = {}): void {
|
|
110
|
-
this.signInWithRedirect({ ...options, mode: 'signup' });
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Handle authentication callback
|
|
115
|
-
*
|
|
116
|
-
* Call this on app startup to check if the current page load is a
|
|
117
|
-
* redirect back from the authentication server. If it is, this method
|
|
118
|
-
* will extract the tokens, store them, and clean up the URL.
|
|
119
|
-
*
|
|
120
|
-
* @returns Session data if this is a callback, null otherwise
|
|
121
|
-
* @throws {OxyAuthenticationError} If state validation fails (CSRF attack)
|
|
122
|
-
*
|
|
123
|
-
* @example
|
|
124
|
-
* ```typescript
|
|
125
|
-
* // In your app's root component or startup logic
|
|
126
|
-
* useEffect(() => {
|
|
127
|
-
* try {
|
|
128
|
-
* const session = oxyServices.handleAuthCallback();
|
|
129
|
-
* if (session) {
|
|
130
|
-
* console.log('Logged in:', session.user);
|
|
131
|
-
* setUser(session.user);
|
|
132
|
-
* } else {
|
|
133
|
-
* // Not a callback, check for existing session
|
|
134
|
-
* const restored = oxyServices.restoreSession();
|
|
135
|
-
* if (!restored) {
|
|
136
|
-
* // No session, show login button
|
|
137
|
-
* }
|
|
138
|
-
* }
|
|
139
|
-
* } catch (error) {
|
|
140
|
-
* console.error('Auth callback failed:', error);
|
|
141
|
-
* }
|
|
142
|
-
* }, []);
|
|
143
|
-
* ```
|
|
144
|
-
*/
|
|
145
|
-
handleAuthCallback(): SessionLoginResponse | null {
|
|
146
|
-
if (typeof window === 'undefined') {
|
|
147
78
|
return null;
|
|
148
79
|
}
|
|
149
80
|
|
|
150
|
-
|
|
151
|
-
const accessToken = url.searchParams.get('access_token');
|
|
152
|
-
const sessionId = url.searchParams.get('session_id');
|
|
153
|
-
const expiresAt = url.searchParams.get('expires_at');
|
|
154
|
-
const state = url.searchParams.get('state');
|
|
155
|
-
const error = url.searchParams.get('error');
|
|
156
|
-
const errorDescription = url.searchParams.get('error_description');
|
|
157
|
-
|
|
158
|
-
// Check if this is an error callback
|
|
159
|
-
if (error) {
|
|
160
|
-
this.clearAuthState();
|
|
161
|
-
throw new OxyAuthenticationError(errorDescription || error);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Check if this is an auth callback
|
|
165
|
-
if (!accessToken || !sessionId) {
|
|
166
|
-
return null; // Not a callback
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Verify state to prevent CSRF attacks
|
|
170
|
-
const savedState = this.getStoredState();
|
|
171
|
-
if (!savedState || state !== savedState) {
|
|
172
|
-
this.clearAuthState();
|
|
173
|
-
throw new OxyAuthenticationError('Invalid state parameter. Possible CSRF attack.');
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Store tokens
|
|
177
|
-
this.storeTokens(accessToken, sessionId);
|
|
178
|
-
this.httpService.setTokens(accessToken);
|
|
179
|
-
|
|
180
|
-
// Build session response (minimal — full user data is fetched separately
|
|
181
|
-
// by the caller via getCurrentUser() once tokens are stored).
|
|
182
|
-
const session: SessionLoginResponse = {
|
|
183
|
-
sessionId,
|
|
184
|
-
deviceId: '', // Not available in redirect flow
|
|
185
|
-
expiresAt: expiresAt || new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
|
|
186
|
-
// Placeholder user — caller MUST fetch real user data via getCurrentUser()
|
|
187
|
-
// before exposing this session to the application. The empty id signals
|
|
188
|
-
// that the user payload has not yet been populated.
|
|
189
|
-
user: { id: '', username: '' },
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
// Clean up URL (remove auth parameters)
|
|
193
|
-
this.cleanAuthCallbackUrl(url);
|
|
194
|
-
|
|
195
|
-
// Clean up storage
|
|
196
|
-
this.clearAuthState();
|
|
197
|
-
|
|
198
|
-
return session;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Restore session from storage
|
|
203
|
-
*
|
|
204
|
-
* Attempts to restore a previously authenticated session from localStorage.
|
|
205
|
-
* Call this on app startup if handleAuthCallback() returns null.
|
|
206
|
-
*
|
|
207
|
-
* @returns True if session was restored, false otherwise
|
|
208
|
-
*
|
|
209
|
-
* @example
|
|
210
|
-
* ```typescript
|
|
211
|
-
* useEffect(() => {
|
|
212
|
-
* const session = oxyServices.handleAuthCallback();
|
|
213
|
-
* if (!session) {
|
|
214
|
-
* const restored = oxyServices.restoreSession();
|
|
215
|
-
* if (!restored) {
|
|
216
|
-
* // No session, user needs to sign in
|
|
217
|
-
* setShowLogin(true);
|
|
218
|
-
* }
|
|
219
|
-
* }
|
|
220
|
-
* }, []);
|
|
221
|
-
* ```
|
|
222
|
-
*/
|
|
223
|
-
restoreSession(): boolean {
|
|
224
|
-
if (typeof window === 'undefined') {
|
|
81
|
+
restoreSession(): boolean {
|
|
225
82
|
return false;
|
|
226
83
|
}
|
|
227
84
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if (token && sessionId) {
|
|
232
|
-
this.httpService.setTokens(token);
|
|
233
|
-
return true;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
return false;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Clear stored session
|
|
241
|
-
*
|
|
242
|
-
* Removes all authentication data from storage. Call this on logout.
|
|
243
|
-
*/
|
|
244
|
-
clearStoredSession(): void {
|
|
245
|
-
if (typeof window === 'undefined') {
|
|
246
|
-
return;
|
|
85
|
+
clearStoredSession(): void {
|
|
86
|
+
this.httpService.clearTokens();
|
|
247
87
|
}
|
|
248
88
|
|
|
249
|
-
|
|
250
|
-
localStorage.removeItem((this.constructor as any).SESSION_STORAGE_KEY);
|
|
251
|
-
this.httpService.clearTokens();
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Get stored session ID
|
|
256
|
-
*/
|
|
257
|
-
getStoredSessionId(): string | null {
|
|
258
|
-
if (typeof window === 'undefined') {
|
|
89
|
+
getStoredSessionId(): string | null {
|
|
259
90
|
return null;
|
|
260
91
|
}
|
|
261
92
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
redirectUri: string;
|
|
273
|
-
state: string;
|
|
274
|
-
nonce: string;
|
|
275
|
-
clientId: string;
|
|
276
|
-
}): string {
|
|
277
|
-
const url = new URL(`${(this.config.authWebUrl || (this.constructor as any).DEFAULT_AUTH_URL)}/${params.mode}`);
|
|
278
|
-
url.searchParams.set('redirect_uri', params.redirectUri);
|
|
279
|
-
url.searchParams.set('state', params.state);
|
|
280
|
-
url.searchParams.set('nonce', params.nonce);
|
|
281
|
-
url.searchParams.set('client_id', params.clientId);
|
|
282
|
-
url.searchParams.set('response_type', 'token');
|
|
283
|
-
return url.toString();
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Store tokens in localStorage
|
|
288
|
-
*
|
|
289
|
-
* @private
|
|
290
|
-
*/
|
|
291
|
-
public storeTokens(accessToken: string, sessionId: string): void {
|
|
292
|
-
if (typeof window === 'undefined') {
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
localStorage.setItem((this.constructor as any).TOKEN_STORAGE_KEY, accessToken);
|
|
297
|
-
localStorage.setItem((this.constructor as any).SESSION_STORAGE_KEY, sessionId);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* Generate cryptographically secure state for CSRF protection
|
|
302
|
-
*
|
|
303
|
-
* @private
|
|
304
|
-
*/
|
|
305
|
-
public generateState(): string {
|
|
306
|
-
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
307
|
-
return crypto.randomUUID();
|
|
308
|
-
}
|
|
309
|
-
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
|
|
310
|
-
const bytes = new Uint8Array(16);
|
|
311
|
-
crypto.getRandomValues(bytes);
|
|
312
|
-
return Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
|
|
313
|
-
}
|
|
314
|
-
throw new Error('No secure random source available for state generation');
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Generate nonce for replay attack prevention
|
|
319
|
-
*
|
|
320
|
-
* @private
|
|
321
|
-
*/
|
|
322
|
-
public generateNonce(): string {
|
|
323
|
-
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
324
|
-
return crypto.randomUUID();
|
|
93
|
+
public generateState(): string {
|
|
94
|
+
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
95
|
+
return crypto.randomUUID();
|
|
96
|
+
}
|
|
97
|
+
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
|
|
98
|
+
const bytes = new Uint8Array(16);
|
|
99
|
+
crypto.getRandomValues(bytes);
|
|
100
|
+
return Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
|
|
101
|
+
}
|
|
102
|
+
throw new Error('No secure random source available for state generation');
|
|
325
103
|
}
|
|
326
|
-
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
|
|
327
|
-
const bytes = new Uint8Array(16);
|
|
328
|
-
crypto.getRandomValues(bytes);
|
|
329
|
-
return Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
|
|
330
|
-
}
|
|
331
|
-
throw new Error('No secure random source available for nonce generation');
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
/**
|
|
335
|
-
* Store auth state in session storage
|
|
336
|
-
*
|
|
337
|
-
* @private
|
|
338
|
-
*/
|
|
339
|
-
public storeAuthState(state: string, nonce: string): void {
|
|
340
|
-
if (typeof window === 'undefined') {
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
sessionStorage.setItem((this.constructor as any).STATE_STORAGE_KEY, state);
|
|
345
|
-
sessionStorage.setItem((this.constructor as any).NONCE_STORAGE_KEY, nonce);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* Get stored state
|
|
350
|
-
*
|
|
351
|
-
* @private
|
|
352
|
-
*/
|
|
353
|
-
public getStoredState(): string | null {
|
|
354
|
-
if (typeof window === 'undefined') {
|
|
355
|
-
return null;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
return sessionStorage.getItem((this.constructor as any).STATE_STORAGE_KEY);
|
|
359
|
-
}
|
|
360
104
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
*
|
|
364
|
-
* @private
|
|
365
|
-
*/
|
|
366
|
-
public clearAuthState(): void {
|
|
367
|
-
if (typeof window === 'undefined') {
|
|
368
|
-
return;
|
|
105
|
+
public generateNonce(): string {
|
|
106
|
+
return this.generateState();
|
|
369
107
|
}
|
|
370
108
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
*/
|
|
381
|
-
public savePreAuthUrl(url: string): void {
|
|
382
|
-
if (typeof window === 'undefined') {
|
|
383
|
-
return;
|
|
109
|
+
public cleanAuthCallbackUrl(url: URL): void {
|
|
110
|
+
url.searchParams.delete('access_token');
|
|
111
|
+
url.searchParams.delete('session_id');
|
|
112
|
+
url.searchParams.delete('expires_at');
|
|
113
|
+
url.searchParams.delete('state');
|
|
114
|
+
url.searchParams.delete('nonce');
|
|
115
|
+
url.searchParams.delete('error');
|
|
116
|
+
url.searchParams.delete('error_description');
|
|
117
|
+
window.history.replaceState({}, '', url.toString());
|
|
384
118
|
}
|
|
385
|
-
|
|
386
|
-
sessionStorage.setItem((this.constructor as any).PRE_AUTH_URL_KEY, url);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
/**
|
|
390
|
-
* Clean authentication parameters from URL
|
|
391
|
-
*
|
|
392
|
-
* @private
|
|
393
|
-
*/
|
|
394
|
-
public cleanAuthCallbackUrl(url: URL): void {
|
|
395
|
-
// Remove auth parameters
|
|
396
|
-
url.searchParams.delete('access_token');
|
|
397
|
-
url.searchParams.delete('session_id');
|
|
398
|
-
url.searchParams.delete('expires_at');
|
|
399
|
-
url.searchParams.delete('state');
|
|
400
|
-
url.searchParams.delete('nonce');
|
|
401
|
-
url.searchParams.delete('error');
|
|
402
|
-
url.searchParams.delete('error_description');
|
|
403
|
-
|
|
404
|
-
// Update URL without reloading page
|
|
405
|
-
window.history.replaceState({}, '', url.toString());
|
|
406
|
-
}
|
|
407
119
|
};
|
|
408
120
|
}
|
|
409
121
|
|
|
410
|
-
// Export the mixin function as both named and default
|
|
411
122
|
export { OxyServicesRedirectAuthMixin as RedirectAuthMixin };
|