@memberjunction/server 2.90.0 → 2.92.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/AuthProviderFactory.d.ts +24 -0
- package/dist/auth/AuthProviderFactory.d.ts.map +1 -0
- package/dist/auth/AuthProviderFactory.js +82 -0
- package/dist/auth/AuthProviderFactory.js.map +1 -0
- package/dist/auth/BaseAuthProvider.d.ts +18 -0
- package/dist/auth/BaseAuthProvider.d.ts.map +1 -0
- package/dist/auth/BaseAuthProvider.js +42 -0
- package/dist/auth/BaseAuthProvider.js.map +1 -0
- package/dist/auth/IAuthProvider.d.ts +13 -0
- package/dist/auth/IAuthProvider.d.ts.map +1 -0
- package/dist/auth/IAuthProvider.js +2 -0
- package/dist/auth/IAuthProvider.js.map +1 -0
- package/dist/auth/__tests__/backward-compatibility.test.d.ts +2 -0
- package/dist/auth/__tests__/backward-compatibility.test.d.ts.map +1 -0
- package/dist/auth/__tests__/backward-compatibility.test.js +135 -0
- package/dist/auth/__tests__/backward-compatibility.test.js.map +1 -0
- package/dist/auth/index.d.ts +22 -7
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +65 -32
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/initializeProviders.d.ts +2 -0
- package/dist/auth/initializeProviders.d.ts.map +1 -0
- package/dist/auth/initializeProviders.js +23 -0
- package/dist/auth/initializeProviders.js.map +1 -0
- package/dist/auth/providers/Auth0Provider.d.ts +9 -0
- package/dist/auth/providers/Auth0Provider.d.ts.map +1 -0
- package/dist/auth/providers/Auth0Provider.js +42 -0
- package/dist/auth/providers/Auth0Provider.js.map +1 -0
- package/dist/auth/providers/CognitoProvider.d.ts +9 -0
- package/dist/auth/providers/CognitoProvider.d.ts.map +1 -0
- package/dist/auth/providers/CognitoProvider.js +46 -0
- package/dist/auth/providers/CognitoProvider.js.map +1 -0
- package/dist/auth/providers/GoogleProvider.d.ts +9 -0
- package/dist/auth/providers/GoogleProvider.d.ts.map +1 -0
- package/dist/auth/providers/GoogleProvider.js +41 -0
- package/dist/auth/providers/GoogleProvider.js.map +1 -0
- package/dist/auth/providers/MSALProvider.d.ts +9 -0
- package/dist/auth/providers/MSALProvider.d.ts.map +1 -0
- package/dist/auth/providers/MSALProvider.js +42 -0
- package/dist/auth/providers/MSALProvider.js.map +1 -0
- package/dist/auth/providers/OktaProvider.d.ts +9 -0
- package/dist/auth/providers/OktaProvider.d.ts.map +1 -0
- package/dist/auth/providers/OktaProvider.js +42 -0
- package/dist/auth/providers/OktaProvider.js.map +1 -0
- package/dist/config.d.ts +97 -21
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +13 -6
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +25 -17
- package/dist/context.js.map +1 -1
- package/dist/generated/generated.d.ts +6 -0
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +31 -0
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +1 -1
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +5 -4
- package/dist/generic/ResolverBase.js.map +1 -1
- package/package.json +39 -39
- package/src/auth/AuthProviderFactory.ts +152 -0
- package/src/auth/BaseAuthProvider.ts +71 -0
- package/src/auth/IAuthProvider.ts +49 -0
- package/src/auth/__tests__/backward-compatibility.test.ts +183 -0
- package/src/auth/index.ts +104 -36
- package/src/auth/initializeProviders.ts +31 -0
- package/src/auth/providers/Auth0Provider.ts +45 -0
- package/src/auth/providers/CognitoProvider.ts +50 -0
- package/src/auth/providers/GoogleProvider.ts +45 -0
- package/src/auth/providers/MSALProvider.ts +45 -0
- package/src/auth/providers/OktaProvider.ts +46 -0
- package/src/config.ts +14 -10
- package/src/context.ts +40 -17
- package/src/generated/generated.ts +19 -0
- package/src/generic/ResolverBase.ts +9 -4
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
|
|
2
|
+
import { AuthProviderFactory } from '../AuthProviderFactory';
|
|
3
|
+
import { IAuthProvider } from '../IAuthProvider';
|
|
4
|
+
import { initializeAuthProviders } from '../initializeProviders';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Test suite for backward compatibility of the new auth provider system
|
|
8
|
+
*/
|
|
9
|
+
describe('Authentication Provider Backward Compatibility', () => {
|
|
10
|
+
let factory: AuthProviderFactory;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
factory = AuthProviderFactory.getInstance();
|
|
14
|
+
factory.clear();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
factory.clear();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('Legacy Configuration Support', () => {
|
|
22
|
+
it('should create MSAL provider from legacy config', () => {
|
|
23
|
+
// Simulate legacy environment variables
|
|
24
|
+
process.env.TENANT_ID = 'test-tenant-id';
|
|
25
|
+
process.env.WEB_CLIENT_ID = 'test-client-id';
|
|
26
|
+
|
|
27
|
+
// Initialize with legacy config
|
|
28
|
+
initializeAuthProviders();
|
|
29
|
+
|
|
30
|
+
// Check that MSAL provider was created
|
|
31
|
+
const msalProvider = factory.getByName('msal');
|
|
32
|
+
expect(msalProvider).toBeDefined();
|
|
33
|
+
expect(msalProvider?.issuer).toContain('test-tenant-id');
|
|
34
|
+
expect(msalProvider?.audience).toBe('test-client-id');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should create Auth0 provider from legacy config', () => {
|
|
38
|
+
// Simulate legacy environment variables
|
|
39
|
+
process.env.AUTH0_DOMAIN = 'test.auth0.com';
|
|
40
|
+
process.env.AUTH0_CLIENT_ID = 'auth0-client-id';
|
|
41
|
+
process.env.AUTH0_CLIENT_SECRET = 'auth0-secret';
|
|
42
|
+
|
|
43
|
+
// Initialize with legacy config
|
|
44
|
+
initializeAuthProviders();
|
|
45
|
+
|
|
46
|
+
// Check that Auth0 provider was created
|
|
47
|
+
const auth0Provider = factory.getByName('auth0');
|
|
48
|
+
expect(auth0Provider).toBeDefined();
|
|
49
|
+
expect(auth0Provider?.issuer).toBe('https://test.auth0.com/');
|
|
50
|
+
expect(auth0Provider?.audience).toBe('auth0-client-id');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
describe('Provider Registry Functionality', () => {
|
|
56
|
+
it('should find providers by issuer with different formats', () => {
|
|
57
|
+
// Register a test provider
|
|
58
|
+
const testProvider = {
|
|
59
|
+
name: 'test',
|
|
60
|
+
issuer: 'https://test.provider.com/oauth2',
|
|
61
|
+
audience: 'test-audience',
|
|
62
|
+
jwksUri: 'https://test.provider.com/.well-known/jwks.json',
|
|
63
|
+
validateConfig: () => true,
|
|
64
|
+
getSigningKey: jest.fn(),
|
|
65
|
+
extractUserInfo: jest.fn(),
|
|
66
|
+
matchesIssuer: (issuer: string) => {
|
|
67
|
+
const normalized = issuer.toLowerCase().replace(/\/$/, '');
|
|
68
|
+
return normalized === 'https://test.provider.com/oauth2';
|
|
69
|
+
}
|
|
70
|
+
} as IAuthProvider;
|
|
71
|
+
|
|
72
|
+
factory.register(testProvider);
|
|
73
|
+
|
|
74
|
+
// Test with exact match
|
|
75
|
+
expect(factory.getByIssuer('https://test.provider.com/oauth2')).toBe(testProvider);
|
|
76
|
+
|
|
77
|
+
// Test with trailing slash
|
|
78
|
+
expect(factory.getByIssuer('https://test.provider.com/oauth2/')).toBe(testProvider);
|
|
79
|
+
|
|
80
|
+
// Test with different case
|
|
81
|
+
expect(factory.getByIssuer('https://TEST.PROVIDER.COM/oauth2')).toBe(testProvider);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should cache issuer lookups for performance', () => {
|
|
85
|
+
const testProvider = {
|
|
86
|
+
name: 'test',
|
|
87
|
+
issuer: 'https://test.provider.com',
|
|
88
|
+
audience: 'test',
|
|
89
|
+
jwksUri: 'https://test.provider.com/jwks',
|
|
90
|
+
validateConfig: () => true,
|
|
91
|
+
getSigningKey: jest.fn(),
|
|
92
|
+
extractUserInfo: jest.fn(),
|
|
93
|
+
matchesIssuer: jest.fn((issuer: string): boolean => issuer === 'https://test.provider.com')
|
|
94
|
+
} as IAuthProvider;
|
|
95
|
+
|
|
96
|
+
factory.register(testProvider);
|
|
97
|
+
|
|
98
|
+
// First lookup
|
|
99
|
+
factory.getByIssuer('https://test.provider.com');
|
|
100
|
+
expect(testProvider.matchesIssuer).toHaveBeenCalledTimes(1);
|
|
101
|
+
|
|
102
|
+
// Second lookup should use cache
|
|
103
|
+
factory.getByIssuer('https://test.provider.com');
|
|
104
|
+
expect(testProvider.matchesIssuer).toHaveBeenCalledTimes(1);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('User Info Extraction', () => {
|
|
109
|
+
it('should extract user info from different token formats', () => {
|
|
110
|
+
// Test MSAL token format
|
|
111
|
+
const msalPayload = {
|
|
112
|
+
iss: 'https://login.microsoftonline.com/tenant/v2.0',
|
|
113
|
+
email: 'user@example.com',
|
|
114
|
+
given_name: 'John',
|
|
115
|
+
family_name: 'Doe',
|
|
116
|
+
name: 'John Doe',
|
|
117
|
+
preferred_username: 'john.doe@example.com'
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Test Auth0 token format
|
|
121
|
+
const auth0Payload = {
|
|
122
|
+
iss: 'https://test.auth0.com/',
|
|
123
|
+
email: 'user@example.com',
|
|
124
|
+
given_name: 'Jane',
|
|
125
|
+
family_name: 'Smith',
|
|
126
|
+
name: 'Jane Smith'
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// Test Okta token format
|
|
130
|
+
const oktaPayload = {
|
|
131
|
+
iss: 'https://test.okta.com/oauth2/default',
|
|
132
|
+
email: 'user@example.com',
|
|
133
|
+
given_name: 'Bob',
|
|
134
|
+
family_name: 'Johnson',
|
|
135
|
+
name: 'Bob Johnson',
|
|
136
|
+
preferred_username: 'bob.johnson'
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Initialize providers
|
|
140
|
+
initializeAuthProviders();
|
|
141
|
+
|
|
142
|
+
// Test extraction for each provider type
|
|
143
|
+
const msalProvider = factory.getByIssuer(msalPayload.iss);
|
|
144
|
+
if (msalProvider) {
|
|
145
|
+
const msalUserInfo = msalProvider.extractUserInfo(msalPayload);
|
|
146
|
+
expect(msalUserInfo.email).toBe('user@example.com');
|
|
147
|
+
expect(msalUserInfo.firstName).toBe('John');
|
|
148
|
+
expect(msalUserInfo.lastName).toBe('Doe');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const auth0Provider = factory.getByIssuer(auth0Payload.iss);
|
|
152
|
+
if (auth0Provider) {
|
|
153
|
+
const auth0UserInfo = auth0Provider.extractUserInfo(auth0Payload);
|
|
154
|
+
expect(auth0UserInfo.email).toBe('user@example.com');
|
|
155
|
+
expect(auth0UserInfo.firstName).toBe('Jane');
|
|
156
|
+
expect(auth0UserInfo.lastName).toBe('Smith');
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe('Error Handling', () => {
|
|
162
|
+
it('should handle missing provider gracefully', () => {
|
|
163
|
+
const unknownIssuer = 'https://unknown.provider.com';
|
|
164
|
+
const provider = factory.getByIssuer(unknownIssuer);
|
|
165
|
+
expect(provider).toBeUndefined();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('should validate provider configuration', () => {
|
|
169
|
+
const invalidProvider = {
|
|
170
|
+
name: 'invalid',
|
|
171
|
+
issuer: '', // Invalid: empty issuer
|
|
172
|
+
audience: 'test',
|
|
173
|
+
jwksUri: 'https://test.com/jwks',
|
|
174
|
+
validateConfig: () => false,
|
|
175
|
+
getSigningKey: jest.fn(),
|
|
176
|
+
extractUserInfo: jest.fn(),
|
|
177
|
+
matchesIssuer: jest.fn()
|
|
178
|
+
} as IAuthProvider;
|
|
179
|
+
|
|
180
|
+
expect(() => factory.register(invalidProvider)).toThrow();
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
});
|
package/src/auth/index.ts
CHANGED
|
@@ -1,33 +1,28 @@
|
|
|
1
|
-
import { JwtHeader, SigningKeyCallback } from 'jsonwebtoken';
|
|
2
|
-
import
|
|
3
|
-
import { auth0Domain, auth0WebClientID, configInfo, tenantID, webClientID } from '../config.js';
|
|
1
|
+
import { JwtHeader, SigningKeyCallback, JwtPayload } from 'jsonwebtoken';
|
|
2
|
+
import { configInfo } from '../config.js';
|
|
4
3
|
import { UserCache } from '@memberjunction/sqlserver-dataprovider';
|
|
5
4
|
import sql from 'mssql';
|
|
6
5
|
import { Metadata, RoleInfo, UserInfo } from '@memberjunction/core';
|
|
7
6
|
import { NewUserBase } from './newUsers.js';
|
|
8
7
|
import { MJGlobal } from '@memberjunction/global';
|
|
9
8
|
import { UserEntity, UserEntityType } from '@memberjunction/core-entities';
|
|
9
|
+
import { AuthProviderFactory } from './AuthProviderFactory.js';
|
|
10
|
+
import { initializeAuthProviders } from './initializeProviders.js';
|
|
10
11
|
|
|
11
12
|
export { TokenExpiredError } from './tokenExpiredError.js';
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const missingAuth0Config = !auth0Domain || !auth0WebClientID;
|
|
13
|
+
export { IAuthProvider } from './IAuthProvider.js';
|
|
14
|
+
export { AuthProviderFactory } from './AuthProviderFactory.js';
|
|
15
15
|
|
|
16
16
|
// This is a hard-coded forever constant due to internal migrations
|
|
17
17
|
const SYSTEM_USER_ID = 'ecafccec-6a37-ef11-86d4-000d3a4e707e';
|
|
18
18
|
|
|
19
19
|
class MissingAuthError extends Error {
|
|
20
20
|
constructor() {
|
|
21
|
-
super('
|
|
21
|
+
super('No authentication providers configured. Please configure at least one auth provider in mj.config.cjs');
|
|
22
22
|
this.name = 'MissingAuthError';
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
const issuers = {
|
|
27
|
-
azure: `https://login.microsoftonline.com/${tenantID}/v2.0`,
|
|
28
|
-
auth0: `https://${auth0Domain}/`,
|
|
29
|
-
};
|
|
30
|
-
|
|
31
26
|
const refreshUserCache = async (dataSource?: sql.ConnectionPool) => {
|
|
32
27
|
const startTime: number = Date.now();
|
|
33
28
|
await UserCache.Instance.Refresh(dataSource);
|
|
@@ -51,16 +46,40 @@ const refreshUserCache = async (dataSource?: sql.ConnectionPool) => {
|
|
|
51
46
|
);
|
|
52
47
|
};
|
|
53
48
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Gets validation options for a specific issuer
|
|
51
|
+
* This maintains backward compatibility with the old structure
|
|
52
|
+
*/
|
|
53
|
+
export const getValidationOptions = (issuer: string): { audience: string; jwksUri: string } | undefined => {
|
|
54
|
+
const factory = AuthProviderFactory.getInstance();
|
|
55
|
+
const provider = factory.getByIssuer(issuer);
|
|
56
|
+
|
|
57
|
+
if (!provider) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
audience: provider.audience,
|
|
63
|
+
jwksUri: provider.jwksUri
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Backward compatible validationOptions object
|
|
69
|
+
* @deprecated Use getValidationOptions() or AuthProviderRegistry instead
|
|
70
|
+
*/
|
|
71
|
+
export const validationOptions: Record<string, { audience: string; jwksUri: string }> = new Proxy({}, {
|
|
72
|
+
get: (target, prop: string) => {
|
|
73
|
+
return getValidationOptions(prop);
|
|
58
74
|
},
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
jwksUri: `https://login.microsoftonline.com/${tenantID}/discovery/v2.0/keys`,
|
|
75
|
+
has: (target, prop: string) => {
|
|
76
|
+
return getValidationOptions(prop) !== undefined;
|
|
62
77
|
},
|
|
63
|
-
|
|
78
|
+
ownKeys: () => {
|
|
79
|
+
const factory = AuthProviderFactory.getInstance();
|
|
80
|
+
return factory.getAllProviders().map(p => p.issuer);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
64
83
|
|
|
65
84
|
export class UserPayload {
|
|
66
85
|
aio?: string;
|
|
@@ -78,31 +97,77 @@ export class UserPayload {
|
|
|
78
97
|
tid?: string;
|
|
79
98
|
uti?: string;
|
|
80
99
|
ver?: string;
|
|
81
|
-
|
|
100
|
+
email?: string;
|
|
101
|
+
given_name?: string;
|
|
102
|
+
family_name?: string;
|
|
103
|
+
[key: string]: unknown; // Allow additional claims
|
|
82
104
|
}
|
|
83
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Gets signing keys for JWT validation
|
|
108
|
+
*/
|
|
84
109
|
export const getSigningKeys = (issuer: string) => (header: JwtHeader, cb: SigningKeyCallback) => {
|
|
85
|
-
|
|
86
|
-
|
|
110
|
+
const factory = AuthProviderFactory.getInstance();
|
|
111
|
+
|
|
112
|
+
// Initialize providers if not already done
|
|
113
|
+
if (!factory.hasProviders()) {
|
|
114
|
+
initializeAuthProviders();
|
|
87
115
|
}
|
|
88
116
|
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
117
|
+
const provider = factory.getByIssuer(issuer);
|
|
118
|
+
|
|
119
|
+
if (!provider) {
|
|
120
|
+
// Check if we have any providers at all
|
|
121
|
+
if (!factory.hasProviders()) {
|
|
122
|
+
throw new MissingAuthError();
|
|
123
|
+
}
|
|
124
|
+
throw new Error(`No authentication provider found for issuer: ${issuer}`);
|
|
92
125
|
}
|
|
93
|
-
|
|
94
|
-
|
|
126
|
+
|
|
127
|
+
provider.getSigningKey(header, cb);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Extracts user information from JWT payload using the appropriate provider
|
|
132
|
+
*/
|
|
133
|
+
export const extractUserInfoFromPayload = (payload: JwtPayload): {
|
|
134
|
+
email?: string;
|
|
135
|
+
firstName?: string;
|
|
136
|
+
lastName?: string;
|
|
137
|
+
fullName?: string;
|
|
138
|
+
preferredUsername?: string;
|
|
139
|
+
} => {
|
|
140
|
+
const factory = AuthProviderFactory.getInstance();
|
|
141
|
+
const issuer = payload.iss;
|
|
142
|
+
|
|
143
|
+
if (!issuer) {
|
|
144
|
+
// Fallback to default extraction
|
|
145
|
+
const preferredUsername = payload.preferred_username as string | undefined;
|
|
146
|
+
return {
|
|
147
|
+
email: payload.email as string | undefined || preferredUsername,
|
|
148
|
+
firstName: payload.given_name as string | undefined,
|
|
149
|
+
lastName: payload.family_name as string | undefined,
|
|
150
|
+
fullName: payload.name as string | undefined,
|
|
151
|
+
preferredUsername
|
|
152
|
+
};
|
|
95
153
|
}
|
|
96
|
-
|
|
97
|
-
|
|
154
|
+
|
|
155
|
+
const provider = factory.getByIssuer(issuer);
|
|
156
|
+
|
|
157
|
+
if (!provider) {
|
|
158
|
+
// Fallback to default extraction
|
|
159
|
+
const fullName = payload.name as string | undefined;
|
|
160
|
+
const preferredUsername = payload.preferred_username as string | undefined;
|
|
161
|
+
return {
|
|
162
|
+
email: payload.email as string | undefined || preferredUsername,
|
|
163
|
+
firstName: payload.given_name as string | undefined || fullName?.split(' ')[0],
|
|
164
|
+
lastName: payload.family_name as string | undefined || fullName?.split(' ')[1] || fullName?.split(' ')[0],
|
|
165
|
+
fullName,
|
|
166
|
+
preferredUsername
|
|
167
|
+
};
|
|
98
168
|
}
|
|
99
169
|
|
|
100
|
-
|
|
101
|
-
.getSigningKey(header.kid)
|
|
102
|
-
.then((key) => {
|
|
103
|
-
cb(null, 'publicKey' in key ? key.publicKey : key.rsaPublicKey);
|
|
104
|
-
})
|
|
105
|
-
.catch((err) => console.error(err));
|
|
170
|
+
return provider.extractUserInfo(payload);
|
|
106
171
|
};
|
|
107
172
|
|
|
108
173
|
export const getSystemUser = async (dataSource?: sql.ConnectionPool, attemptCacheUpdateIfNeeded: boolean = true): Promise<UserInfo> => {
|
|
@@ -200,3 +265,6 @@ export const verifyUserRecord = async (
|
|
|
200
265
|
|
|
201
266
|
return user;
|
|
202
267
|
};
|
|
268
|
+
|
|
269
|
+
// Initialize providers on module load
|
|
270
|
+
initializeAuthProviders();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { configInfo } from '../config.js';
|
|
2
|
+
import { AuthProviderConfig, LogError, LogStatus } from '@memberjunction/core';
|
|
3
|
+
import { AuthProviderFactory } from './AuthProviderFactory.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Initialize authentication providers from configuration
|
|
7
|
+
*/
|
|
8
|
+
export function initializeAuthProviders(): void {
|
|
9
|
+
const factory = AuthProviderFactory.getInstance();
|
|
10
|
+
|
|
11
|
+
// Clear any existing providers
|
|
12
|
+
factory.clear();
|
|
13
|
+
|
|
14
|
+
// Initialize providers from authProviders config
|
|
15
|
+
if (configInfo.authProviders && configInfo.authProviders.length > 0) {
|
|
16
|
+
for (const providerConfig of configInfo.authProviders) {
|
|
17
|
+
try {
|
|
18
|
+
const provider = AuthProviderFactory.createProvider(providerConfig as AuthProviderConfig);
|
|
19
|
+
factory.register(provider);
|
|
20
|
+
LogStatus(`Registered auth provider: ${provider.name} (type: ${providerConfig.type})`);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
LogError(`Failed to initialize auth provider ${providerConfig.name}: ${error}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Validate we have at least one provider
|
|
28
|
+
if (!factory.hasProviders()) {
|
|
29
|
+
LogError('No authentication providers configured. Please configure authProviders array in mj.config.cjs');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { JwtPayload } from 'jsonwebtoken';
|
|
2
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
3
|
+
import { AuthProviderConfig, AuthUserInfo } from '@memberjunction/core';
|
|
4
|
+
import { BaseAuthProvider } from '../BaseAuthProvider.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Auth0 authentication provider implementation
|
|
8
|
+
*/
|
|
9
|
+
@RegisterClass(BaseAuthProvider, 'auth0')
|
|
10
|
+
export class Auth0Provider extends BaseAuthProvider {
|
|
11
|
+
constructor(config: AuthProviderConfig) {
|
|
12
|
+
super(config);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Extracts user information from Auth0 JWT payload
|
|
17
|
+
*/
|
|
18
|
+
extractUserInfo(payload: JwtPayload): AuthUserInfo {
|
|
19
|
+
// Auth0 uses standard OIDC claims
|
|
20
|
+
const email = payload.email as string | undefined;
|
|
21
|
+
const fullName = payload.name as string | undefined;
|
|
22
|
+
const firstName = payload.given_name as string | undefined;
|
|
23
|
+
const lastName = payload.family_name as string | undefined;
|
|
24
|
+
const preferredUsername = payload.preferred_username as string | undefined || email;
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
email,
|
|
28
|
+
firstName: firstName || fullName?.split(' ')[0],
|
|
29
|
+
lastName: lastName || fullName?.split(' ')[1] || fullName?.split(' ')[0],
|
|
30
|
+
fullName,
|
|
31
|
+
preferredUsername
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Validates Auth0-specific configuration
|
|
37
|
+
*/
|
|
38
|
+
validateConfig(): boolean {
|
|
39
|
+
const baseValid = super.validateConfig();
|
|
40
|
+
const hasClientId = !!this.config.clientId;
|
|
41
|
+
const hasDomain = !!this.config.domain;
|
|
42
|
+
|
|
43
|
+
return baseValid && hasClientId && hasDomain;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { JwtPayload } from 'jsonwebtoken';
|
|
2
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
3
|
+
import { AuthProviderConfig, AuthUserInfo } from '@memberjunction/core';
|
|
4
|
+
import { BaseAuthProvider } from '../BaseAuthProvider.js';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* AWS Cognito authentication provider implementation
|
|
9
|
+
*/
|
|
10
|
+
@RegisterClass(BaseAuthProvider, 'cognito')
|
|
11
|
+
export class CognitoProvider extends BaseAuthProvider {
|
|
12
|
+
constructor(config: AuthProviderConfig) {
|
|
13
|
+
super(config);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Extracts user information from Cognito JWT payload
|
|
18
|
+
*/
|
|
19
|
+
extractUserInfo(payload: JwtPayload): AuthUserInfo {
|
|
20
|
+
// Cognito uses custom claims with 'cognito:' prefix for some fields
|
|
21
|
+
const email = payload.email as string | undefined ||
|
|
22
|
+
payload['cognito:username'] as string | undefined;
|
|
23
|
+
const fullName = payload.name as string | undefined;
|
|
24
|
+
const firstName = payload.given_name as string | undefined;
|
|
25
|
+
const lastName = payload.family_name as string | undefined;
|
|
26
|
+
const preferredUsername = payload['cognito:username'] as string | undefined ||
|
|
27
|
+
payload.preferred_username as string | undefined ||
|
|
28
|
+
email;
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
email,
|
|
32
|
+
firstName: firstName || fullName?.split(' ')[0],
|
|
33
|
+
lastName: lastName || fullName?.split(' ')[1] || fullName?.split(' ')[0],
|
|
34
|
+
fullName,
|
|
35
|
+
preferredUsername
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Validates Cognito-specific configuration
|
|
41
|
+
*/
|
|
42
|
+
validateConfig(): boolean {
|
|
43
|
+
const baseValid = super.validateConfig();
|
|
44
|
+
const hasClientId = !!this.config.clientId;
|
|
45
|
+
const hasRegion = !!this.config.region;
|
|
46
|
+
const hasUserPoolId = !!this.config.userPoolId;
|
|
47
|
+
|
|
48
|
+
return baseValid && hasClientId && hasRegion && hasUserPoolId;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { JwtPayload } from 'jsonwebtoken';
|
|
2
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
3
|
+
import { AuthProviderConfig, AuthUserInfo } from '@memberjunction/core';
|
|
4
|
+
import { BaseAuthProvider } from '../BaseAuthProvider.js';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Google Identity Platform authentication provider implementation
|
|
9
|
+
*/
|
|
10
|
+
@RegisterClass(BaseAuthProvider, 'google')
|
|
11
|
+
export class GoogleProvider extends BaseAuthProvider {
|
|
12
|
+
constructor(config: AuthProviderConfig) {
|
|
13
|
+
super(config);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Extracts user information from Google JWT payload
|
|
18
|
+
*/
|
|
19
|
+
extractUserInfo(payload: JwtPayload): AuthUserInfo {
|
|
20
|
+
// Google uses standard OIDC claims
|
|
21
|
+
const email = payload.email as string | undefined;
|
|
22
|
+
const fullName = payload.name as string | undefined;
|
|
23
|
+
const firstName = payload.given_name as string | undefined;
|
|
24
|
+
const lastName = payload.family_name as string | undefined;
|
|
25
|
+
const preferredUsername = email; // Google typically uses email as username
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
email,
|
|
29
|
+
firstName: firstName || fullName?.split(' ')[0],
|
|
30
|
+
lastName: lastName || fullName?.split(' ')[1] || fullName?.split(' ')[0],
|
|
31
|
+
fullName,
|
|
32
|
+
preferredUsername
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Validates Google-specific configuration
|
|
38
|
+
*/
|
|
39
|
+
validateConfig(): boolean {
|
|
40
|
+
const baseValid = super.validateConfig();
|
|
41
|
+
const hasClientId = !!this.config.clientId;
|
|
42
|
+
|
|
43
|
+
return baseValid && hasClientId;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { JwtPayload } from 'jsonwebtoken';
|
|
2
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
3
|
+
import { AuthProviderConfig, AuthUserInfo } from '@memberjunction/core';
|
|
4
|
+
import { BaseAuthProvider } from '../BaseAuthProvider.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Microsoft Authentication Library (MSAL) provider implementation
|
|
8
|
+
*/
|
|
9
|
+
@RegisterClass(BaseAuthProvider, 'msal')
|
|
10
|
+
export class MSALProvider extends BaseAuthProvider {
|
|
11
|
+
constructor(config: AuthProviderConfig) {
|
|
12
|
+
super(config);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Extracts user information from MSAL/Azure AD JWT payload
|
|
17
|
+
*/
|
|
18
|
+
extractUserInfo(payload: JwtPayload): AuthUserInfo {
|
|
19
|
+
// MSAL/Azure AD uses some custom claims
|
|
20
|
+
const email = payload.email as string | undefined || payload.preferred_username as string | undefined;
|
|
21
|
+
const fullName = payload.name as string | undefined;
|
|
22
|
+
const firstName = payload.given_name as string | undefined;
|
|
23
|
+
const lastName = payload.family_name as string | undefined;
|
|
24
|
+
const preferredUsername = payload.preferred_username as string | undefined;
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
email,
|
|
28
|
+
firstName: firstName || fullName?.split(' ')[0],
|
|
29
|
+
lastName: lastName || fullName?.split(' ')[1] || fullName?.split(' ')[0],
|
|
30
|
+
fullName,
|
|
31
|
+
preferredUsername
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Validates MSAL-specific configuration
|
|
37
|
+
*/
|
|
38
|
+
validateConfig(): boolean {
|
|
39
|
+
const baseValid = super.validateConfig();
|
|
40
|
+
const hasClientId = !!this.config.clientId;
|
|
41
|
+
const hasTenantId = !!this.config.tenantId;
|
|
42
|
+
|
|
43
|
+
return baseValid && hasClientId && hasTenantId;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { JwtPayload } from 'jsonwebtoken';
|
|
2
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
3
|
+
import { AuthProviderConfig, AuthUserInfo } from '@memberjunction/core';
|
|
4
|
+
import { BaseAuthProvider } from '../BaseAuthProvider.js';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Okta authentication provider implementation
|
|
9
|
+
*/
|
|
10
|
+
@RegisterClass(BaseAuthProvider, 'okta')
|
|
11
|
+
export class OktaProvider extends BaseAuthProvider {
|
|
12
|
+
constructor(config: AuthProviderConfig) {
|
|
13
|
+
super(config);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Extracts user information from Okta JWT payload
|
|
18
|
+
*/
|
|
19
|
+
extractUserInfo(payload: JwtPayload): AuthUserInfo {
|
|
20
|
+
// Okta uses standard OIDC claims plus some custom ones
|
|
21
|
+
const email = payload.email as string | undefined || payload.preferred_username as string | undefined;
|
|
22
|
+
const fullName = payload.name as string | undefined;
|
|
23
|
+
const firstName = payload.given_name as string | undefined;
|
|
24
|
+
const lastName = payload.family_name as string | undefined;
|
|
25
|
+
const preferredUsername = payload.preferred_username as string | undefined || email;
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
email,
|
|
29
|
+
firstName: firstName || fullName?.split(' ')[0],
|
|
30
|
+
lastName: lastName || fullName?.split(' ')[1] || fullName?.split(' ')[0],
|
|
31
|
+
fullName,
|
|
32
|
+
preferredUsername
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Validates Okta-specific configuration
|
|
38
|
+
*/
|
|
39
|
+
validateConfig(): boolean {
|
|
40
|
+
const baseValid = super.validateConfig();
|
|
41
|
+
const hasClientId = !!this.config.clientId;
|
|
42
|
+
const hasDomain = !!this.config.domain;
|
|
43
|
+
|
|
44
|
+
return baseValid && hasClientId && hasDomain;
|
|
45
|
+
}
|
|
46
|
+
}
|