@optimizely-opal/opal-tool-ocp-sdk 0.0.0-beta.11 → 0.0.0-beta.13
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/README.md +114 -55
- package/dist/auth/AuthUtils.d.ts +26 -0
- package/dist/auth/AuthUtils.d.ts.map +1 -0
- package/dist/auth/AuthUtils.js +109 -0
- package/dist/auth/AuthUtils.js.map +1 -0
- package/dist/auth/AuthUtils.test.d.ts +2 -0
- package/dist/auth/AuthUtils.test.d.ts.map +1 -0
- package/dist/auth/AuthUtils.test.js +601 -0
- package/dist/auth/AuthUtils.test.js.map +1 -0
- package/dist/auth/TokenVerifier.d.ts +31 -0
- package/dist/auth/TokenVerifier.d.ts.map +1 -0
- package/dist/auth/TokenVerifier.js +127 -0
- package/dist/auth/TokenVerifier.js.map +1 -0
- package/dist/auth/TokenVerifier.test.d.ts +2 -0
- package/dist/auth/TokenVerifier.test.d.ts.map +1 -0
- package/dist/auth/TokenVerifier.test.js +125 -0
- package/dist/auth/TokenVerifier.test.js.map +1 -0
- package/dist/function/GlobalToolFunction.d.ts +27 -0
- package/dist/function/GlobalToolFunction.d.ts.map +1 -0
- package/dist/function/GlobalToolFunction.js +53 -0
- package/dist/function/GlobalToolFunction.js.map +1 -0
- package/dist/function/GlobalToolFunction.test.d.ts +2 -0
- package/dist/function/GlobalToolFunction.test.d.ts.map +1 -0
- package/dist/function/GlobalToolFunction.test.js +425 -0
- package/dist/function/GlobalToolFunction.test.js.map +1 -0
- package/dist/function/ToolFunction.d.ts +3 -7
- package/dist/function/ToolFunction.d.ts.map +1 -1
- package/dist/function/ToolFunction.js +6 -10
- package/dist/function/ToolFunction.js.map +1 -1
- package/dist/function/ToolFunction.test.js +177 -196
- package/dist/function/ToolFunction.test.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/service/Service.d.ts +14 -13
- package/dist/service/Service.d.ts.map +1 -1
- package/dist/service/Service.js +22 -16
- package/dist/service/Service.js.map +1 -1
- package/dist/service/Service.test.js +8 -3
- package/dist/service/Service.test.js.map +1 -1
- package/dist/types/Models.d.ts +5 -5
- package/dist/types/Models.d.ts.map +1 -1
- package/dist/types/Models.js +9 -9
- package/dist/types/Models.js.map +1 -1
- package/package.json +4 -3
- package/src/auth/AuthUtils.test.ts +729 -0
- package/src/auth/AuthUtils.ts +117 -0
- package/src/auth/TokenVerifier.test.ts +165 -0
- package/src/auth/TokenVerifier.ts +145 -0
- package/src/function/GlobalToolFunction.test.ts +505 -0
- package/src/function/GlobalToolFunction.ts +56 -0
- package/src/function/ToolFunction.test.ts +194 -214
- package/src/function/ToolFunction.ts +6 -10
- package/src/index.ts +2 -0
- package/src/service/Service.test.ts +8 -3
- package/src/service/Service.ts +53 -24
- package/src/types/Models.ts +4 -4
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { getAppContext, logger } from '@zaiusinc/app-sdk';
|
|
2
|
+
import { getTokenVerifier } from './TokenVerifier';
|
|
3
|
+
import { OptiIdAuthData } from '../types/Models';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Validate the OptiID access token
|
|
7
|
+
*
|
|
8
|
+
* @param accessToken - The access token to validate
|
|
9
|
+
* @returns true if the token is valid
|
|
10
|
+
*/
|
|
11
|
+
async function validateAccessToken(accessToken: string | undefined): Promise<boolean> {
|
|
12
|
+
try {
|
|
13
|
+
if (!accessToken) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
const tokenVerifier = await getTokenVerifier();
|
|
17
|
+
return await tokenVerifier.verify(accessToken);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
logger.error('OptiID token validation failed:', error);
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Extract and validate basic OptiID authentication data from request
|
|
26
|
+
*
|
|
27
|
+
* @param request - The incoming request
|
|
28
|
+
* @returns object with authData and accessToken, or null if invalid
|
|
29
|
+
*/
|
|
30
|
+
export function extractAuthData(request: any): { authData: OptiIdAuthData; accessToken: string } | null {
|
|
31
|
+
const authData = request?.bodyJSON?.auth as OptiIdAuthData;
|
|
32
|
+
const accessToken = authData?.credentials?.access_token;
|
|
33
|
+
if (!accessToken || authData?.provider?.toLowerCase() !== 'optiid') {
|
|
34
|
+
logger.error('OptiID token is required but not provided');
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return { authData, accessToken };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Validate organization ID matches the app context
|
|
43
|
+
*
|
|
44
|
+
* @param customerId - The customer ID from the auth data
|
|
45
|
+
* @returns true if the organization ID is valid
|
|
46
|
+
*/
|
|
47
|
+
function validateOrganizationId(customerId: string | undefined): boolean {
|
|
48
|
+
if (!customerId) {
|
|
49
|
+
logger.error('Organisation ID is required but not provided');
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const appOrganisationId = getAppContext()?.account?.organizationId;
|
|
54
|
+
if (customerId !== appOrganisationId) {
|
|
55
|
+
logger.error(`Invalid organisation ID: expected ${appOrganisationId}, received ${customerId}`);
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Check if a request should skip authentication (discovery/ready endpoints)
|
|
64
|
+
*
|
|
65
|
+
* @param request - The incoming request
|
|
66
|
+
* @returns true if auth should be skipped
|
|
67
|
+
*/
|
|
68
|
+
function shouldSkipAuth(request: any): boolean {
|
|
69
|
+
return request.path === '/discovery' || request.path === '/ready';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Core authentication flow - extracts auth data and validates token
|
|
74
|
+
*
|
|
75
|
+
* @param request - The incoming request
|
|
76
|
+
* @param validateOrg - Whether to validate organization ID
|
|
77
|
+
* @returns true if authentication succeeds
|
|
78
|
+
*/
|
|
79
|
+
async function authenticateRequest(request: any, validateOrg: boolean): Promise<boolean> {
|
|
80
|
+
if (shouldSkipAuth(request)) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const authInfo = extractAuthData(request);
|
|
85
|
+
if (!authInfo) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const { authData, accessToken } = authInfo;
|
|
90
|
+
|
|
91
|
+
// Validate organization ID if required
|
|
92
|
+
if (validateOrg && !validateOrganizationId(authData.credentials?.customer_id)) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return await validateAccessToken(accessToken);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Authenticate a request for regular functions (with organization validation)
|
|
101
|
+
*
|
|
102
|
+
* @param request - The incoming request
|
|
103
|
+
* @returns true if authentication and authorization succeed
|
|
104
|
+
*/
|
|
105
|
+
export async function authenticateRegularRequest(request: any): Promise<boolean> {
|
|
106
|
+
return await authenticateRequest(request, true);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Authenticate a request for global functions (without organization validation)
|
|
111
|
+
*
|
|
112
|
+
* @param request - The incoming request
|
|
113
|
+
* @returns true if authentication succeeds
|
|
114
|
+
*/
|
|
115
|
+
export async function authenticateGlobalRequest(request: any): Promise<boolean> {
|
|
116
|
+
return await authenticateRequest(request, false);
|
|
117
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
|
|
2
|
+
// Mock the app-sdk module
|
|
3
|
+
jest.mock('@zaiusinc/app-sdk', () => ({
|
|
4
|
+
logger: {
|
|
5
|
+
info: jest.fn(),
|
|
6
|
+
error: jest.fn(),
|
|
7
|
+
warn: jest.fn(),
|
|
8
|
+
debug: jest.fn(),
|
|
9
|
+
},
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
import { TokenVerifier } from './TokenVerifier';
|
|
13
|
+
|
|
14
|
+
// Test constants
|
|
15
|
+
const TEST_ISSUER = 'https://prep.login.optimizely.com/oauth2/default';
|
|
16
|
+
const TEST_JWKS_URI = 'https://prep.login.optimizely.com/oauth2/v1/keys';
|
|
17
|
+
|
|
18
|
+
// Mock fetch globally
|
|
19
|
+
global.fetch = jest.fn();
|
|
20
|
+
|
|
21
|
+
describe('TokenVerifier', () => {
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
// Reset fetch mock
|
|
24
|
+
(global.fetch as jest.Mock).mockReset();
|
|
25
|
+
|
|
26
|
+
// Reset singleton instance for each test
|
|
27
|
+
(TokenVerifier as any).instance = null;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('getInitializedInstance', () => {
|
|
31
|
+
it('should initialize successfully', async () => {
|
|
32
|
+
// Mock fetch for OAuth2 authorization server discovery
|
|
33
|
+
(global.fetch as jest.Mock).mockResolvedValue({
|
|
34
|
+
ok: true,
|
|
35
|
+
json: jest.fn().mockResolvedValue({
|
|
36
|
+
issuer: TEST_ISSUER,
|
|
37
|
+
jwks_uri: TEST_JWKS_URI
|
|
38
|
+
})
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const tokenVerifier = await TokenVerifier.getInitializedInstance();
|
|
42
|
+
expect(tokenVerifier).toBeInstanceOf(TokenVerifier);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should throw error when discovery document is invalid', async () => {
|
|
46
|
+
(global.fetch as jest.Mock).mockResolvedValue({
|
|
47
|
+
ok: true,
|
|
48
|
+
json: jest.fn().mockResolvedValue({
|
|
49
|
+
// Missing issuer and jwks_uri
|
|
50
|
+
})
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
await expect(TokenVerifier.getInitializedInstance()).rejects.toThrow(
|
|
54
|
+
'Invalid discovery document: missing issuer or jwks_uri'
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should throw error when discovery endpoint is unreachable', async () => {
|
|
59
|
+
(global.fetch as jest.Mock).mockResolvedValue({
|
|
60
|
+
ok: false,
|
|
61
|
+
status: 404,
|
|
62
|
+
statusText: 'Not Found'
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await expect(TokenVerifier.getInitializedInstance()).rejects.toThrow(
|
|
66
|
+
'Failed to fetch discovery document: 404 Not Found'
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('token verification', () => {
|
|
72
|
+
let tokenVerifier: TokenVerifier;
|
|
73
|
+
|
|
74
|
+
beforeEach(async () => {
|
|
75
|
+
// Mock successful initialization
|
|
76
|
+
(global.fetch as jest.Mock).mockResolvedValue({
|
|
77
|
+
ok: true,
|
|
78
|
+
json: jest.fn().mockResolvedValue({
|
|
79
|
+
issuer: TEST_ISSUER,
|
|
80
|
+
jwks_uri: TEST_JWKS_URI
|
|
81
|
+
})
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
tokenVerifier = await TokenVerifier.getInitializedInstance();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should throw error for empty token', async () => {
|
|
88
|
+
await expect(tokenVerifier.verify('')).rejects.toThrow(
|
|
89
|
+
'Token cannot be null or empty'
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should throw error for undefined token', async () => {
|
|
94
|
+
await expect(tokenVerifier.verify(undefined)).rejects.toThrow(
|
|
95
|
+
'Token cannot be null or empty'
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should throw error for whitespace-only token', async () => {
|
|
100
|
+
await expect(tokenVerifier.verify(' ')).rejects.toThrow(
|
|
101
|
+
'Token cannot be null or empty'
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should return false for invalid token format', async () => {
|
|
106
|
+
const result = await tokenVerifier.verify('invalid.jwt.token');
|
|
107
|
+
expect(result).toBe(false);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('singleton pattern', () => {
|
|
112
|
+
it('should return same instance when called multiple times', async () => {
|
|
113
|
+
// Mock successful initialization
|
|
114
|
+
(global.fetch as jest.Mock).mockResolvedValue({
|
|
115
|
+
ok: true,
|
|
116
|
+
json: jest.fn().mockResolvedValue({
|
|
117
|
+
issuer: TEST_ISSUER,
|
|
118
|
+
jwks_uri: TEST_JWKS_URI
|
|
119
|
+
})
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const instance1 = await TokenVerifier.getInitializedInstance();
|
|
123
|
+
const instance2 = await TokenVerifier.getInitializedInstance();
|
|
124
|
+
expect(instance1).toBe(instance2);
|
|
125
|
+
});
|
|
126
|
+
it('should call correct prod OAuth2 authorization server discovery URL', async () => {
|
|
127
|
+
const fetchSpy = jest.spyOn(global, 'fetch').mockResolvedValue({
|
|
128
|
+
ok: true,
|
|
129
|
+
json: jest.fn().mockResolvedValue({
|
|
130
|
+
issuer: TEST_ISSUER,
|
|
131
|
+
jwks_uri: TEST_JWKS_URI
|
|
132
|
+
})
|
|
133
|
+
} as unknown as Response);
|
|
134
|
+
|
|
135
|
+
await TokenVerifier.getInitializedInstance();
|
|
136
|
+
|
|
137
|
+
expect(fetchSpy).toHaveBeenCalledWith(
|
|
138
|
+
'https://login.optimizely.com/oauth2/default/.well-known/oauth-authorization-server'
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should call correct prep OAuth2 authorization server discovery URL', async () => {
|
|
143
|
+
// Set environment variable to staging
|
|
144
|
+
process.env.ENVIRONMENT = 'staging';
|
|
145
|
+
|
|
146
|
+
const fetchSpy = jest.spyOn(global, 'fetch').mockResolvedValue({
|
|
147
|
+
ok: true,
|
|
148
|
+
json: jest.fn().mockResolvedValue({
|
|
149
|
+
issuer: TEST_ISSUER,
|
|
150
|
+
jwks_uri: TEST_JWKS_URI
|
|
151
|
+
})
|
|
152
|
+
} as unknown as Response);
|
|
153
|
+
|
|
154
|
+
await TokenVerifier.getInitializedInstance();
|
|
155
|
+
|
|
156
|
+
expect(fetchSpy).toHaveBeenCalledWith(
|
|
157
|
+
'https://prep.login.optimizely.com/oauth2/default/.well-known/oauth-authorization-server'
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
// Clean up environment variable
|
|
161
|
+
delete process.env.ENVIRONMENT;
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
});
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { jwtVerify, createRemoteJWKSet } from 'jose';
|
|
2
|
+
import { logger } from '@zaiusinc/app-sdk';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Default JWKS cache expiration time in milliseconds (1 hour)
|
|
6
|
+
*/
|
|
7
|
+
const DEFAULT_JWKS_EXPIRES_IN = 60 * 60 * 1000;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Default clock skew tolerance in seconds
|
|
11
|
+
*/
|
|
12
|
+
const DEFAULT_LEEWAY = 30;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Expected JWT audience for token validation
|
|
16
|
+
*/
|
|
17
|
+
const AUDIENCE = 'api://default';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Prep Base URL for Optimizely OAuth2 endpoints
|
|
21
|
+
*/
|
|
22
|
+
const PREP_BASE_URL = 'https://prep.login.optimizely.com/oauth2/default';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Prod Base URL for Optimizely OAuth2 endpoints
|
|
26
|
+
*/
|
|
27
|
+
const PROD_BASE_URL = 'https://login.optimizely.com/oauth2/default';
|
|
28
|
+
|
|
29
|
+
interface DiscoveryDocument {
|
|
30
|
+
issuer: string;
|
|
31
|
+
jwks_uri: string;
|
|
32
|
+
[key: string]: any;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class TokenVerifier {
|
|
36
|
+
private static instance: TokenVerifier | null = null;
|
|
37
|
+
private jwksUri?: string;
|
|
38
|
+
private issuer?: string;
|
|
39
|
+
private jwks?: ReturnType<typeof createRemoteJWKSet>;
|
|
40
|
+
private initialized: boolean = false;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Verify the provided Optimizely JWT token string
|
|
44
|
+
* @param token JWT token string to verify
|
|
45
|
+
* @returns boolean true if verification successful, false otherwise
|
|
46
|
+
* @throws Error if token is null, empty, or verifier is not properly configured
|
|
47
|
+
*/
|
|
48
|
+
public async verify(token: string | undefined): Promise<boolean> {
|
|
49
|
+
if (!token || token.trim().length === 0) {
|
|
50
|
+
throw new Error('Token cannot be null or empty');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return this.verifyToken(token);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private static getInstance(): TokenVerifier {
|
|
57
|
+
if (!TokenVerifier.instance) {
|
|
58
|
+
TokenVerifier.instance = new TokenVerifier();
|
|
59
|
+
}
|
|
60
|
+
return TokenVerifier.instance;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get singleton instance of TokenVerifier and ensure it's initialized
|
|
65
|
+
* @returns Promise<TokenVerifier> - initialized singleton instance
|
|
66
|
+
*/
|
|
67
|
+
public static async getInitializedInstance(): Promise<TokenVerifier> {
|
|
68
|
+
const instance = TokenVerifier.getInstance();
|
|
69
|
+
if (!instance.initialized) {
|
|
70
|
+
await instance.initialize();
|
|
71
|
+
}
|
|
72
|
+
return instance;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Initialize the TokenVerifier with discovery document from well-known endpoint
|
|
77
|
+
*/
|
|
78
|
+
private async initialize(): Promise<void> {
|
|
79
|
+
if (this.initialized) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
// Use prep URL when environment variable is set to 'staging', otherwise use prod
|
|
85
|
+
const environment = process.env.ENVIRONMENT || 'production';
|
|
86
|
+
const baseUrl = environment === 'staging' ? PREP_BASE_URL : PROD_BASE_URL;
|
|
87
|
+
const discoveryDocument = await this.fetchDiscoveryDocument(baseUrl);
|
|
88
|
+
this.issuer = discoveryDocument.issuer;
|
|
89
|
+
this.jwksUri = discoveryDocument.jwks_uri;
|
|
90
|
+
this.jwks = createRemoteJWKSet(new URL(this.jwksUri), {
|
|
91
|
+
cacheMaxAge: DEFAULT_JWKS_EXPIRES_IN,
|
|
92
|
+
cooldownDuration: DEFAULT_JWKS_EXPIRES_IN
|
|
93
|
+
});
|
|
94
|
+
this.initialized = true;
|
|
95
|
+
logger.info(`TokenVerifier initialized with issuer: ${this.issuer} (environment: ${environment})`);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
logger.error('Failed to initialize TokenVerifier', error);
|
|
98
|
+
// Re-throw the original error to preserve specific error messages for tests
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Fetch discovery document from well-known endpoint
|
|
105
|
+
*/
|
|
106
|
+
private async fetchDiscoveryDocument(baseUrl: string): Promise<DiscoveryDocument> {
|
|
107
|
+
const wellKnownUrl = `${baseUrl}/.well-known/oauth-authorization-server`;
|
|
108
|
+
|
|
109
|
+
const response = await fetch(wellKnownUrl);
|
|
110
|
+
if (!response.ok) {
|
|
111
|
+
throw new Error(`Failed to fetch discovery document: ${response.status} ${response.statusText}`);
|
|
112
|
+
}
|
|
113
|
+
const discoveryDocument = await response.json() as DiscoveryDocument;
|
|
114
|
+
if (!discoveryDocument.issuer || !discoveryDocument.jwks_uri) {
|
|
115
|
+
throw new Error('Invalid discovery document: missing issuer or jwks_uri');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return discoveryDocument;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private async verifyToken(token: string): Promise<boolean> {
|
|
122
|
+
if (!this.initialized) {
|
|
123
|
+
throw new Error('TokenVerifier not initialized. Call initialize() first.');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!this.jwks || !this.issuer) {
|
|
127
|
+
throw new Error('TokenVerifier not properly configured.');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
await jwtVerify(token, this.jwks, {
|
|
132
|
+
issuer: this.issuer,
|
|
133
|
+
audience: AUDIENCE,
|
|
134
|
+
clockTolerance: DEFAULT_LEEWAY,
|
|
135
|
+
});
|
|
136
|
+
return true;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
logger.error('Token verification failed:', error);
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export const getTokenVerifier = async (): Promise<TokenVerifier> => TokenVerifier.getInitializedInstance();
|