@memberjunction/ng-auth-services 2.129.0 → 2.130.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/dist/lib/IAuthProvider.d.ts +179 -17
- package/dist/lib/IAuthProvider.d.ts.map +1 -1
- package/dist/lib/auth-types.d.ts +351 -0
- package/dist/lib/auth-types.d.ts.map +1 -0
- package/dist/lib/auth-types.js +98 -0
- package/dist/lib/auth-types.js.map +1 -0
- package/dist/lib/mjexplorer-auth-base.service.d.ts +225 -49
- package/dist/lib/mjexplorer-auth-base.service.d.ts.map +1 -1
- package/dist/lib/mjexplorer-auth-base.service.js +189 -54
- package/dist/lib/mjexplorer-auth-base.service.js.map +1 -1
- package/dist/lib/providers/mjexplorer-auth0-provider.service.d.ts +69 -17
- package/dist/lib/providers/mjexplorer-auth0-provider.service.d.ts.map +1 -1
- package/dist/lib/providers/mjexplorer-auth0-provider.service.js +255 -75
- package/dist/lib/providers/mjexplorer-auth0-provider.service.js.map +1 -1
- package/dist/lib/providers/mjexplorer-msal-provider.service.d.ts +65 -16
- package/dist/lib/providers/mjexplorer-msal-provider.service.d.ts.map +1 -1
- package/dist/lib/providers/mjexplorer-msal-provider.service.js +345 -113
- package/dist/lib/providers/mjexplorer-msal-provider.service.js.map +1 -1
- package/dist/lib/providers/mjexplorer-okta-provider.service.d.ts +63 -30
- package/dist/lib/providers/mjexplorer-okta-provider.service.d.ts.map +1 -1
- package/dist/lib/providers/mjexplorer-okta-provider.service.js +273 -234
- package/dist/lib/providers/mjexplorer-okta-provider.service.js.map +1 -1
- package/dist/public-api.d.ts +1 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +2 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +3 -3
|
@@ -7,15 +7,19 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
var MJOktaProvider_1;
|
|
8
8
|
import { Injectable, Inject } from '@angular/core';
|
|
9
9
|
import { RegisterClass } from '@memberjunction/global';
|
|
10
|
-
import { BehaviorSubject, from } from 'rxjs';
|
|
11
10
|
import { MJAuthBase } from '../mjexplorer-auth-base.service';
|
|
12
11
|
import OktaAuth from '@okta/okta-auth-js';
|
|
12
|
+
import { AuthErrorType } from '../auth-types';
|
|
13
13
|
import * as i0 from "@angular/core";
|
|
14
14
|
// Prevent tree-shaking by explicitly referencing the class
|
|
15
15
|
export function LoadMJOktaProvider() {
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
* Okta authentication provider
|
|
18
|
+
* Okta authentication provider implementation - v3.0.0
|
|
19
|
+
*
|
|
20
|
+
* Implements the abstract methods from MJAuthBase to hide Okta-specific details.
|
|
21
|
+
* The key abstraction is that Okta stores the JWT in IDToken.idToken,
|
|
22
|
+
* but consumers never need to know this detail.
|
|
19
23
|
*/
|
|
20
24
|
let MJOktaProvider = class MJOktaProvider extends MJAuthBase {
|
|
21
25
|
static { MJOktaProvider_1 = this; }
|
|
@@ -23,8 +27,7 @@ let MJOktaProvider = class MJOktaProvider extends MJAuthBase {
|
|
|
23
27
|
static PROVIDER_TYPE = 'okta';
|
|
24
28
|
type = MJOktaProvider_1.PROVIDER_TYPE;
|
|
25
29
|
oktaAuth;
|
|
26
|
-
|
|
27
|
-
isRefreshing = false; // Flag to prevent re-triggering during refresh
|
|
30
|
+
isRefreshing = false;
|
|
28
31
|
/**
|
|
29
32
|
* Factory function to provide Angular dependencies required by Okta
|
|
30
33
|
* Stored as a static property for the factory to access without instantiation
|
|
@@ -33,11 +36,11 @@ let MJOktaProvider = class MJOktaProvider extends MJAuthBase {
|
|
|
33
36
|
{
|
|
34
37
|
provide: 'oktaConfig',
|
|
35
38
|
useValue: {
|
|
36
|
-
clientId: environment
|
|
37
|
-
domain: environment
|
|
38
|
-
issuer: environment
|
|
39
|
-
redirectUri: environment
|
|
40
|
-
scopes: environment
|
|
39
|
+
clientId: environment['OKTA_CLIENTID'],
|
|
40
|
+
domain: environment['OKTA_DOMAIN'],
|
|
41
|
+
issuer: environment['OKTA_ISSUER'] || `https://${environment['OKTA_DOMAIN']}/oauth2/default`,
|
|
42
|
+
redirectUri: environment['OKTA_REDIRECT_URI'] || window.location.origin,
|
|
43
|
+
scopes: environment['OKTA_SCOPES'] || ['openid', 'profile', 'email']
|
|
41
44
|
}
|
|
42
45
|
}
|
|
43
46
|
];
|
|
@@ -48,7 +51,7 @@ let MJOktaProvider = class MJOktaProvider extends MJAuthBase {
|
|
|
48
51
|
};
|
|
49
52
|
super(config);
|
|
50
53
|
this.oktaConfig = oktaConfig;
|
|
51
|
-
// Build configuration with defaults
|
|
54
|
+
// Build configuration with defaults
|
|
52
55
|
const oktaAuthConfig = {
|
|
53
56
|
clientId: this.oktaConfig.clientId,
|
|
54
57
|
redirectUri: this.oktaConfig.redirectUri || window.location.origin + '/callback',
|
|
@@ -61,25 +64,24 @@ let MJOktaProvider = class MJOktaProvider extends MJAuthBase {
|
|
|
61
64
|
this.oktaAuth = new OktaAuth(oktaAuthConfig);
|
|
62
65
|
// Listen for token events
|
|
63
66
|
this.oktaAuth.authStateManager.subscribe((authState) => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
//
|
|
67
|
+
const state = authState;
|
|
68
|
+
this.updateAuthState(state.isAuthenticated || false);
|
|
69
|
+
// Don't update user info if we're in the middle of a refresh
|
|
67
70
|
if (!this.isRefreshing) {
|
|
68
|
-
if (
|
|
69
|
-
this.
|
|
71
|
+
if (state.isAuthenticated && state.idToken) {
|
|
72
|
+
const userInfo = this.mapOktaTokenToStandard(state.idToken);
|
|
73
|
+
this.updateUserInfo(userInfo);
|
|
70
74
|
}
|
|
71
75
|
else {
|
|
72
|
-
this.
|
|
76
|
+
this.updateUserInfo(null);
|
|
73
77
|
}
|
|
74
78
|
}
|
|
75
79
|
});
|
|
76
|
-
// Initialize Okta authentication state
|
|
77
|
-
this.initializeOkta();
|
|
78
80
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
// ============================================================================
|
|
82
|
+
// LIFECYCLE METHODS
|
|
83
|
+
// ============================================================================
|
|
84
|
+
async initialize() {
|
|
83
85
|
// Check URL for logout indicator
|
|
84
86
|
const urlParams = new URLSearchParams(window.location.search);
|
|
85
87
|
const isPostLogout = urlParams.has('logout');
|
|
@@ -96,65 +98,42 @@ let MJOktaProvider = class MJOktaProvider extends MJAuthBase {
|
|
|
96
98
|
const authState = await this.oktaAuth.authStateManager.getAuthState();
|
|
97
99
|
if (authState?.isAuthenticated) {
|
|
98
100
|
this.updateAuthState(true);
|
|
99
|
-
|
|
100
|
-
this.isAuthenticated$.next(true);
|
|
101
|
-
// Get and emit the ID token with proper format
|
|
101
|
+
// Get and update user info
|
|
102
102
|
const idToken = await this.oktaAuth.tokenManager.get('idToken');
|
|
103
|
-
const accessToken = await this.oktaAuth.tokenManager.get('accessToken');
|
|
104
103
|
if (idToken) {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
idToken: idToken.idToken, // The actual token string
|
|
108
|
-
accessToken: accessToken?.accessToken,
|
|
109
|
-
};
|
|
110
|
-
this.userClaims$.next(claims);
|
|
104
|
+
const userInfo = this.mapOktaTokenToStandard(idToken);
|
|
105
|
+
this.updateUserInfo(userInfo);
|
|
111
106
|
}
|
|
112
107
|
}
|
|
113
108
|
}
|
|
114
109
|
catch (error) {
|
|
115
|
-
console.error('Okta
|
|
110
|
+
console.error('[Okta] Initialization redirect handling error:', error);
|
|
116
111
|
}
|
|
117
112
|
return; // Don't check for existing session after handling redirect
|
|
118
113
|
}
|
|
119
114
|
// Only check for existing session if not a redirect
|
|
120
|
-
// This prevents auto-login after logout
|
|
121
115
|
try {
|
|
122
|
-
const isAuthenticated =
|
|
123
|
-
if (isAuthenticated
|
|
116
|
+
const isAuthenticated = await this.oktaAuth.isAuthenticated();
|
|
117
|
+
if (isAuthenticated) {
|
|
124
118
|
// Double-check we actually have valid tokens
|
|
125
119
|
const idToken = await this.oktaAuth.tokenManager.get('idToken');
|
|
126
|
-
|
|
127
|
-
if (idToken && idToken.idToken) {
|
|
128
|
-
// Format claims to match what app.component expects
|
|
129
|
-
const claims = {
|
|
130
|
-
...idToken.claims,
|
|
131
|
-
idToken: idToken.idToken, // The actual token string
|
|
132
|
-
accessToken: accessToken?.accessToken,
|
|
133
|
-
};
|
|
134
|
-
this.userClaims$.next(claims);
|
|
135
|
-
// Ensure the authenticated state is properly set
|
|
136
|
-
this.authenticated = true;
|
|
120
|
+
if (idToken?.idToken) {
|
|
137
121
|
this.updateAuthState(true);
|
|
138
|
-
//
|
|
139
|
-
this.
|
|
122
|
+
// Update user info from cached token
|
|
123
|
+
const userInfo = this.mapOktaTokenToStandard(idToken);
|
|
124
|
+
this.updateUserInfo(userInfo);
|
|
140
125
|
}
|
|
141
126
|
else {
|
|
142
127
|
// No valid tokens, stay logged out
|
|
143
|
-
this.authenticated = false;
|
|
144
128
|
this.updateAuthState(false);
|
|
145
129
|
}
|
|
146
130
|
}
|
|
147
131
|
}
|
|
148
132
|
catch (error) {
|
|
149
|
-
|
|
150
|
-
console.warn('Error checking Okta authentication status:', error);
|
|
133
|
+
console.warn('[Okta] Error checking authentication status:', error);
|
|
151
134
|
}
|
|
152
135
|
}
|
|
153
|
-
|
|
154
|
-
// Convert Promise to Observable
|
|
155
|
-
return from(this.loginAsync(options));
|
|
156
|
-
}
|
|
157
|
-
async loginAsync(options) {
|
|
136
|
+
async loginInternal(options) {
|
|
158
137
|
try {
|
|
159
138
|
// Check if we're in a redirect callback
|
|
160
139
|
if (this.oktaAuth.isLoginRedirect()) {
|
|
@@ -163,12 +142,12 @@ let MJOktaProvider = class MJOktaProvider extends MJAuthBase {
|
|
|
163
142
|
}
|
|
164
143
|
// Start the login flow
|
|
165
144
|
await this.oktaAuth.signInWithRedirect({
|
|
166
|
-
originalUri: options?.targetUrl || '/',
|
|
145
|
+
originalUri: options?.['targetUrl'] || '/',
|
|
167
146
|
...options
|
|
168
147
|
});
|
|
169
148
|
}
|
|
170
149
|
catch (error) {
|
|
171
|
-
console.error('Okta
|
|
150
|
+
console.error('[Okta] Login error:', error);
|
|
172
151
|
throw error;
|
|
173
152
|
}
|
|
174
153
|
}
|
|
@@ -176,22 +155,10 @@ let MJOktaProvider = class MJOktaProvider extends MJAuthBase {
|
|
|
176
155
|
try {
|
|
177
156
|
// Clear the local authentication state immediately
|
|
178
157
|
this.updateAuthState(false);
|
|
179
|
-
this.
|
|
180
|
-
this.isAuthenticated$.next(false);
|
|
181
|
-
this.userClaims$.next(null);
|
|
158
|
+
this.updateUserInfo(null);
|
|
182
159
|
// Clear all tokens from local storage
|
|
183
160
|
await this.oktaAuth.tokenManager.clear();
|
|
184
|
-
// Get the ID token to pass to logout (needed for proper logout)
|
|
185
|
-
let idToken;
|
|
186
|
-
try {
|
|
187
|
-
const token = await this.oktaAuth.tokenManager.get('idToken');
|
|
188
|
-
idToken = token?.idToken;
|
|
189
|
-
}
|
|
190
|
-
catch {
|
|
191
|
-
// Token might already be cleared
|
|
192
|
-
}
|
|
193
161
|
// Sign out from Okta completely
|
|
194
|
-
// The clearTokensBeforeRedirect ensures tokens are cleared before redirect
|
|
195
162
|
await this.oktaAuth.signOut({
|
|
196
163
|
postLogoutRedirectUri: window.location.origin,
|
|
197
164
|
clearTokensBeforeRedirect: true
|
|
@@ -199,220 +166,292 @@ let MJOktaProvider = class MJOktaProvider extends MJAuthBase {
|
|
|
199
166
|
// Note: The signOut call will redirect the browser, so code after this won't execute
|
|
200
167
|
}
|
|
201
168
|
catch (error) {
|
|
202
|
-
console.error('Okta
|
|
169
|
+
console.error('[Okta] Logout error:', error);
|
|
203
170
|
// If logout fails, at least clear local state and reload
|
|
204
171
|
window.location.href = window.location.origin;
|
|
205
172
|
}
|
|
206
173
|
}
|
|
207
|
-
async
|
|
174
|
+
async handleCallback() {
|
|
208
175
|
try {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
220
|
-
// Check if tokens exist and are not expired
|
|
221
|
-
const idToken = await this.oktaAuth.tokenManager.get('idToken');
|
|
222
|
-
const accessToken = await this.oktaAuth.tokenManager.get('accessToken');
|
|
223
|
-
if (!idToken || !accessToken) {
|
|
224
|
-
console.warn('No tokens available to refresh');
|
|
225
|
-
this.isRefreshing = false;
|
|
226
|
-
// Don't update the claims observable to avoid triggering handleLogin
|
|
227
|
-
return from([null]);
|
|
228
|
-
}
|
|
229
|
-
// Attempt to renew tokens using the refresh token if available
|
|
230
|
-
// Note: For PKCE flow (which we're using), Okta will try to use refresh tokens if configured
|
|
231
|
-
// If refresh tokens aren't available, it will attempt silent authentication via iframe
|
|
232
|
-
const renewedTokens = await this.oktaAuth.token.renewTokens();
|
|
233
|
-
// Store the renewed tokens - this will trigger authStateManager but we'll ignore it
|
|
234
|
-
if (renewedTokens.idToken) {
|
|
235
|
-
this.oktaAuth.tokenManager.setTokens(renewedTokens);
|
|
236
|
-
// Update user claims with renewed token - but DON'T emit to userClaims$
|
|
237
|
-
// to avoid triggering handleLogin in app.component
|
|
238
|
-
const newIdToken = renewedTokens.idToken;
|
|
239
|
-
const claims = {
|
|
240
|
-
...newIdToken.claims,
|
|
241
|
-
idToken: newIdToken.idToken,
|
|
242
|
-
accessToken: renewedTokens.accessToken?.accessToken,
|
|
243
|
-
};
|
|
244
|
-
// Wait a moment before resetting the flag to ensure authStateManager event is handled
|
|
245
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
246
|
-
// Reset flag after tokens are set
|
|
247
|
-
this.isRefreshing = false;
|
|
248
|
-
// Return the claims directly without updating the BehaviorSubject
|
|
249
|
-
// This prevents the app component from re-initializing GraphQL
|
|
250
|
-
return from([claims]);
|
|
176
|
+
if (this.oktaAuth.isLoginRedirect()) {
|
|
177
|
+
await this.oktaAuth.handleLoginRedirect();
|
|
178
|
+
// After handling redirect, check if we're authenticated
|
|
179
|
+
const authState = await this.oktaAuth.authStateManager.getAuthState();
|
|
180
|
+
if (authState?.isAuthenticated) {
|
|
181
|
+
// Do a controlled reload after successful login
|
|
182
|
+
setTimeout(() => {
|
|
183
|
+
window.location.href = window.location.origin;
|
|
184
|
+
}, 100);
|
|
185
|
+
}
|
|
251
186
|
}
|
|
252
|
-
this.isRefreshing = false;
|
|
253
|
-
return from([null]);
|
|
254
187
|
}
|
|
255
188
|
catch (error) {
|
|
256
|
-
console.error('Okta
|
|
257
|
-
this.isRefreshing = false; // Reset flag on error
|
|
258
|
-
// Check if the error is due to expired session or no prompt allowed
|
|
259
|
-
if (error?.errorCode === 'login_required' ||
|
|
260
|
-
error?.message?.includes('not to prompt') ||
|
|
261
|
-
error?.message?.includes('login_required')) {
|
|
262
|
-
// Session has expired, user needs to re-authenticate
|
|
263
|
-
console.warn('Session expired - user needs to re-authenticate');
|
|
264
|
-
// Don't update the claims observable to avoid triggering handleLogin
|
|
265
|
-
return from([null]);
|
|
266
|
-
}
|
|
267
|
-
// For other errors, still throw
|
|
189
|
+
console.error('[Okta] Callback handling error:', error);
|
|
268
190
|
throw error;
|
|
269
191
|
}
|
|
270
192
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
193
|
+
// ============================================================================
|
|
194
|
+
// ABSTRACT METHOD IMPLEMENTATIONS (v3.0.0)
|
|
195
|
+
// ============================================================================
|
|
196
|
+
/**
|
|
197
|
+
* Extract ID token from Okta's storage
|
|
198
|
+
*
|
|
199
|
+
* Okta stores the JWT in IDToken.idToken
|
|
200
|
+
* This is the key abstraction - consumers never need to know about Okta's structure!
|
|
201
|
+
*/
|
|
202
|
+
async extractIdTokenInternal() {
|
|
277
203
|
try {
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
282
|
-
// Get user info from Okta
|
|
283
|
-
const user = await this.oktaAuth.getUser();
|
|
284
|
-
return user;
|
|
204
|
+
const idToken = await this.oktaAuth.tokenManager.get('idToken');
|
|
205
|
+
// Okta-specific detail: JWT is in idToken property
|
|
206
|
+
return idToken?.idToken || null;
|
|
285
207
|
}
|
|
286
208
|
catch (error) {
|
|
287
|
-
console.error('Okta
|
|
209
|
+
console.error('[Okta] Error extracting ID token:', error);
|
|
288
210
|
return null;
|
|
289
211
|
}
|
|
290
212
|
}
|
|
291
|
-
|
|
213
|
+
/**
|
|
214
|
+
* Extract complete token info from Okta
|
|
215
|
+
*
|
|
216
|
+
* Maps Okta's token structure to StandardAuthToken
|
|
217
|
+
*/
|
|
218
|
+
async extractTokenInfoInternal() {
|
|
292
219
|
try {
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
// The app.component expects claims.idToken to contain the actual token string
|
|
298
|
-
const claims = {
|
|
299
|
-
...idToken.claims,
|
|
300
|
-
idToken: idToken.idToken, // Add the actual token string
|
|
301
|
-
accessToken: authState.accessToken?.accessToken,
|
|
302
|
-
};
|
|
303
|
-
this.userClaims$.next(claims);
|
|
304
|
-
}
|
|
305
|
-
else {
|
|
306
|
-
this.userClaims$.next(null);
|
|
220
|
+
const idToken = await this.oktaAuth.tokenManager.get('idToken');
|
|
221
|
+
const accessToken = await this.oktaAuth.tokenManager.get('accessToken');
|
|
222
|
+
if (!idToken?.idToken) {
|
|
223
|
+
return null;
|
|
307
224
|
}
|
|
308
|
-
return
|
|
225
|
+
return {
|
|
226
|
+
idToken: idToken.idToken,
|
|
227
|
+
accessToken: accessToken?.accessToken,
|
|
228
|
+
expiresAt: idToken.expiresAt ? idToken.expiresAt * 1000 : 0, // Convert to milliseconds
|
|
229
|
+
scopes: idToken.scopes
|
|
230
|
+
};
|
|
309
231
|
}
|
|
310
232
|
catch (error) {
|
|
311
|
-
console.error('Okta
|
|
312
|
-
return
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
checkExpiredTokenError(error) {
|
|
316
|
-
// Check for Okta-specific token expiration errors
|
|
317
|
-
if (!error || typeof error !== 'string') {
|
|
318
|
-
return false;
|
|
233
|
+
console.error('[Okta] Error extracting token info:', error);
|
|
234
|
+
return null;
|
|
319
235
|
}
|
|
320
|
-
const errorLower = error.toLowerCase();
|
|
321
|
-
return errorLower.includes('token expired') ||
|
|
322
|
-
errorLower.includes('invalid_token') ||
|
|
323
|
-
errorLower.includes('token_expired') ||
|
|
324
|
-
errorLower.includes('unauthorized');
|
|
325
236
|
}
|
|
326
237
|
/**
|
|
327
|
-
*
|
|
238
|
+
* Extract user info from Okta claims
|
|
239
|
+
*
|
|
240
|
+
* Maps Okta's IDToken structure to StandardUserInfo
|
|
328
241
|
*/
|
|
329
|
-
async
|
|
242
|
+
async extractUserInfoInternal() {
|
|
330
243
|
try {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const authState = await this.oktaAuth.authStateManager.getAuthState();
|
|
335
|
-
if (authState?.isAuthenticated) {
|
|
336
|
-
// Do a controlled reload after successful login
|
|
337
|
-
// This ensures the app fully reinitializes with the authenticated state
|
|
338
|
-
setTimeout(() => {
|
|
339
|
-
window.location.href = window.location.origin;
|
|
340
|
-
}, 100);
|
|
341
|
-
}
|
|
244
|
+
const idToken = await this.oktaAuth.tokenManager.get('idToken');
|
|
245
|
+
if (!idToken) {
|
|
246
|
+
return null;
|
|
342
247
|
}
|
|
248
|
+
return this.mapOktaTokenToStandard(idToken);
|
|
343
249
|
}
|
|
344
250
|
catch (error) {
|
|
345
|
-
console.error('Okta
|
|
346
|
-
|
|
251
|
+
console.error('[Okta] Error extracting user info:', error);
|
|
252
|
+
return null;
|
|
347
253
|
}
|
|
348
254
|
}
|
|
349
255
|
/**
|
|
350
|
-
*
|
|
256
|
+
* Refresh token using Okta's token renewal
|
|
257
|
+
*
|
|
258
|
+
* Uses renewTokens() to get new tokens silently
|
|
351
259
|
*/
|
|
352
|
-
async
|
|
260
|
+
async refreshTokenInternal() {
|
|
353
261
|
try {
|
|
262
|
+
console.log('[Okta] Attempting to refresh token...');
|
|
263
|
+
// Set flag to prevent authStateManager from triggering updates
|
|
264
|
+
this.isRefreshing = true;
|
|
265
|
+
// First check if we're authenticated
|
|
266
|
+
const isAuthenticated = await this.oktaAuth.isAuthenticated();
|
|
267
|
+
if (!isAuthenticated) {
|
|
268
|
+
this.isRefreshing = false;
|
|
269
|
+
return {
|
|
270
|
+
success: false,
|
|
271
|
+
error: {
|
|
272
|
+
type: AuthErrorType.NO_ACTIVE_SESSION,
|
|
273
|
+
message: 'User is not authenticated',
|
|
274
|
+
userMessage: 'You need to log in to continue.'
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
// Check if tokens exist
|
|
279
|
+
const idToken = await this.oktaAuth.tokenManager.get('idToken');
|
|
354
280
|
const accessToken = await this.oktaAuth.tokenManager.get('accessToken');
|
|
355
|
-
|
|
281
|
+
if (!idToken || !accessToken) {
|
|
282
|
+
this.isRefreshing = false;
|
|
283
|
+
return {
|
|
284
|
+
success: false,
|
|
285
|
+
error: {
|
|
286
|
+
type: AuthErrorType.NO_ACTIVE_SESSION,
|
|
287
|
+
message: 'No tokens available to refresh',
|
|
288
|
+
userMessage: 'Session not found. Please log in again.'
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
// Attempt to renew tokens
|
|
293
|
+
const renewedTokens = await this.oktaAuth.token.renewTokens();
|
|
294
|
+
if (renewedTokens.idToken) {
|
|
295
|
+
// Store the renewed tokens
|
|
296
|
+
this.oktaAuth.tokenManager.setTokens(renewedTokens);
|
|
297
|
+
// Wait a moment before resetting the flag
|
|
298
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
299
|
+
this.isRefreshing = false;
|
|
300
|
+
const newIdToken = renewedTokens.idToken;
|
|
301
|
+
console.log('[Okta] Token refresh successful', {
|
|
302
|
+
expiresAt: newIdToken.expiresAt ? new Date(newIdToken.expiresAt * 1000).toISOString() : 'N/A'
|
|
303
|
+
});
|
|
304
|
+
const token = {
|
|
305
|
+
idToken: newIdToken.idToken || '',
|
|
306
|
+
accessToken: renewedTokens.accessToken?.accessToken,
|
|
307
|
+
expiresAt: newIdToken.expiresAt ? newIdToken.expiresAt * 1000 : 0,
|
|
308
|
+
scopes: newIdToken.scopes
|
|
309
|
+
};
|
|
310
|
+
return {
|
|
311
|
+
success: true,
|
|
312
|
+
token
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
this.isRefreshing = false;
|
|
316
|
+
return {
|
|
317
|
+
success: false,
|
|
318
|
+
error: {
|
|
319
|
+
type: AuthErrorType.NO_ACTIVE_SESSION,
|
|
320
|
+
message: 'Token renewal succeeded but no ID token returned',
|
|
321
|
+
userMessage: 'Session refresh failed. Please log in again.'
|
|
322
|
+
}
|
|
323
|
+
};
|
|
356
324
|
}
|
|
357
325
|
catch (error) {
|
|
358
|
-
console.error('
|
|
359
|
-
|
|
326
|
+
console.error('[Okta] Token refresh failed:', error);
|
|
327
|
+
this.isRefreshing = false;
|
|
328
|
+
return {
|
|
329
|
+
success: false,
|
|
330
|
+
error: this.classifyErrorInternal(error)
|
|
331
|
+
};
|
|
360
332
|
}
|
|
361
333
|
}
|
|
362
334
|
/**
|
|
363
|
-
*
|
|
335
|
+
* Classify Okta-specific errors into semantic types
|
|
336
|
+
*
|
|
337
|
+
* Maps Okta error patterns to AuthErrorType enum
|
|
364
338
|
*/
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
339
|
+
classifyErrorInternal(error) {
|
|
340
|
+
const errorObj = error;
|
|
341
|
+
const message = errorObj?.['message'] || 'Unknown error';
|
|
342
|
+
const errorCode = errorObj?.['errorCode'] || '';
|
|
343
|
+
// Check for specific Okta error patterns
|
|
344
|
+
if (errorCode === 'login_required' || message.includes('login_required')) {
|
|
345
|
+
return {
|
|
346
|
+
type: AuthErrorType.NO_ACTIVE_SESSION,
|
|
347
|
+
message,
|
|
348
|
+
originalError: error,
|
|
349
|
+
userMessage: 'You need to log in to continue.'
|
|
350
|
+
};
|
|
369
351
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
352
|
+
if (message.includes('not to prompt') || message.includes('consent_required')) {
|
|
353
|
+
return {
|
|
354
|
+
type: AuthErrorType.INTERACTION_REQUIRED,
|
|
355
|
+
message,
|
|
356
|
+
originalError: error,
|
|
357
|
+
userMessage: 'Additional authentication is required. Please log in again.'
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
if (errorCode === 'user_cancelled' || message.includes('user cancelled')) {
|
|
361
|
+
return {
|
|
362
|
+
type: AuthErrorType.USER_CANCELLED,
|
|
363
|
+
message,
|
|
364
|
+
originalError: error,
|
|
365
|
+
userMessage: 'Login was cancelled.'
|
|
366
|
+
};
|
|
373
367
|
}
|
|
368
|
+
if (message.includes('token expired') || message.includes('invalid_token') || message.includes('unauthorized')) {
|
|
369
|
+
return {
|
|
370
|
+
type: AuthErrorType.TOKEN_EXPIRED,
|
|
371
|
+
message,
|
|
372
|
+
originalError: error,
|
|
373
|
+
userMessage: 'Your session has expired. Please log in again.'
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
if (message.includes('network') || message.includes('fetch')) {
|
|
377
|
+
return {
|
|
378
|
+
type: AuthErrorType.NETWORK_ERROR,
|
|
379
|
+
message,
|
|
380
|
+
originalError: error,
|
|
381
|
+
userMessage: 'Network error. Please check your connection and try again.'
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
// Default to unknown error
|
|
385
|
+
return {
|
|
386
|
+
type: AuthErrorType.UNKNOWN_ERROR,
|
|
387
|
+
message,
|
|
388
|
+
originalError: error,
|
|
389
|
+
userMessage: 'An unexpected error occurred. Please try again.'
|
|
390
|
+
};
|
|
374
391
|
}
|
|
375
|
-
//
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
+
// ============================================================================
|
|
393
|
+
// HELPER METHODS
|
|
394
|
+
// ============================================================================
|
|
395
|
+
/**
|
|
396
|
+
* Map Okta IDToken to StandardUserInfo
|
|
397
|
+
*/
|
|
398
|
+
mapOktaTokenToStandard(idToken) {
|
|
399
|
+
const claims = idToken.claims;
|
|
400
|
+
return {
|
|
401
|
+
id: claims.sub || '',
|
|
402
|
+
email: claims.email || '',
|
|
403
|
+
name: claims.name || '',
|
|
404
|
+
givenName: claims.given_name,
|
|
405
|
+
familyName: claims.family_name,
|
|
406
|
+
preferredUsername: claims.preferred_username,
|
|
407
|
+
emailVerified: claims.email_verified
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Get profile picture URL from Okta
|
|
412
|
+
*
|
|
413
|
+
* Okta may include picture URL in user claims, similar to Auth0.
|
|
414
|
+
* If available, we can also fetch from Okta's /userinfo endpoint.
|
|
415
|
+
*/
|
|
416
|
+
async getProfilePictureUrlInternal() {
|
|
417
|
+
try {
|
|
418
|
+
const idToken = await this.oktaAuth.tokenManager.get('idToken');
|
|
419
|
+
if (!idToken) {
|
|
420
|
+
return null;
|
|
392
421
|
}
|
|
393
|
-
|
|
394
|
-
|
|
422
|
+
// Check if picture is in claims
|
|
423
|
+
const pictureUrl = idToken.claims.picture;
|
|
424
|
+
if (pictureUrl) {
|
|
425
|
+
return pictureUrl;
|
|
395
426
|
}
|
|
427
|
+
// Alternatively, fetch from userinfo endpoint
|
|
428
|
+
const user = await this.oktaAuth.getUser();
|
|
429
|
+
return user?.picture || null;
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
console.error('[Okta] Error getting profile picture:', error);
|
|
433
|
+
return null;
|
|
396
434
|
}
|
|
397
|
-
// Check authentication status
|
|
398
|
-
const isAuthenticated = this.oktaAuth ? await this.oktaAuth.isAuthenticated() : false;
|
|
399
|
-
this.updateAuthState(isAuthenticated);
|
|
400
|
-
}
|
|
401
|
-
async loginInternal(options) {
|
|
402
|
-
await this.loginAsync(options);
|
|
403
435
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
436
|
+
/**
|
|
437
|
+
* Handle session expiry - no-op for Okta
|
|
438
|
+
*
|
|
439
|
+
* Okta uses refresh tokens, so it doesn't need interactive re-authentication
|
|
440
|
+
* when tokens expire. If refresh fails, the base class will throw an error
|
|
441
|
+
* and the user must log out/in manually.
|
|
442
|
+
*/
|
|
443
|
+
async handleSessionExpiryInternal() {
|
|
444
|
+
// No-op - Okta doesn't need interactive re-auth
|
|
445
|
+
return;
|
|
409
446
|
}
|
|
447
|
+
// ============================================================================
|
|
448
|
+
// CONFIGURATION
|
|
449
|
+
// ============================================================================
|
|
410
450
|
getRequiredConfig() {
|
|
411
451
|
return ['clientId', 'domain'];
|
|
412
452
|
}
|
|
413
|
-
validateConfig(
|
|
414
|
-
|
|
415
|
-
return _config.clientId && (_config.domain || _config.issuer);
|
|
453
|
+
validateConfig(config) {
|
|
454
|
+
return !!(config['clientId'] && (config['domain'] || config['issuer']));
|
|
416
455
|
}
|
|
417
456
|
static ɵfac = function MJOktaProvider_Factory(t) { return new (t || MJOktaProvider)(i0.ɵɵinject('oktaConfig')); };
|
|
418
457
|
static ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: MJOktaProvider, factory: MJOktaProvider.ɵfac, providedIn: 'root' });
|