-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathconfidentialClient.ts
More file actions
133 lines (119 loc) · 4.41 KB
/
Copy pathconfidentialClient.ts
File metadata and controls
133 lines (119 loc) · 4.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import {AccessTokenError, ConfidentialClientConfiguration, OAuth2Client, Token} from '.';
import {OpenIDClientFactory} from './openIDClientFactory';
import {Configuration} from './configuration';
import {Client} from 'openid-client';
import {JWT_EXPIRE_AFTER_SECS, JWT_NOT_BEFORE_SECS, PACKAGE_NAME} from './constants';
import {unixTimestamp} from './unixTimestamp';
import debugModule from 'debug';
import {HttpsProxyAgent} from 'https-proxy-agent';
const debug = debugModule(`${PACKAGE_NAME}:ConfidentialClient`);
/**
* Helper class that supports FactSet's implementation of the OAuth 2.0
* client credentials flow.
*
* The main purpose of this class is to provide an access token that can
* be used to authenticate against FactSet's APIs. It takes care of fetching
* the access token, caching it and refreshing it as needed.
*/
export class ConfidentialClient implements OAuth2Client {
private readonly _config: ConfidentialClientConfiguration;
private _token: Token;
private _openIDClient!: Client;
private _options: {proxyUrl: string} | null;
/**
* @param path Path to credentials configuration file.
* @param _options HTTP proxy options.
*/
constructor(path: string, _options?: {proxyUrl: string});
/**
* Example config
*
* ```json
* {
* "name": "Application Name registered with FactSet:Developer",
* "clientId": "Client ID registered with FactSet:Developer",
* "clientAuthType": "Confidential",
* "owners": ["Owner ID(s) of this configuration"],
* "jwk": {
* "kty": "RSA",
* "use": "sig",
* "alg": "RS256",
* "kid": "Key ID",
* "d": "ECC Private Key",
* "n": "Modulus",
* "e": "Exponent",
* "p": "First Prime Factor",
* "q": "Second Prime Factor",
* "dp": "First Factor CRT Exponent",
* "dq": "Second Factor CRT Exponent",
* "qi": "First CRT Coefficient",
* }
* }
* ```
*
* @param config FacSet ConfidentialClient configuration object
*/
constructor(config: ConfidentialClientConfiguration);
constructor(param: ConfidentialClientConfiguration | string, _options?: {proxyUrl: string}) {
this._config = Configuration.loadConfig(param);
this._token = new Token('', 0);
this._options = _options ?? null;
}
/**
* Returns an access token that can be used for authentication.
*
* If the cache contains a valid access token, it's returned. Otherwise
* a new access token is retrieved from FactSet's authorization server.
*
* The access token should be used immediately and not stored to avoid
* any issues with token expiry.
*
* The access token is used in the Authorization header when when accessing
* FactSet's APIs. Example: `{"Authorization": "Bearer access-token"}`
*
* @returns access token for protected resource requests
*/
public async getAccessToken(): Promise<string> {
if (this._token.isExpired() === false) {
debug('Retrieving cached token. Expires at %d, in %d seconds.', this._token.expiresAt, this._token.expiresIn);
return this._token.token;
}
debug('Token is expired or invalid');
if (this._options?.proxyUrl) {
const proxyAgent = new HttpsProxyAgent(`${this._options.proxyUrl}`);
this._openIDClient = await OpenIDClientFactory.getClient(this._config, proxyAgent);
} else {
this._openIDClient = await OpenIDClientFactory.getClient(this._config);
}
this._token = await this.fetchAccessToken();
return this._token.token;
}
private async fetchAccessToken(): Promise<Token> {
debug('Fetching new access token');
try {
const now = unixTimestamp();
const tokenSet = await this._openIDClient.grant(
{
grant_type: 'client_credentials',
},
{
clientAssertionPayload: {
nbf: now - JWT_NOT_BEFORE_SECS,
iat: now,
exp: now + JWT_EXPIRE_AFTER_SECS,
},
}
);
if (tokenSet.access_token === undefined || tokenSet.expires_at === undefined) {
throw new AccessTokenError('Got an invalid token');
}
debug('Got access token that expires at %d, in %d seconds', tokenSet.expires_at, tokenSet.expires_in);
return new Token(tokenSet.access_token, tokenSet.expires_at);
} catch (error) {
if (error instanceof AccessTokenError) {
throw error;
}
throw new AccessTokenError('Error attempting to get access token', error);
}
}
}