@opentdf/sdk 0.13.0-beta.123 → 0.13.0-beta.125
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/src/auth/token-providers.js +247 -0
- package/dist/cjs/src/index.js +6 -2
- package/dist/types/src/auth/token-providers.d.ts +100 -0
- package/dist/types/src/auth/token-providers.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/web/src/auth/token-providers.js +242 -0
- package/dist/web/src/index.js +2 -1
- package/package.json +1 -1
- package/src/auth/token-providers.ts +303 -0
- package/src/index.ts +8 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.clientCredentialsTokenProvider = clientCredentialsTokenProvider;
|
|
4
|
+
exports.refreshTokenProvider = refreshTokenProvider;
|
|
5
|
+
exports.externalJwtTokenProvider = externalJwtTokenProvider;
|
|
6
|
+
const errors_js_1 = require("../errors.js");
|
|
7
|
+
const utils_js_1 = require("../utils.js");
|
|
8
|
+
function resolveTokenEndpoint(oidcOrigin, override) {
|
|
9
|
+
if (override?.trim())
|
|
10
|
+
return override;
|
|
11
|
+
const base = oidcOrigin?.trim();
|
|
12
|
+
if (!base) {
|
|
13
|
+
throw new errors_js_1.ConfigurationError('oidcOrigin or oidcTokenEndpoint is required');
|
|
14
|
+
}
|
|
15
|
+
return `${(0, utils_js_1.rstrip)(base, '/')}/protocol/openid-connect/token`;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Decode a JWT's exp claim without verifying the signature.
|
|
19
|
+
* Returns the expiration time in seconds since epoch, or undefined if not present.
|
|
20
|
+
*/
|
|
21
|
+
function getJwtExpiration(token) {
|
|
22
|
+
try {
|
|
23
|
+
const parts = token.split('.');
|
|
24
|
+
if (parts.length !== 3)
|
|
25
|
+
return undefined;
|
|
26
|
+
// Base64url decode the payload
|
|
27
|
+
const payload = parts[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
28
|
+
const padded = payload + '='.repeat((4 - (payload.length % 4)) % 4);
|
|
29
|
+
const decoded = JSON.parse(atob(padded));
|
|
30
|
+
return typeof decoded.exp === 'number' ? decoded.exp : undefined;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Compute the absolute expiry (seconds since epoch) for a token response.
|
|
38
|
+
* Prefers `expires_in` from the token response, falls back to the JWT `exp` claim.
|
|
39
|
+
*/
|
|
40
|
+
function resolveTokenExpiry(accessToken, expiresIn) {
|
|
41
|
+
if (typeof expiresIn === 'number') {
|
|
42
|
+
return Date.now() / 1000 + expiresIn;
|
|
43
|
+
}
|
|
44
|
+
return getJwtExpiration(accessToken);
|
|
45
|
+
}
|
|
46
|
+
function isTokenExpired(expiry, bufferSeconds = 30) {
|
|
47
|
+
if (expiry === undefined)
|
|
48
|
+
return true;
|
|
49
|
+
return Date.now() / 1000 >= expiry - bufferSeconds;
|
|
50
|
+
}
|
|
51
|
+
async function fetchToken(tokenEndpoint, body) {
|
|
52
|
+
const response = await fetch(tokenEndpoint, {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
headers: {
|
|
55
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
56
|
+
Accept: 'application/json',
|
|
57
|
+
},
|
|
58
|
+
body: new URLSearchParams(body).toString(),
|
|
59
|
+
});
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
const text = await response.text();
|
|
62
|
+
throw new errors_js_1.TdfError(`Token request failed: POST [${tokenEndpoint}] => ${response.status} ${response.statusText}: ${text}`);
|
|
63
|
+
}
|
|
64
|
+
return (await response.json());
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Creates a TokenProvider that obtains tokens via the OAuth2 client credentials grant.
|
|
68
|
+
* Tokens are cached and automatically refreshed when expired.
|
|
69
|
+
*
|
|
70
|
+
* **Not for browser use.** Client secrets must not be exposed in client-side code.
|
|
71
|
+
* Use this only in server-side (Node.js/Deno) environments.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* const client = new OpenTDF({
|
|
76
|
+
* interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({
|
|
77
|
+
* clientId: 'opentdf',
|
|
78
|
+
* clientSecret: 'secret',
|
|
79
|
+
* oidcOrigin: 'http://localhost:8080/auth/realms/opentdf',
|
|
80
|
+
* }))],
|
|
81
|
+
* platformUrl: 'http://localhost:8080',
|
|
82
|
+
* });
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
function clientCredentialsTokenProvider(options) {
|
|
86
|
+
if (!options.clientId || !options.clientSecret) {
|
|
87
|
+
throw new errors_js_1.ConfigurationError('clientId and clientSecret are required');
|
|
88
|
+
}
|
|
89
|
+
const tokenEndpoint = resolveTokenEndpoint(options.oidcOrigin, options.oidcTokenEndpoint);
|
|
90
|
+
let cachedToken;
|
|
91
|
+
let cachedExpiry;
|
|
92
|
+
let inFlight;
|
|
93
|
+
return async () => {
|
|
94
|
+
if (cachedToken && !isTokenExpired(cachedExpiry)) {
|
|
95
|
+
return cachedToken;
|
|
96
|
+
}
|
|
97
|
+
if (!inFlight) {
|
|
98
|
+
inFlight = (async () => {
|
|
99
|
+
try {
|
|
100
|
+
const resp = await fetchToken(tokenEndpoint, {
|
|
101
|
+
grant_type: 'client_credentials',
|
|
102
|
+
client_id: options.clientId,
|
|
103
|
+
client_secret: options.clientSecret,
|
|
104
|
+
});
|
|
105
|
+
cachedToken = resp.access_token;
|
|
106
|
+
cachedExpiry = resolveTokenExpiry(resp.access_token, resp.expires_in);
|
|
107
|
+
return cachedToken;
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
inFlight = undefined;
|
|
111
|
+
}
|
|
112
|
+
})();
|
|
113
|
+
}
|
|
114
|
+
return inFlight;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Creates a TokenProvider that uses a refresh token to obtain access tokens.
|
|
119
|
+
* On the first call, exchanges the refresh token. Subsequent calls use the
|
|
120
|
+
* latest refresh token from the IdP response.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```ts
|
|
124
|
+
* const client = new OpenTDF({
|
|
125
|
+
* interceptors: [authTokenInterceptor(refreshTokenProvider({
|
|
126
|
+
* clientId: 'my-app',
|
|
127
|
+
* refreshToken: 'refresh-token-from-login',
|
|
128
|
+
* oidcOrigin: 'http://localhost:8080/auth/realms/opentdf',
|
|
129
|
+
* }))],
|
|
130
|
+
* platformUrl: 'http://localhost:8080',
|
|
131
|
+
* });
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
function refreshTokenProvider(options) {
|
|
135
|
+
if (!options.clientId || !options.refreshToken) {
|
|
136
|
+
throw new errors_js_1.ConfigurationError('clientId and refreshToken are required');
|
|
137
|
+
}
|
|
138
|
+
const tokenEndpoint = resolveTokenEndpoint(options.oidcOrigin, options.oidcTokenEndpoint);
|
|
139
|
+
let currentRefreshToken = options.refreshToken;
|
|
140
|
+
let cachedToken;
|
|
141
|
+
let cachedExpiry;
|
|
142
|
+
let inFlight;
|
|
143
|
+
return async () => {
|
|
144
|
+
if (cachedToken && !isTokenExpired(cachedExpiry)) {
|
|
145
|
+
return cachedToken;
|
|
146
|
+
}
|
|
147
|
+
if (!inFlight) {
|
|
148
|
+
inFlight = (async () => {
|
|
149
|
+
try {
|
|
150
|
+
const resp = await fetchToken(tokenEndpoint, {
|
|
151
|
+
grant_type: 'refresh_token',
|
|
152
|
+
refresh_token: currentRefreshToken,
|
|
153
|
+
client_id: options.clientId,
|
|
154
|
+
});
|
|
155
|
+
cachedToken = resp.access_token;
|
|
156
|
+
cachedExpiry = resolveTokenExpiry(resp.access_token, resp.expires_in);
|
|
157
|
+
if (resp.refresh_token) {
|
|
158
|
+
currentRefreshToken = resp.refresh_token;
|
|
159
|
+
}
|
|
160
|
+
return cachedToken;
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
inFlight = undefined;
|
|
164
|
+
}
|
|
165
|
+
})();
|
|
166
|
+
}
|
|
167
|
+
return inFlight;
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Creates a TokenProvider that exchanges an external JWT for a platform token
|
|
172
|
+
* via RFC 8693 token exchange. After the initial exchange, uses the refresh
|
|
173
|
+
* token for subsequent calls.
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```ts
|
|
177
|
+
* const client = new OpenTDF({
|
|
178
|
+
* interceptors: [authTokenInterceptor(externalJwtTokenProvider({
|
|
179
|
+
* clientId: 'my-app',
|
|
180
|
+
* externalJwt: 'eyJhbGciOi...',
|
|
181
|
+
* oidcOrigin: 'http://localhost:8080/auth/realms/opentdf',
|
|
182
|
+
* }))],
|
|
183
|
+
* platformUrl: 'http://localhost:8080',
|
|
184
|
+
* });
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
function externalJwtTokenProvider(options) {
|
|
188
|
+
if (!options.clientId || !options.externalJwt) {
|
|
189
|
+
throw new errors_js_1.ConfigurationError('clientId and externalJwt are required');
|
|
190
|
+
}
|
|
191
|
+
const tokenEndpoint = resolveTokenEndpoint(options.oidcOrigin, options.oidcTokenEndpoint);
|
|
192
|
+
let cachedToken;
|
|
193
|
+
let cachedExpiry;
|
|
194
|
+
let currentRefreshToken;
|
|
195
|
+
let initialExchangeDone = false;
|
|
196
|
+
let inFlight;
|
|
197
|
+
return async () => {
|
|
198
|
+
if (cachedToken && !isTokenExpired(cachedExpiry)) {
|
|
199
|
+
return cachedToken;
|
|
200
|
+
}
|
|
201
|
+
if (!inFlight) {
|
|
202
|
+
inFlight = (async () => {
|
|
203
|
+
try {
|
|
204
|
+
let resp;
|
|
205
|
+
if (!initialExchangeDone) {
|
|
206
|
+
resp = await fetchToken(tokenEndpoint, {
|
|
207
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
|
|
208
|
+
subject_token: options.externalJwt,
|
|
209
|
+
subject_token_type: 'urn:ietf:params:oauth:token-type:jwt',
|
|
210
|
+
audience: options.clientId,
|
|
211
|
+
client_id: options.clientId,
|
|
212
|
+
});
|
|
213
|
+
initialExchangeDone = true;
|
|
214
|
+
}
|
|
215
|
+
else if (currentRefreshToken) {
|
|
216
|
+
resp = await fetchToken(tokenEndpoint, {
|
|
217
|
+
grant_type: 'refresh_token',
|
|
218
|
+
refresh_token: currentRefreshToken,
|
|
219
|
+
client_id: options.clientId,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
// Re-exchange the original JWT if no refresh token available
|
|
224
|
+
resp = await fetchToken(tokenEndpoint, {
|
|
225
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
|
|
226
|
+
subject_token: options.externalJwt,
|
|
227
|
+
subject_token_type: 'urn:ietf:params:oauth:token-type:jwt',
|
|
228
|
+
audience: options.clientId,
|
|
229
|
+
client_id: options.clientId,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
cachedToken = resp.access_token;
|
|
233
|
+
cachedExpiry = resolveTokenExpiry(resp.access_token, resp.expires_in);
|
|
234
|
+
if (resp.refresh_token) {
|
|
235
|
+
currentRefreshToken = resp.refresh_token;
|
|
236
|
+
}
|
|
237
|
+
return cachedToken;
|
|
238
|
+
}
|
|
239
|
+
finally {
|
|
240
|
+
inFlight = undefined;
|
|
241
|
+
}
|
|
242
|
+
})();
|
|
243
|
+
}
|
|
244
|
+
return inFlight;
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9rZW4tcHJvdmlkZXJzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2F1dGgvdG9rZW4tcHJvdmlkZXJzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBMElBLHdFQWlDQztBQW1CRCxvREFtQ0M7QUFtQkQsNERBMERDO0FBN1NELDRDQUE0RDtBQUM1RCwwQ0FBcUM7QUFxRHJDLFNBQVMsb0JBQW9CLENBQUMsVUFBa0IsRUFBRSxRQUFpQjtJQUNqRSxJQUFJLFFBQVEsRUFBRSxJQUFJLEVBQUU7UUFBRSxPQUFPLFFBQVEsQ0FBQztJQUN0QyxNQUFNLElBQUksR0FBRyxVQUFVLEVBQUUsSUFBSSxFQUFFLENBQUM7SUFDaEMsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDO1FBQ1YsTUFBTSxJQUFJLDhCQUFrQixDQUFDLDZDQUE2QyxDQUFDLENBQUM7SUFDOUUsQ0FBQztJQUNELE9BQU8sR0FBRyxJQUFBLGlCQUFNLEVBQUMsSUFBSSxFQUFFLEdBQUcsQ0FBQyxnQ0FBZ0MsQ0FBQztBQUM5RCxDQUFDO0FBRUQ7OztHQUdHO0FBQ0gsU0FBUyxnQkFBZ0IsQ0FBQyxLQUFhO0lBQ3JDLElBQUksQ0FBQztRQUNILE1BQU0sS0FBSyxHQUFHLEtBQUssQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDL0IsSUFBSSxLQUFLLENBQUMsTUFBTSxLQUFLLENBQUM7WUFBRSxPQUFPLFNBQVMsQ0FBQztRQUN6QywrQkFBK0I7UUFDL0IsTUFBTSxPQUFPLEdBQUcsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsR0FBRyxDQUFDLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxHQUFHLENBQUMsQ0FBQztRQUMvRCxNQUFNLE1BQU0sR0FBRyxPQUFPLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLENBQUMs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
|
package/dist/cjs/src/index.js
CHANGED
|
@@ -36,7 +36,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
36
36
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.ConfigurationError = exports.AttributeNotFoundError = exports.AttributeValidationError = exports.NetworkError = exports.DecryptError = exports.InvalidFileError = exports.IntegrityError = exports.PermissionDeniedError = exports.TdfError = exports.PlatformClient = exports.tdfSpecVersion = exports.clientType = exports.version = exports.attributeValueExists = exports.attributeExists = exports.validateAttributes = exports.listAttributes = exports.attributeFQNsAsValues = exports.authProviderInterceptor = exports.authTokenDPoPInterceptor = exports.authTokenInterceptor = exports.AuthProviders = exports.withHeaders = exports.HttpRequest = void 0;
|
|
39
|
+
exports.ConfigurationError = exports.AttributeNotFoundError = exports.AttributeValidationError = exports.NetworkError = exports.DecryptError = exports.InvalidFileError = exports.IntegrityError = exports.PermissionDeniedError = exports.TdfError = exports.PlatformClient = exports.tdfSpecVersion = exports.clientType = exports.version = exports.attributeValueExists = exports.attributeExists = exports.validateAttributes = exports.listAttributes = exports.attributeFQNsAsValues = exports.externalJwtTokenProvider = exports.refreshTokenProvider = exports.clientCredentialsTokenProvider = exports.authProviderInterceptor = exports.authTokenDPoPInterceptor = exports.authTokenInterceptor = exports.AuthProviders = exports.withHeaders = exports.HttpRequest = void 0;
|
|
40
40
|
var auth_js_1 = require("./auth/auth.js");
|
|
41
41
|
Object.defineProperty(exports, "HttpRequest", { enumerable: true, get: function () { return auth_js_1.HttpRequest; } });
|
|
42
42
|
Object.defineProperty(exports, "withHeaders", { enumerable: true, get: function () { return auth_js_1.withHeaders; } });
|
|
@@ -45,6 +45,10 @@ var interceptors_js_1 = require("./auth/interceptors.js");
|
|
|
45
45
|
Object.defineProperty(exports, "authTokenInterceptor", { enumerable: true, get: function () { return interceptors_js_1.authTokenInterceptor; } });
|
|
46
46
|
Object.defineProperty(exports, "authTokenDPoPInterceptor", { enumerable: true, get: function () { return interceptors_js_1.authTokenDPoPInterceptor; } });
|
|
47
47
|
Object.defineProperty(exports, "authProviderInterceptor", { enumerable: true, get: function () { return interceptors_js_1.authProviderInterceptor; } });
|
|
48
|
+
var token_providers_js_1 = require("./auth/token-providers.js");
|
|
49
|
+
Object.defineProperty(exports, "clientCredentialsTokenProvider", { enumerable: true, get: function () { return token_providers_js_1.clientCredentialsTokenProvider; } });
|
|
50
|
+
Object.defineProperty(exports, "refreshTokenProvider", { enumerable: true, get: function () { return token_providers_js_1.refreshTokenProvider; } });
|
|
51
|
+
Object.defineProperty(exports, "externalJwtTokenProvider", { enumerable: true, get: function () { return token_providers_js_1.externalJwtTokenProvider; } });
|
|
48
52
|
var api_js_1 = require("./policy/api.js");
|
|
49
53
|
Object.defineProperty(exports, "attributeFQNsAsValues", { enumerable: true, get: function () { return api_js_1.attributeFQNsAsValues; } });
|
|
50
54
|
var discovery_js_1 = require("./policy/discovery.js");
|
|
@@ -71,4 +75,4 @@ Object.defineProperty(exports, "AttributeNotFoundError", { enumerable: true, get
|
|
|
71
75
|
Object.defineProperty(exports, "ConfigurationError", { enumerable: true, get: function () { return errors_js_1.ConfigurationError; } });
|
|
72
76
|
__exportStar(require("./seekable.js"), exports);
|
|
73
77
|
__exportStar(require("../tdf3/src/models/index.js"), exports);
|
|
74
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
78
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsMENBQThGO0FBQWpELHNHQUFBLFdBQVcsT0FBQTtBQUFFLHNHQUFBLFdBQVcsT0FBQTtBQUNyRSxxRUFBcUQ7QUFDckQsMERBU2dDO0FBUjlCLHVIQUFBLG9CQUFvQixPQUFBO0FBQ3BCLDJIQUFBLHdCQUF3QixPQUFBO0FBQ3hCLDBIQUFBLHVCQUF1QixPQUFBO0FBT3pCLGdFQU9tQztBQU5qQyxvSUFBQSw4QkFBOEIsT0FBQTtBQUM5QiwwSEFBQSxvQkFBb0IsT0FBQTtBQUNwQiw4SEFBQSx3QkFBd0IsT0FBQTtBQUsxQiwwQ0FBd0Q7QUFBL0MsK0dBQUEscUJBQXFCLE9BQUE7QUFDOUIsc0RBSytCO0FBSjdCLDhHQUFBLGNBQWMsT0FBQTtBQUNkLGtIQUFBLGtCQUFrQixPQUFBO0FBQ2xCLCtHQUFBLGVBQWUsT0FBQTtBQUNmLG9IQUFBLG9CQUFvQixPQUFBO0FBRXRCLDJDQUFtRTtBQUExRCxxR0FBQSxPQUFPLE9BQUE7QUFBRSx3R0FBQSxVQUFVLE9BQUE7QUFBRSw0R0FBQSxjQUFjLE9BQUE7QUFDNUMsNkNBQWtHO0FBQXpGLDZHQUFBLGNBQWMsT0FBQTtBQUN2QiwrQ0FBNkI7QUFDN0IseUNBVXFCO0FBVG5CLHFHQUFBLFFBQVEsT0FBQTtBQUNSLGtIQUFBLHFCQUFxQixPQUFBO0FBQ3JCLDJHQUFBLGNBQWMsT0FBQTtBQUNkLDZHQUFBLGdCQUFnQixPQUFBO0FBQ2hCLHlHQUFBLFlBQVksT0FBQTtBQUNaLHlHQUFBLFlBQVksT0FBQTtBQUNaLHFIQUFBLHdCQUF3QixPQUFBO0FBQ3hCLG1IQUFBLHNCQUFzQixPQUFBO0FBQ3RCLCtHQUFBLGtCQUFrQixPQUFBO0FBRXBCLGdEQUE4QjtBQUM5Qiw4REFBNEMifQ==
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { type TokenProvider } from './interceptors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Options for client credentials token provider.
|
|
4
|
+
*
|
|
5
|
+
* **Not for browser use.** Client secrets must not be exposed in client-side code.
|
|
6
|
+
* Use this only in server-side (Node.js/Deno) environments.
|
|
7
|
+
*/
|
|
8
|
+
export type ClientCredentialsTokenProviderOptions = {
|
|
9
|
+
/** OIDC client ID. */
|
|
10
|
+
clientId: string;
|
|
11
|
+
/** OIDC client secret. */
|
|
12
|
+
clientSecret: string;
|
|
13
|
+
/** OIDC IdP origin, e.g. 'http://localhost:8080/auth/realms/opentdf'. */
|
|
14
|
+
oidcOrigin: string;
|
|
15
|
+
/** Override the token endpoint (defaults to `${oidcOrigin}/protocol/openid-connect/token`). */
|
|
16
|
+
oidcTokenEndpoint?: string;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Options for refresh token provider.
|
|
20
|
+
*/
|
|
21
|
+
export type RefreshTokenProviderOptions = {
|
|
22
|
+
/** OIDC client ID. */
|
|
23
|
+
clientId: string;
|
|
24
|
+
/** Refresh token obtained from a prior login flow. */
|
|
25
|
+
refreshToken: string;
|
|
26
|
+
/** OIDC IdP origin, e.g. 'http://localhost:8080/auth/realms/opentdf'. */
|
|
27
|
+
oidcOrigin: string;
|
|
28
|
+
/** Override the token endpoint. */
|
|
29
|
+
oidcTokenEndpoint?: string;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Options for external JWT token provider (RFC 8693 token exchange).
|
|
33
|
+
*/
|
|
34
|
+
export type ExternalJwtTokenProviderOptions = {
|
|
35
|
+
/** OIDC client ID. */
|
|
36
|
+
clientId: string;
|
|
37
|
+
/** External JWT to exchange. */
|
|
38
|
+
externalJwt: string;
|
|
39
|
+
/** OIDC IdP origin, e.g. 'http://localhost:8080/auth/realms/opentdf'. */
|
|
40
|
+
oidcOrigin: string;
|
|
41
|
+
/** Override the token endpoint. */
|
|
42
|
+
oidcTokenEndpoint?: string;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Creates a TokenProvider that obtains tokens via the OAuth2 client credentials grant.
|
|
46
|
+
* Tokens are cached and automatically refreshed when expired.
|
|
47
|
+
*
|
|
48
|
+
* **Not for browser use.** Client secrets must not be exposed in client-side code.
|
|
49
|
+
* Use this only in server-side (Node.js/Deno) environments.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* const client = new OpenTDF({
|
|
54
|
+
* interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({
|
|
55
|
+
* clientId: 'opentdf',
|
|
56
|
+
* clientSecret: 'secret',
|
|
57
|
+
* oidcOrigin: 'http://localhost:8080/auth/realms/opentdf',
|
|
58
|
+
* }))],
|
|
59
|
+
* platformUrl: 'http://localhost:8080',
|
|
60
|
+
* });
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export declare function clientCredentialsTokenProvider(options: ClientCredentialsTokenProviderOptions): TokenProvider;
|
|
64
|
+
/**
|
|
65
|
+
* Creates a TokenProvider that uses a refresh token to obtain access tokens.
|
|
66
|
+
* On the first call, exchanges the refresh token. Subsequent calls use the
|
|
67
|
+
* latest refresh token from the IdP response.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* const client = new OpenTDF({
|
|
72
|
+
* interceptors: [authTokenInterceptor(refreshTokenProvider({
|
|
73
|
+
* clientId: 'my-app',
|
|
74
|
+
* refreshToken: 'refresh-token-from-login',
|
|
75
|
+
* oidcOrigin: 'http://localhost:8080/auth/realms/opentdf',
|
|
76
|
+
* }))],
|
|
77
|
+
* platformUrl: 'http://localhost:8080',
|
|
78
|
+
* });
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare function refreshTokenProvider(options: RefreshTokenProviderOptions): TokenProvider;
|
|
82
|
+
/**
|
|
83
|
+
* Creates a TokenProvider that exchanges an external JWT for a platform token
|
|
84
|
+
* via RFC 8693 token exchange. After the initial exchange, uses the refresh
|
|
85
|
+
* token for subsequent calls.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```ts
|
|
89
|
+
* const client = new OpenTDF({
|
|
90
|
+
* interceptors: [authTokenInterceptor(externalJwtTokenProvider({
|
|
91
|
+
* clientId: 'my-app',
|
|
92
|
+
* externalJwt: 'eyJhbGciOi...',
|
|
93
|
+
* oidcOrigin: 'http://localhost:8080/auth/realms/opentdf',
|
|
94
|
+
* }))],
|
|
95
|
+
* platformUrl: 'http://localhost:8080',
|
|
96
|
+
* });
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export declare function externalJwtTokenProvider(options: ExternalJwtTokenProviderOptions): TokenProvider;
|
|
100
|
+
//# sourceMappingURL=token-providers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-providers.d.ts","sourceRoot":"","sources":["../../../../src/auth/token-providers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAIvD;;;;;GAKG;AACH,MAAM,MAAM,qCAAqC,GAAG;IAClD,sBAAsB;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,0BAA0B;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAC;IACnB,+FAA+F;IAC/F,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG;IACxC,sBAAsB;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,YAAY,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAC;IACnB,mCAAmC;IACnC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,+BAA+B,GAAG;IAC5C,sBAAsB;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,gCAAgC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAC;IACnB,mCAAmC;IACnC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAwEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,qCAAqC,GAC7C,aAAa,CA+Bf;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,2BAA2B,GAAG,aAAa,CAmCxF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,+BAA+B,GAAG,aAAa,CA0DhG"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { type AuthProvider, type HttpMethod, HttpRequest, withHeaders } from './auth/auth.js';
|
|
2
2
|
export * as AuthProviders from './auth/providers.js';
|
|
3
3
|
export { authTokenInterceptor, authTokenDPoPInterceptor, authProviderInterceptor, type AuthConfig, type DPoPInterceptor, type DPoPInterceptorOptions, type Interceptor, type TokenProvider, } from './auth/interceptors.js';
|
|
4
|
+
export { clientCredentialsTokenProvider, refreshTokenProvider, externalJwtTokenProvider, type ClientCredentialsTokenProviderOptions, type RefreshTokenProviderOptions, type ExternalJwtTokenProviderOptions, } from './auth/token-providers.js';
|
|
4
5
|
export { attributeFQNsAsValues } from './policy/api.js';
|
|
5
6
|
export { listAttributes, validateAttributes, attributeExists, attributeValueExists, } from './policy/discovery.js';
|
|
6
7
|
export { version, clientType, tdfSpecVersion } from './version.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC9F,OAAO,KAAK,aAAa,MAAM,qBAAqB,CAAC;AACrD,OAAO,EACL,oBAAoB,EACpB,wBAAwB,EACxB,uBAAuB,EACvB,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,KAAK,WAAW,EAChB,KAAK,aAAa,GACnB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,eAAe,EACf,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAClG,cAAc,cAAc,CAAC;AAC7B,OAAO,EACL,QAAQ,EACR,qBAAqB,EACrB,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,wBAAwB,EACxB,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AACrB,cAAc,eAAe,CAAC;AAC9B,cAAc,6BAA6B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC9F,OAAO,KAAK,aAAa,MAAM,qBAAqB,CAAC;AACrD,OAAO,EACL,oBAAoB,EACpB,wBAAwB,EACxB,uBAAuB,EACvB,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,KAAK,WAAW,EAChB,KAAK,aAAa,GACnB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,8BAA8B,EAC9B,oBAAoB,EACpB,wBAAwB,EACxB,KAAK,qCAAqC,EAC1C,KAAK,2BAA2B,EAChC,KAAK,+BAA+B,GACrC,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,eAAe,EACf,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAClG,cAAc,cAAc,CAAC;AAC7B,OAAO,EACL,QAAQ,EACR,qBAAqB,EACrB,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,wBAAwB,EACxB,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AACrB,cAAc,eAAe,CAAC;AAC9B,cAAc,6BAA6B,CAAC"}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { ConfigurationError, TdfError } from '../errors.js';
|
|
2
|
+
import { rstrip } from '../utils.js';
|
|
3
|
+
function resolveTokenEndpoint(oidcOrigin, override) {
|
|
4
|
+
if (override?.trim())
|
|
5
|
+
return override;
|
|
6
|
+
const base = oidcOrigin?.trim();
|
|
7
|
+
if (!base) {
|
|
8
|
+
throw new ConfigurationError('oidcOrigin or oidcTokenEndpoint is required');
|
|
9
|
+
}
|
|
10
|
+
return `${rstrip(base, '/')}/protocol/openid-connect/token`;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Decode a JWT's exp claim without verifying the signature.
|
|
14
|
+
* Returns the expiration time in seconds since epoch, or undefined if not present.
|
|
15
|
+
*/
|
|
16
|
+
function getJwtExpiration(token) {
|
|
17
|
+
try {
|
|
18
|
+
const parts = token.split('.');
|
|
19
|
+
if (parts.length !== 3)
|
|
20
|
+
return undefined;
|
|
21
|
+
// Base64url decode the payload
|
|
22
|
+
const payload = parts[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
23
|
+
const padded = payload + '='.repeat((4 - (payload.length % 4)) % 4);
|
|
24
|
+
const decoded = JSON.parse(atob(padded));
|
|
25
|
+
return typeof decoded.exp === 'number' ? decoded.exp : undefined;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Compute the absolute expiry (seconds since epoch) for a token response.
|
|
33
|
+
* Prefers `expires_in` from the token response, falls back to the JWT `exp` claim.
|
|
34
|
+
*/
|
|
35
|
+
function resolveTokenExpiry(accessToken, expiresIn) {
|
|
36
|
+
if (typeof expiresIn === 'number') {
|
|
37
|
+
return Date.now() / 1000 + expiresIn;
|
|
38
|
+
}
|
|
39
|
+
return getJwtExpiration(accessToken);
|
|
40
|
+
}
|
|
41
|
+
function isTokenExpired(expiry, bufferSeconds = 30) {
|
|
42
|
+
if (expiry === undefined)
|
|
43
|
+
return true;
|
|
44
|
+
return Date.now() / 1000 >= expiry - bufferSeconds;
|
|
45
|
+
}
|
|
46
|
+
async function fetchToken(tokenEndpoint, body) {
|
|
47
|
+
const response = await fetch(tokenEndpoint, {
|
|
48
|
+
method: 'POST',
|
|
49
|
+
headers: {
|
|
50
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
51
|
+
Accept: 'application/json',
|
|
52
|
+
},
|
|
53
|
+
body: new URLSearchParams(body).toString(),
|
|
54
|
+
});
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
const text = await response.text();
|
|
57
|
+
throw new TdfError(`Token request failed: POST [${tokenEndpoint}] => ${response.status} ${response.statusText}: ${text}`);
|
|
58
|
+
}
|
|
59
|
+
return (await response.json());
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Creates a TokenProvider that obtains tokens via the OAuth2 client credentials grant.
|
|
63
|
+
* Tokens are cached and automatically refreshed when expired.
|
|
64
|
+
*
|
|
65
|
+
* **Not for browser use.** Client secrets must not be exposed in client-side code.
|
|
66
|
+
* Use this only in server-side (Node.js/Deno) environments.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* const client = new OpenTDF({
|
|
71
|
+
* interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({
|
|
72
|
+
* clientId: 'opentdf',
|
|
73
|
+
* clientSecret: 'secret',
|
|
74
|
+
* oidcOrigin: 'http://localhost:8080/auth/realms/opentdf',
|
|
75
|
+
* }))],
|
|
76
|
+
* platformUrl: 'http://localhost:8080',
|
|
77
|
+
* });
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export function clientCredentialsTokenProvider(options) {
|
|
81
|
+
if (!options.clientId || !options.clientSecret) {
|
|
82
|
+
throw new ConfigurationError('clientId and clientSecret are required');
|
|
83
|
+
}
|
|
84
|
+
const tokenEndpoint = resolveTokenEndpoint(options.oidcOrigin, options.oidcTokenEndpoint);
|
|
85
|
+
let cachedToken;
|
|
86
|
+
let cachedExpiry;
|
|
87
|
+
let inFlight;
|
|
88
|
+
return async () => {
|
|
89
|
+
if (cachedToken && !isTokenExpired(cachedExpiry)) {
|
|
90
|
+
return cachedToken;
|
|
91
|
+
}
|
|
92
|
+
if (!inFlight) {
|
|
93
|
+
inFlight = (async () => {
|
|
94
|
+
try {
|
|
95
|
+
const resp = await fetchToken(tokenEndpoint, {
|
|
96
|
+
grant_type: 'client_credentials',
|
|
97
|
+
client_id: options.clientId,
|
|
98
|
+
client_secret: options.clientSecret,
|
|
99
|
+
});
|
|
100
|
+
cachedToken = resp.access_token;
|
|
101
|
+
cachedExpiry = resolveTokenExpiry(resp.access_token, resp.expires_in);
|
|
102
|
+
return cachedToken;
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
inFlight = undefined;
|
|
106
|
+
}
|
|
107
|
+
})();
|
|
108
|
+
}
|
|
109
|
+
return inFlight;
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Creates a TokenProvider that uses a refresh token to obtain access tokens.
|
|
114
|
+
* On the first call, exchanges the refresh token. Subsequent calls use the
|
|
115
|
+
* latest refresh token from the IdP response.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts
|
|
119
|
+
* const client = new OpenTDF({
|
|
120
|
+
* interceptors: [authTokenInterceptor(refreshTokenProvider({
|
|
121
|
+
* clientId: 'my-app',
|
|
122
|
+
* refreshToken: 'refresh-token-from-login',
|
|
123
|
+
* oidcOrigin: 'http://localhost:8080/auth/realms/opentdf',
|
|
124
|
+
* }))],
|
|
125
|
+
* platformUrl: 'http://localhost:8080',
|
|
126
|
+
* });
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
export function refreshTokenProvider(options) {
|
|
130
|
+
if (!options.clientId || !options.refreshToken) {
|
|
131
|
+
throw new ConfigurationError('clientId and refreshToken are required');
|
|
132
|
+
}
|
|
133
|
+
const tokenEndpoint = resolveTokenEndpoint(options.oidcOrigin, options.oidcTokenEndpoint);
|
|
134
|
+
let currentRefreshToken = options.refreshToken;
|
|
135
|
+
let cachedToken;
|
|
136
|
+
let cachedExpiry;
|
|
137
|
+
let inFlight;
|
|
138
|
+
return async () => {
|
|
139
|
+
if (cachedToken && !isTokenExpired(cachedExpiry)) {
|
|
140
|
+
return cachedToken;
|
|
141
|
+
}
|
|
142
|
+
if (!inFlight) {
|
|
143
|
+
inFlight = (async () => {
|
|
144
|
+
try {
|
|
145
|
+
const resp = await fetchToken(tokenEndpoint, {
|
|
146
|
+
grant_type: 'refresh_token',
|
|
147
|
+
refresh_token: currentRefreshToken,
|
|
148
|
+
client_id: options.clientId,
|
|
149
|
+
});
|
|
150
|
+
cachedToken = resp.access_token;
|
|
151
|
+
cachedExpiry = resolveTokenExpiry(resp.access_token, resp.expires_in);
|
|
152
|
+
if (resp.refresh_token) {
|
|
153
|
+
currentRefreshToken = resp.refresh_token;
|
|
154
|
+
}
|
|
155
|
+
return cachedToken;
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
inFlight = undefined;
|
|
159
|
+
}
|
|
160
|
+
})();
|
|
161
|
+
}
|
|
162
|
+
return inFlight;
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Creates a TokenProvider that exchanges an external JWT for a platform token
|
|
167
|
+
* via RFC 8693 token exchange. After the initial exchange, uses the refresh
|
|
168
|
+
* token for subsequent calls.
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```ts
|
|
172
|
+
* const client = new OpenTDF({
|
|
173
|
+
* interceptors: [authTokenInterceptor(externalJwtTokenProvider({
|
|
174
|
+
* clientId: 'my-app',
|
|
175
|
+
* externalJwt: 'eyJhbGciOi...',
|
|
176
|
+
* oidcOrigin: 'http://localhost:8080/auth/realms/opentdf',
|
|
177
|
+
* }))],
|
|
178
|
+
* platformUrl: 'http://localhost:8080',
|
|
179
|
+
* });
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
export function externalJwtTokenProvider(options) {
|
|
183
|
+
if (!options.clientId || !options.externalJwt) {
|
|
184
|
+
throw new ConfigurationError('clientId and externalJwt are required');
|
|
185
|
+
}
|
|
186
|
+
const tokenEndpoint = resolveTokenEndpoint(options.oidcOrigin, options.oidcTokenEndpoint);
|
|
187
|
+
let cachedToken;
|
|
188
|
+
let cachedExpiry;
|
|
189
|
+
let currentRefreshToken;
|
|
190
|
+
let initialExchangeDone = false;
|
|
191
|
+
let inFlight;
|
|
192
|
+
return async () => {
|
|
193
|
+
if (cachedToken && !isTokenExpired(cachedExpiry)) {
|
|
194
|
+
return cachedToken;
|
|
195
|
+
}
|
|
196
|
+
if (!inFlight) {
|
|
197
|
+
inFlight = (async () => {
|
|
198
|
+
try {
|
|
199
|
+
let resp;
|
|
200
|
+
if (!initialExchangeDone) {
|
|
201
|
+
resp = await fetchToken(tokenEndpoint, {
|
|
202
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
|
|
203
|
+
subject_token: options.externalJwt,
|
|
204
|
+
subject_token_type: 'urn:ietf:params:oauth:token-type:jwt',
|
|
205
|
+
audience: options.clientId,
|
|
206
|
+
client_id: options.clientId,
|
|
207
|
+
});
|
|
208
|
+
initialExchangeDone = true;
|
|
209
|
+
}
|
|
210
|
+
else if (currentRefreshToken) {
|
|
211
|
+
resp = await fetchToken(tokenEndpoint, {
|
|
212
|
+
grant_type: 'refresh_token',
|
|
213
|
+
refresh_token: currentRefreshToken,
|
|
214
|
+
client_id: options.clientId,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
// Re-exchange the original JWT if no refresh token available
|
|
219
|
+
resp = await fetchToken(tokenEndpoint, {
|
|
220
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
|
|
221
|
+
subject_token: options.externalJwt,
|
|
222
|
+
subject_token_type: 'urn:ietf:params:oauth:token-type:jwt',
|
|
223
|
+
audience: options.clientId,
|
|
224
|
+
client_id: options.clientId,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
cachedToken = resp.access_token;
|
|
228
|
+
cachedExpiry = resolveTokenExpiry(resp.access_token, resp.expires_in);
|
|
229
|
+
if (resp.refresh_token) {
|
|
230
|
+
currentRefreshToken = resp.refresh_token;
|
|
231
|
+
}
|
|
232
|
+
return cachedToken;
|
|
233
|
+
}
|
|
234
|
+
finally {
|
|
235
|
+
inFlight = undefined;
|
|
236
|
+
}
|
|
237
|
+
})();
|
|
238
|
+
}
|
|
239
|
+
return inFlight;
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9rZW4tcHJvdmlkZXJzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2F1dGgvdG9rZW4tcHJvdmlkZXJzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUNBLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxRQUFRLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFDNUQsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGFBQWEsQ0FBQztBQXFEckMsU0FBUyxvQkFBb0IsQ0FBQyxVQUFrQixFQUFFLFFBQWlCO0lBQ2pFLElBQUksUUFBUSxFQUFFLElBQUksRUFBRTtRQUFFLE9BQU8sUUFBUSxDQUFDO0lBQ3RDLE1BQU0sSUFBSSxHQUFHLFVBQVUsRUFBRSxJQUFJLEVBQUUsQ0FBQztJQUNoQyxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDVixNQUFNLElBQUksa0JBQWtCLENBQUMsNkNBQTZDLENBQUMsQ0FBQztJQUM5RSxDQUFDO0lBQ0QsT0FBTyxHQUFHLE1BQU0sQ0FBQyxJQUFJLEVBQUUsR0FBRyxDQUFDLGdDQUFnQyxDQUFDO0FBQzlELENBQUM7QUFFRDs7O0dBR0c7QUFDSCxTQUFTLGdCQUFnQixDQUFDLEtBQWE7SUFDckMsSUFBSSxDQUFDO1FBQ0gsTUFBTSxLQUFLLEdBQUcsS0FBSyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUMvQixJQUFJLEtBQUssQ0FBQyxNQUFNLEtBQUssQ0FBQztZQUFFLE9BQU8sU0FBUyxDQUFDO1FBQ3pDLCtCQUErQjtRQUMvQixNQUFNLE9BQU8sR0FBRyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxHQUFHLENBQUMsQ0FBQyxPQUFPLENBQUMsSUFBSSxFQUFFLEdBQUcsQ0FBQyxDQUFDO1FBQy9ELE1BQU0sTUFBTSxHQUFHLE9BQU8sR0FBRyxHQUFHLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLE1BQU0s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
|
package/dist/web/src/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { HttpRequest, withHeaders } from './auth/auth.js';
|
|
2
2
|
export * as AuthProviders from './auth/providers.js';
|
|
3
3
|
export { authTokenInterceptor, authTokenDPoPInterceptor, authProviderInterceptor, } from './auth/interceptors.js';
|
|
4
|
+
export { clientCredentialsTokenProvider, refreshTokenProvider, externalJwtTokenProvider, } from './auth/token-providers.js';
|
|
4
5
|
export { attributeFQNsAsValues } from './policy/api.js';
|
|
5
6
|
export { listAttributes, validateAttributes, attributeExists, attributeValueExists, } from './policy/discovery.js';
|
|
6
7
|
export { version, clientType, tdfSpecVersion } from './version.js';
|
|
@@ -9,4 +10,4 @@ export * from './opentdf.js';
|
|
|
9
10
|
export { TdfError, PermissionDeniedError, IntegrityError, InvalidFileError, DecryptError, NetworkError, AttributeValidationError, AttributeNotFoundError, ConfigurationError, } from './errors.js';
|
|
10
11
|
export * from './seekable.js';
|
|
11
12
|
export * from '../tdf3/src/models/index.js';
|
|
12
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
13
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFzQyxXQUFXLEVBQUUsV0FBVyxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDOUYsT0FBTyxLQUFLLGFBQWEsTUFBTSxxQkFBcUIsQ0FBQztBQUNyRCxPQUFPLEVBQ0wsb0JBQW9CLEVBQ3BCLHdCQUF3QixFQUN4Qix1QkFBdUIsR0FNeEIsTUFBTSx3QkFBd0IsQ0FBQztBQUNoQyxPQUFPLEVBQ0wsOEJBQThCLEVBQzlCLG9CQUFvQixFQUNwQix3QkFBd0IsR0FJekIsTUFBTSwyQkFBMkIsQ0FBQztBQUNuQyxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUN4RCxPQUFPLEVBQ0wsY0FBYyxFQUNkLGtCQUFrQixFQUNsQixlQUFlLEVBQ2Ysb0JBQW9CLEdBQ3JCLE1BQU0sdUJBQXVCLENBQUM7QUFDL0IsT0FBTyxFQUFFLE9BQU8sRUFBRSxVQUFVLEVBQUUsY0FBYyxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQ25FLE9BQU8sRUFBRSxjQUFjLEVBQXFELE1BQU0sZUFBZSxDQUFDO0FBQ2xHLGNBQWMsY0FBYyxDQUFDO0FBQzdCLE9BQU8sRUFDTCxRQUFRLEVBQ1IscUJBQXFCLEVBQ3JCLGNBQWMsRUFDZCxnQkFBZ0IsRUFDaEIsWUFBWSxFQUNaLFlBQVksRUFDWix3QkFBd0IsRUFDeEIsc0JBQXNCLEVBQ3RCLGtCQUFrQixHQUNuQixNQUFNLGFBQWEsQ0FBQztBQUNyQixjQUFjLGVBQWUsQ0FBQztBQUM5QixjQUFjLDZCQUE2QixDQUFDIn0=
|
package/package.json
CHANGED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { type TokenProvider } from './interceptors.js';
|
|
2
|
+
import { ConfigurationError, TdfError } from '../errors.js';
|
|
3
|
+
import { rstrip } from '../utils.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Options for client credentials token provider.
|
|
7
|
+
*
|
|
8
|
+
* **Not for browser use.** Client secrets must not be exposed in client-side code.
|
|
9
|
+
* Use this only in server-side (Node.js/Deno) environments.
|
|
10
|
+
*/
|
|
11
|
+
export type ClientCredentialsTokenProviderOptions = {
|
|
12
|
+
/** OIDC client ID. */
|
|
13
|
+
clientId: string;
|
|
14
|
+
/** OIDC client secret. */
|
|
15
|
+
clientSecret: string;
|
|
16
|
+
/** OIDC IdP origin, e.g. 'http://localhost:8080/auth/realms/opentdf'. */
|
|
17
|
+
oidcOrigin: string;
|
|
18
|
+
/** Override the token endpoint (defaults to `${oidcOrigin}/protocol/openid-connect/token`). */
|
|
19
|
+
oidcTokenEndpoint?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Options for refresh token provider.
|
|
24
|
+
*/
|
|
25
|
+
export type RefreshTokenProviderOptions = {
|
|
26
|
+
/** OIDC client ID. */
|
|
27
|
+
clientId: string;
|
|
28
|
+
/** Refresh token obtained from a prior login flow. */
|
|
29
|
+
refreshToken: string;
|
|
30
|
+
/** OIDC IdP origin, e.g. 'http://localhost:8080/auth/realms/opentdf'. */
|
|
31
|
+
oidcOrigin: string;
|
|
32
|
+
/** Override the token endpoint. */
|
|
33
|
+
oidcTokenEndpoint?: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Options for external JWT token provider (RFC 8693 token exchange).
|
|
38
|
+
*/
|
|
39
|
+
export type ExternalJwtTokenProviderOptions = {
|
|
40
|
+
/** OIDC client ID. */
|
|
41
|
+
clientId: string;
|
|
42
|
+
/** External JWT to exchange. */
|
|
43
|
+
externalJwt: string;
|
|
44
|
+
/** OIDC IdP origin, e.g. 'http://localhost:8080/auth/realms/opentdf'. */
|
|
45
|
+
oidcOrigin: string;
|
|
46
|
+
/** Override the token endpoint. */
|
|
47
|
+
oidcTokenEndpoint?: string;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
type TokenResponse = {
|
|
51
|
+
access_token: string;
|
|
52
|
+
refresh_token?: string;
|
|
53
|
+
expires_in?: number;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
function resolveTokenEndpoint(oidcOrigin: string, override?: string): string {
|
|
57
|
+
if (override?.trim()) return override;
|
|
58
|
+
const base = oidcOrigin?.trim();
|
|
59
|
+
if (!base) {
|
|
60
|
+
throw new ConfigurationError('oidcOrigin or oidcTokenEndpoint is required');
|
|
61
|
+
}
|
|
62
|
+
return `${rstrip(base, '/')}/protocol/openid-connect/token`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Decode a JWT's exp claim without verifying the signature.
|
|
67
|
+
* Returns the expiration time in seconds since epoch, or undefined if not present.
|
|
68
|
+
*/
|
|
69
|
+
function getJwtExpiration(token: string): number | undefined {
|
|
70
|
+
try {
|
|
71
|
+
const parts = token.split('.');
|
|
72
|
+
if (parts.length !== 3) return undefined;
|
|
73
|
+
// Base64url decode the payload
|
|
74
|
+
const payload = parts[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
75
|
+
const padded = payload + '='.repeat((4 - (payload.length % 4)) % 4);
|
|
76
|
+
const decoded = JSON.parse(atob(padded));
|
|
77
|
+
return typeof decoded.exp === 'number' ? decoded.exp : undefined;
|
|
78
|
+
} catch {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Compute the absolute expiry (seconds since epoch) for a token response.
|
|
85
|
+
* Prefers `expires_in` from the token response, falls back to the JWT `exp` claim.
|
|
86
|
+
*/
|
|
87
|
+
function resolveTokenExpiry(accessToken: string, expiresIn?: number): number | undefined {
|
|
88
|
+
if (typeof expiresIn === 'number') {
|
|
89
|
+
return Date.now() / 1000 + expiresIn;
|
|
90
|
+
}
|
|
91
|
+
return getJwtExpiration(accessToken);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isTokenExpired(expiry: number | undefined, bufferSeconds = 30): boolean {
|
|
95
|
+
if (expiry === undefined) return true;
|
|
96
|
+
return Date.now() / 1000 >= expiry - bufferSeconds;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function fetchToken(
|
|
100
|
+
tokenEndpoint: string,
|
|
101
|
+
body: Record<string, string>
|
|
102
|
+
): Promise<TokenResponse> {
|
|
103
|
+
const response = await fetch(tokenEndpoint, {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
headers: {
|
|
106
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
107
|
+
Accept: 'application/json',
|
|
108
|
+
},
|
|
109
|
+
body: new URLSearchParams(body).toString(),
|
|
110
|
+
});
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
const text = await response.text();
|
|
113
|
+
throw new TdfError(
|
|
114
|
+
`Token request failed: POST [${tokenEndpoint}] => ${response.status} ${response.statusText}: ${text}`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
return (await response.json()) as TokenResponse;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Creates a TokenProvider that obtains tokens via the OAuth2 client credentials grant.
|
|
122
|
+
* Tokens are cached and automatically refreshed when expired.
|
|
123
|
+
*
|
|
124
|
+
* **Not for browser use.** Client secrets must not be exposed in client-side code.
|
|
125
|
+
* Use this only in server-side (Node.js/Deno) environments.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```ts
|
|
129
|
+
* const client = new OpenTDF({
|
|
130
|
+
* interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({
|
|
131
|
+
* clientId: 'opentdf',
|
|
132
|
+
* clientSecret: 'secret',
|
|
133
|
+
* oidcOrigin: 'http://localhost:8080/auth/realms/opentdf',
|
|
134
|
+
* }))],
|
|
135
|
+
* platformUrl: 'http://localhost:8080',
|
|
136
|
+
* });
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
export function clientCredentialsTokenProvider(
|
|
140
|
+
options: ClientCredentialsTokenProviderOptions
|
|
141
|
+
): TokenProvider {
|
|
142
|
+
if (!options.clientId || !options.clientSecret) {
|
|
143
|
+
throw new ConfigurationError('clientId and clientSecret are required');
|
|
144
|
+
}
|
|
145
|
+
const tokenEndpoint = resolveTokenEndpoint(options.oidcOrigin, options.oidcTokenEndpoint);
|
|
146
|
+
let cachedToken: string | undefined;
|
|
147
|
+
let cachedExpiry: number | undefined;
|
|
148
|
+
let inFlight: Promise<string> | undefined;
|
|
149
|
+
|
|
150
|
+
return async () => {
|
|
151
|
+
if (cachedToken && !isTokenExpired(cachedExpiry)) {
|
|
152
|
+
return cachedToken;
|
|
153
|
+
}
|
|
154
|
+
if (!inFlight) {
|
|
155
|
+
inFlight = (async () => {
|
|
156
|
+
try {
|
|
157
|
+
const resp = await fetchToken(tokenEndpoint, {
|
|
158
|
+
grant_type: 'client_credentials',
|
|
159
|
+
client_id: options.clientId,
|
|
160
|
+
client_secret: options.clientSecret,
|
|
161
|
+
});
|
|
162
|
+
cachedToken = resp.access_token;
|
|
163
|
+
cachedExpiry = resolveTokenExpiry(resp.access_token, resp.expires_in);
|
|
164
|
+
return cachedToken;
|
|
165
|
+
} finally {
|
|
166
|
+
inFlight = undefined;
|
|
167
|
+
}
|
|
168
|
+
})();
|
|
169
|
+
}
|
|
170
|
+
return inFlight;
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Creates a TokenProvider that uses a refresh token to obtain access tokens.
|
|
176
|
+
* On the first call, exchanges the refresh token. Subsequent calls use the
|
|
177
|
+
* latest refresh token from the IdP response.
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```ts
|
|
181
|
+
* const client = new OpenTDF({
|
|
182
|
+
* interceptors: [authTokenInterceptor(refreshTokenProvider({
|
|
183
|
+
* clientId: 'my-app',
|
|
184
|
+
* refreshToken: 'refresh-token-from-login',
|
|
185
|
+
* oidcOrigin: 'http://localhost:8080/auth/realms/opentdf',
|
|
186
|
+
* }))],
|
|
187
|
+
* platformUrl: 'http://localhost:8080',
|
|
188
|
+
* });
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
export function refreshTokenProvider(options: RefreshTokenProviderOptions): TokenProvider {
|
|
192
|
+
if (!options.clientId || !options.refreshToken) {
|
|
193
|
+
throw new ConfigurationError('clientId and refreshToken are required');
|
|
194
|
+
}
|
|
195
|
+
const tokenEndpoint = resolveTokenEndpoint(options.oidcOrigin, options.oidcTokenEndpoint);
|
|
196
|
+
let currentRefreshToken = options.refreshToken;
|
|
197
|
+
let cachedToken: string | undefined;
|
|
198
|
+
let cachedExpiry: number | undefined;
|
|
199
|
+
let inFlight: Promise<string> | undefined;
|
|
200
|
+
|
|
201
|
+
return async () => {
|
|
202
|
+
if (cachedToken && !isTokenExpired(cachedExpiry)) {
|
|
203
|
+
return cachedToken;
|
|
204
|
+
}
|
|
205
|
+
if (!inFlight) {
|
|
206
|
+
inFlight = (async () => {
|
|
207
|
+
try {
|
|
208
|
+
const resp = await fetchToken(tokenEndpoint, {
|
|
209
|
+
grant_type: 'refresh_token',
|
|
210
|
+
refresh_token: currentRefreshToken,
|
|
211
|
+
client_id: options.clientId,
|
|
212
|
+
});
|
|
213
|
+
cachedToken = resp.access_token;
|
|
214
|
+
cachedExpiry = resolveTokenExpiry(resp.access_token, resp.expires_in);
|
|
215
|
+
if (resp.refresh_token) {
|
|
216
|
+
currentRefreshToken = resp.refresh_token;
|
|
217
|
+
}
|
|
218
|
+
return cachedToken;
|
|
219
|
+
} finally {
|
|
220
|
+
inFlight = undefined;
|
|
221
|
+
}
|
|
222
|
+
})();
|
|
223
|
+
}
|
|
224
|
+
return inFlight;
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Creates a TokenProvider that exchanges an external JWT for a platform token
|
|
230
|
+
* via RFC 8693 token exchange. After the initial exchange, uses the refresh
|
|
231
|
+
* token for subsequent calls.
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* ```ts
|
|
235
|
+
* const client = new OpenTDF({
|
|
236
|
+
* interceptors: [authTokenInterceptor(externalJwtTokenProvider({
|
|
237
|
+
* clientId: 'my-app',
|
|
238
|
+
* externalJwt: 'eyJhbGciOi...',
|
|
239
|
+
* oidcOrigin: 'http://localhost:8080/auth/realms/opentdf',
|
|
240
|
+
* }))],
|
|
241
|
+
* platformUrl: 'http://localhost:8080',
|
|
242
|
+
* });
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
245
|
+
export function externalJwtTokenProvider(options: ExternalJwtTokenProviderOptions): TokenProvider {
|
|
246
|
+
if (!options.clientId || !options.externalJwt) {
|
|
247
|
+
throw new ConfigurationError('clientId and externalJwt are required');
|
|
248
|
+
}
|
|
249
|
+
const tokenEndpoint = resolveTokenEndpoint(options.oidcOrigin, options.oidcTokenEndpoint);
|
|
250
|
+
let cachedToken: string | undefined;
|
|
251
|
+
let cachedExpiry: number | undefined;
|
|
252
|
+
let currentRefreshToken: string | undefined;
|
|
253
|
+
let initialExchangeDone = false;
|
|
254
|
+
let inFlight: Promise<string> | undefined;
|
|
255
|
+
|
|
256
|
+
return async () => {
|
|
257
|
+
if (cachedToken && !isTokenExpired(cachedExpiry)) {
|
|
258
|
+
return cachedToken;
|
|
259
|
+
}
|
|
260
|
+
if (!inFlight) {
|
|
261
|
+
inFlight = (async () => {
|
|
262
|
+
try {
|
|
263
|
+
let resp: TokenResponse;
|
|
264
|
+
if (!initialExchangeDone) {
|
|
265
|
+
resp = await fetchToken(tokenEndpoint, {
|
|
266
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
|
|
267
|
+
subject_token: options.externalJwt,
|
|
268
|
+
subject_token_type: 'urn:ietf:params:oauth:token-type:jwt',
|
|
269
|
+
audience: options.clientId,
|
|
270
|
+
client_id: options.clientId,
|
|
271
|
+
});
|
|
272
|
+
initialExchangeDone = true;
|
|
273
|
+
} else if (currentRefreshToken) {
|
|
274
|
+
resp = await fetchToken(tokenEndpoint, {
|
|
275
|
+
grant_type: 'refresh_token',
|
|
276
|
+
refresh_token: currentRefreshToken,
|
|
277
|
+
client_id: options.clientId,
|
|
278
|
+
});
|
|
279
|
+
} else {
|
|
280
|
+
// Re-exchange the original JWT if no refresh token available
|
|
281
|
+
resp = await fetchToken(tokenEndpoint, {
|
|
282
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
|
|
283
|
+
subject_token: options.externalJwt,
|
|
284
|
+
subject_token_type: 'urn:ietf:params:oauth:token-type:jwt',
|
|
285
|
+
audience: options.clientId,
|
|
286
|
+
client_id: options.clientId,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
cachedToken = resp.access_token;
|
|
291
|
+
cachedExpiry = resolveTokenExpiry(resp.access_token, resp.expires_in);
|
|
292
|
+
if (resp.refresh_token) {
|
|
293
|
+
currentRefreshToken = resp.refresh_token;
|
|
294
|
+
}
|
|
295
|
+
return cachedToken;
|
|
296
|
+
} finally {
|
|
297
|
+
inFlight = undefined;
|
|
298
|
+
}
|
|
299
|
+
})();
|
|
300
|
+
}
|
|
301
|
+
return inFlight;
|
|
302
|
+
};
|
|
303
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,14 @@ export {
|
|
|
10
10
|
type Interceptor,
|
|
11
11
|
type TokenProvider,
|
|
12
12
|
} from './auth/interceptors.js';
|
|
13
|
+
export {
|
|
14
|
+
clientCredentialsTokenProvider,
|
|
15
|
+
refreshTokenProvider,
|
|
16
|
+
externalJwtTokenProvider,
|
|
17
|
+
type ClientCredentialsTokenProviderOptions,
|
|
18
|
+
type RefreshTokenProviderOptions,
|
|
19
|
+
type ExternalJwtTokenProviderOptions,
|
|
20
|
+
} from './auth/token-providers.js';
|
|
13
21
|
export { attributeFQNsAsValues } from './policy/api.js';
|
|
14
22
|
export {
|
|
15
23
|
listAttributes,
|