@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.
@@ -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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
@@ -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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsMENBQThGO0FBQWpELHNHQUFBLFdBQVcsT0FBQTtBQUFFLHNHQUFBLFdBQVcsT0FBQTtBQUNyRSxxRUFBcUQ7QUFDckQsMERBU2dDO0FBUjlCLHVIQUFBLG9CQUFvQixPQUFBO0FBQ3BCLDJIQUFBLHdCQUF3QixPQUFBO0FBQ3hCLDBIQUFBLHVCQUF1QixPQUFBO0FBT3pCLDBDQUF3RDtBQUEvQywrR0FBQSxxQkFBcUIsT0FBQTtBQUM5QixzREFLK0I7QUFKN0IsOEdBQUEsY0FBYyxPQUFBO0FBQ2Qsa0hBQUEsa0JBQWtCLE9BQUE7QUFDbEIsK0dBQUEsZUFBZSxPQUFBO0FBQ2Ysb0hBQUEsb0JBQW9CLE9BQUE7QUFFdEIsMkNBQW1FO0FBQTFELHFHQUFBLE9BQU8sT0FBQTtBQUFFLHdHQUFBLFVBQVUsT0FBQTtBQUFFLDRHQUFBLGNBQWMsT0FBQTtBQUM1Qyw2Q0FBa0c7QUFBekYsNkdBQUEsY0FBYyxPQUFBO0FBQ3ZCLCtDQUE2QjtBQUM3Qix5Q0FVcUI7QUFUbkIscUdBQUEsUUFBUSxPQUFBO0FBQ1Isa0hBQUEscUJBQXFCLE9BQUE7QUFDckIsMkdBQUEsY0FBYyxPQUFBO0FBQ2QsNkdBQUEsZ0JBQWdCLE9BQUE7QUFDaEIseUdBQUEsWUFBWSxPQUFBO0FBQ1oseUdBQUEsWUFBWSxPQUFBO0FBQ1oscUhBQUEsd0JBQXdCLE9BQUE7QUFDeEIsbUhBQUEsc0JBQXNCLE9BQUE7QUFDdEIsK0dBQUEsa0JBQWtCLE9BQUE7QUFFcEIsZ0RBQThCO0FBQzlCLDhEQUE0QyJ9
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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
@@ -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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFzQyxXQUFXLEVBQUUsV0FBVyxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDOUYsT0FBTyxLQUFLLGFBQWEsTUFBTSxxQkFBcUIsQ0FBQztBQUNyRCxPQUFPLEVBQ0wsb0JBQW9CLEVBQ3BCLHdCQUF3QixFQUN4Qix1QkFBdUIsR0FNeEIsTUFBTSx3QkFBd0IsQ0FBQztBQUNoQyxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUN4RCxPQUFPLEVBQ0wsY0FBYyxFQUNkLGtCQUFrQixFQUNsQixlQUFlLEVBQ2Ysb0JBQW9CLEdBQ3JCLE1BQU0sdUJBQXVCLENBQUM7QUFDL0IsT0FBTyxFQUFFLE9BQU8sRUFBRSxVQUFVLEVBQUUsY0FBYyxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQ25FLE9BQU8sRUFBRSxjQUFjLEVBQXFELE1BQU0sZUFBZSxDQUFDO0FBQ2xHLGNBQWMsY0FBYyxDQUFDO0FBQzdCLE9BQU8sRUFDTCxRQUFRLEVBQ1IscUJBQXFCLEVBQ3JCLGNBQWMsRUFDZCxnQkFBZ0IsRUFDaEIsWUFBWSxFQUNaLFlBQVksRUFDWix3QkFBd0IsRUFDeEIsc0JBQXNCLEVBQ3RCLGtCQUFrQixHQUNuQixNQUFNLGFBQWEsQ0FBQztBQUNyQixjQUFjLGVBQWUsQ0FBQztBQUM5QixjQUFjLDZCQUE2QixDQUFDIn0=
13
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFzQyxXQUFXLEVBQUUsV0FBVyxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDOUYsT0FBTyxLQUFLLGFBQWEsTUFBTSxxQkFBcUIsQ0FBQztBQUNyRCxPQUFPLEVBQ0wsb0JBQW9CLEVBQ3BCLHdCQUF3QixFQUN4Qix1QkFBdUIsR0FNeEIsTUFBTSx3QkFBd0IsQ0FBQztBQUNoQyxPQUFPLEVBQ0wsOEJBQThCLEVBQzlCLG9CQUFvQixFQUNwQix3QkFBd0IsR0FJekIsTUFBTSwyQkFBMkIsQ0FBQztBQUNuQyxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUN4RCxPQUFPLEVBQ0wsY0FBYyxFQUNkLGtCQUFrQixFQUNsQixlQUFlLEVBQ2Ysb0JBQW9CLEdBQ3JCLE1BQU0sdUJBQXVCLENBQUM7QUFDL0IsT0FBTyxFQUFFLE9BQU8sRUFBRSxVQUFVLEVBQUUsY0FBYyxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQ25FLE9BQU8sRUFBRSxjQUFjLEVBQXFELE1BQU0sZUFBZSxDQUFDO0FBQ2xHLGNBQWMsY0FBYyxDQUFDO0FBQzdCLE9BQU8sRUFDTCxRQUFRLEVBQ1IscUJBQXFCLEVBQ3JCLGNBQWMsRUFDZCxnQkFBZ0IsRUFDaEIsWUFBWSxFQUNaLFlBQVksRUFDWix3QkFBd0IsRUFDeEIsc0JBQXNCLEVBQ3RCLGtCQUFrQixHQUNuQixNQUFNLGFBQWEsQ0FBQztBQUNyQixjQUFjLGVBQWUsQ0FBQztBQUM5QixjQUFjLDZCQUE2QixDQUFDIn0=
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opentdf/sdk",
3
- "version": "0.13.0-beta.123",
3
+ "version": "0.13.0-beta.125",
4
4
  "description": "OpenTDF for the Web",
5
5
  "homepage": "https://github.com/opentdf/web-sdk",
6
6
  "bugs": {
@@ -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,