diff --git a/.eslintrc b/.eslintrc
index 24e0be7..905e6dd 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -26,6 +26,7 @@
"allowModules": ["jose"]
}],
- "@typescript-eslint/no-var-requires": "off"
+ "@typescript-eslint/no-var-requires": "off",
+ "@typescript-eslint/ban-ts-comment": "off"
}
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 750d3c3..7bf9ae4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,3 +6,4 @@ This is the changelog for [Authress SDK](readme.md).
* Added `getUserRolesForResource` for access to user roles on a resource.
* Add `Last-Modified` and `If-Unmodified-Since` support to access record updates.
* Add `Groups` to `AccessRecords`
+* Add `ConnectionsApi` to fetch user credentials for a specific connection
diff --git a/index.d.ts b/index.d.ts
index c20ec6f..8f05f26 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -1243,13 +1243,14 @@ export interface UserPermissionsApi {
/**
* Billable Does the user have the specified permissions to the resource?
* @summary Check to see if a user has permissions to a resource.
- * @param {string} userId The user to check permissions on
+ * @param {string} [userId] The user to check permissions on
* @param {string} resourceUri The uri path of a resource to validate, must be URL encoded, uri segments are allowed, the resource must be a full path, and permissions are not inherited by sub-resources.
* @param {string} permission Permission to check, '*' and scoped permissions can also be checked here.
* @throws {ArgumentRequiredError}
* @throws {UnauthorizedError}
*/
- authorizeUser(userId: string, resourceUri: string, permission: string): Promise>;
+ // @ts-ignore
+ authorizeUser(userId?: string, resourceUri: string, permission: string): Promise>;
/**
* Billable Permanently disable a token. To be used after the token has completed its use. Should be called on all tokens to ensure they are not active indefinitely.
* @summary Disable a token
@@ -1257,6 +1258,7 @@ export interface UserPermissionsApi {
* @param {string} tokenId The relevant token identifier
* @throws {ArgumentRequiredError}
*/
+ // @ts-ignore
disableUserToken(userId?: string, tokenId: string): Promise>;
/**
* Billable Get a summary of the permissions a user has to a particular resource.
@@ -1265,6 +1267,7 @@ export interface UserPermissionsApi {
* @param {string} resourceUri The uri path of a resource to validate, must be URL encoded, uri segments are allowed.
* @throws {ArgumentRequiredError}
*/
+ // @ts-ignore
getUserPermissionsForResource(userId?: string, resourceUri: string): Promise>;
/**
* Billable Get a summary of the roles a user has to a particular resource. Users can be assigned roles from multiple access records, this may cause the same role to appear in the list more than once.
READ: Authress:UserPermissions/{userId}
@@ -1273,6 +1276,7 @@ export interface UserPermissionsApi {
* @param {string} resourceUri The uri path of a resource to get roles for, must be URL encoded. Checks for explicit resource roles, roles attached to parent resources are not returned.
* @throws {ArgumentRequiredError}
*/
+ // @ts-ignore
getUserRolesForResource(userId?: string, resourceUri: string): Promise>;
/**
* Billable Get the users resources. Get the users resources. This result is a list of resource uris that a user has an explicit permission to, a user with * access to all sub resources will return an empty list and {accessToAllSubResources} will be populated. To get a user's list of resources in these cases, it is recommended to also check explicit access to the collection resource, using the authorizeUser endpoint. In the case that the user only has access to a subset of resources in a collection, the list will be paginated.
@@ -1292,6 +1296,7 @@ export interface UserPermissionsApi {
* @param {TokenRequest} body The contents of the permission to set on the token. Will be used instead of the users or clients full permissions. Cannot include permissions that the user or client do not have.
* @throws {ArgumentRequiredError}
*/
+ // @ts-ignore
requestUserToken(userId?: string, body: TokenRequest): Promise>;
}
@@ -1373,6 +1378,19 @@ export class ServiceClientTokenProvider {
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
constructor(accessKey: string);
+
+ /**
+ * @summary Generate a token from this token provider. In most cases should only be used by this library itself
+ * @type {Function>}
+ */
+ getToken(): Promise;
+
+ /**
+ * @summary Create an Authress auth code assertion to exchange with the Authress identity API to generate a JWT for the user
+ * @type {Function>}
+ * @param {string} userId The user to generate a JWT for.
+ */
+ createAuthorizationCode(userId: string): Promise;
}
/**
diff --git a/src/httpClient.js b/src/httpClient.js
index c583091..0329ca9 100644
--- a/src/httpClient.js
+++ b/src/httpClient.js
@@ -14,7 +14,7 @@ class HttpClient {
const client = axios.create({ baseURL: baseUrl });
client.interceptors.request.use(async config => {
- const token = await this.tokenProvider();
+ const token = await typeof (this.tokenProvider === 'function') ? this.tokenProvider() : this.tokenProvider.getToken();
config.headers = {
...config.headers,
Authorization: `Bearer ${token}`
diff --git a/src/serviceClientTokenProvider.js b/src/serviceClientTokenProvider.js
index 9491638..b753ccc 100644
--- a/src/serviceClientTokenProvider.js
+++ b/src/serviceClientTokenProvider.js
@@ -3,8 +3,8 @@ const { default: JwtSigner } = require('jose/jwt/sign');
const { createPrivateKey } = require('crypto');
module.exports = function(accessKey) {
- return async () => {
- if (this.cachedKeyData && this.cachedKeyData.token && this.cachedKeyData.expires > Date.now() + 3600000) {
+ const innerGetToken = async (overrideUserId, expOffset) => {
+ if (!overrideUserId && this.cachedKeyData && this.cachedKeyData.token && this.cachedKeyData.expires > Date.now() + 3600000) {
return this.cachedKeyData.token;
}
@@ -26,22 +26,31 @@ module.exports = function(accessKey) {
const jwt = {
aud: decodedAccessKey.audience,
iss: `https://api.authress.io/v1/clients/${encodeURIComponent(decodedAccessKey.clientId)}`,
- sub: decodedAccessKey.clientId,
+ sub: overrideUserId || decodedAccessKey.clientId,
iat: now,
// valid for 24 hours
- exp: now + 60 * 60 * 24,
+ exp: now + (expOffset || 60 * 60 * 24),
scope: 'openid'
};
if (alg === 'RS256') {
const options = { algorithm: 'RS256', keyid: decodedAccessKey.keyId };
const token = await jwtManager.sign(jwt, decodedAccessKey.privateKey, options);
- this.cachedKeyData = { token, expires: jwt.exp * 1000 };
+ if (!overrideUserId) {
+ this.cachedKeyData = { token, expires: jwt.exp * 1000 };
+ }
return token;
}
const importedKey = createPrivateKey({ key: Buffer.from(decodedAccessKey.privateKey, 'base64'), format: 'der', type: 'pkcs8' });
const token = await new JwtSigner(jwt).setProtectedHeader({ alg: 'EdDSA', kid: decodedAccessKey.keyId }).sign(importedKey);
+ if (!overrideUserId) {
+ this.cachedKeyData = { token, expires: jwt.exp * 1000 };
+ }
return token;
};
+
+ innerGetToken.getToken = innerGetToken;
+ innerGetToken.createAuthorizationCode = userId => innerGetToken(userId, 60);
+ return innerGetToken;
};
diff --git a/tests/serviceClientTokenProvider.test.js b/tests/serviceClientTokenProvider.test.js
index 7010186..1b529dd 100644
--- a/tests/serviceClientTokenProvider.test.js
+++ b/tests/serviceClientTokenProvider.test.js
@@ -11,10 +11,9 @@ afterEach(() => sandbox.restore());
describe('serviceClientTokenProvider.js', () => {
describe('getToken', () => {
it('Validate cache tokens work', async () => {
- const accessKey = 'eyJrZXlJZCI6IjRkZmM0MDJiLTZmOGUtNGZhNy1iYzM3LWZhMzFlMTVhNmRkYyIsInByaXZhdGVLZXkiOiItLS0tLUJFR0lOIFBSSVZBVEUgS0VZLS0tLS1cbk1JSUV2QUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktZd2dnU2lBZ0VBQW9JQkFRQ3IzcUkzSnBQN2tGRHZcbjFnVUl0c2x3V21EV01UbGk3VlRJZEJWNEN2VFpxZHNPNnNWMDFFc1RoOEJBM25QUjJZaUF2UEFuTTlnUTh4bUNcbjZpMzc4eHJUd2RrcS8rU0o5c3ByVkZVeVZxQ0JqKzRSUG1yNllpREgrMFV5Nm10eEI2MzcyaE13Vis0cDAxbDdcbjZSZnRDdWIrVmFOTUxGVTFObWhZRjZFUDNLN1IveklxbHNTemxTWUcwRWprd1ltZjY5RVN0Y2VUcXVkS2tnbGhcbkR3TG5QOGwvWFlCbnV4RDVDQkpXU0tBM0pKaEs0MDhyb2w0U2JpQjZPWFI1Z05ZbCtHakp6ZUt6dkVSUkN6anNcbjE0aUk0NXR0Ty9TaVF1NVJKbThrTDY1ZGc3dUp2dEYrejhZemZpaEtsaGdNSHkwTFVRTXNYZ3ZzVmN1QjhMYUdcbm11bDlZZU9KQWdNQkFBRUNnZ0VBSHc3ZFc0QUNMK3lWWTdIV09Rdm0vUUdvREN6YkJQQ0VhTERwakViV2xscm1cbmRoeWcwQXJwQWo5KzAzb2ZqZVYwa0djVU10RmdremVLL2FoWjVQUzZmVmZEYWN6U1BNZzNMZ3dRVlVkb08rR0pcbmtONHBzTk40dndxK0o0UkxKQ0xTSXZmMmpiN21EL0xjY2RMZWV2eUVYNk9VSGRqSkVST2k2WUJqbjUwdWprMzJcbmx3MEtHOTl2WnRWWmRPY3l6QTYrZG5xMUxWcEtXTEtNeG1uSWVwd2psN3BhbDIzNUI1MnE2dUZQUGZsN1FHS2NcbkN5bEZ3Q1JDZmM2VGV3anRpcVc3SzZkd1ZKSFkwcWFGcDdzZytkbXlGRlRXWmZuaTFkS1UyaTdhdng4a09BTjNcbk00N2szdDZySkJWQ2NHbnNrQzJ5eVpHVHhNNlVKY3lxZVd3Z3QwUUJBUUtCZ1FEY3VLVWFRRnR0Y1FCb05aQ1RcbkowUDVVQjhweGNtVFVwOTZGdTVjd0VPc0xtU2tLTkR3OXN0bWc2Q2N3R3Y1cDMwMjM2S0w4VGNkZmlMZFJwdlZcbjNsSjBCbzE4dWFoUnNlM2ZBM05yNGxWZ0RBakZLQkxuenZHK2FKUWJjdFJCdE5HZk9aOGEyN3grQ254L2JNaEpcbkF0VlgwRFBEcER2K2YvaGNLeFFSeU9VUDRRS0JnUURIVnhyUnl4cmViZVJ6TVcrYzQzTUYrSGxFNVUxT29TNjZcbmxtWWFvaGpXaHJ1TXo2VGc2NHZ0RWZjUHBpeXhpSjZSa1lLT29wdmhhZFQ2dWxxbE10ais2Zmw1NHBsVkhaVlFcbmNEY3pMTklRbjFPWUNlSzFZN2ZTS0EvdlpDQ2JnWi92aE9RTmE1d3VzcWdxUEY5emhOWEpyb1BKbWt5cUlmZFBcbk9TcG9zcGRvcVFLQmdEMzRHV0tsYndYckZCSXQ5OGxZM056Q2dmMVlhcC9TTXJRMGUvZk9nekYwVlExQjZHZStcbjRweUZtREpxVStaai8rUElKZnJrWG5VSlZRQ0xNblY1VmV6OWFmdjZwQ2RMclYxUHVyZ3ZjNGpqMkJLQ2pjeEhcbmJkZm54SzF3TCtmQ3ZKZlh0YlAwdlpjbG1vNnNIQTlqbkVKclVoMDdueHgxRVdYUE1uTkwxQVFCQW9HQVRWY0RcblJkQktiWEF2aVczdHd1NFFTNG02NnpzWUFtRFE4MzIwd2JLUWRuTXh3eEV4QkQ3L1BBeVRVWlFFbFNEUGZPVDZcbnhZSmJmbHFFVW44SStqMC9LYS8zcGcxL3RpRlROREZGaVdwaldpV20xajlIb1Y2K0RDQ1ZCaWxQNldXaWV0aVJcbmJvK0l1aW1BeTFvL0lsK3dYcDZCN1M4YmZZck9IQU91NjQ0VzVua0NnWUJ2WkdJTGRhQWkzYXRoenR0TVJaeFdcbm1oaXYyeXRRU2NIWTlMcVJCMGRYazZOUTkrMEU1MjdraG14Y3dCT1E2UDhrM0RnLyttQTlHWDFyOG15Z3JRTlZcbk9Gb2N2YW90Q2ZyZmdmZTZtRlI1Wm1PM3VRajZIWG42ZGJ3d2srcFEyVTYzMTA4eFRJb0hLd2prQ2xpdWFkTW5cbnFhN2dPUHFZMVA0RHZ2Y2NMSkswQ0E9PVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiYXVkaWVuY2UiOiIxMWZmZTk4ZC0xN2ZiLTRlYzktYTVhMi0xYmEyOGZmMmZlMmMuYWNjb3VudHMuYXV0aHJlc3MuaW8iLCJjbGllbnRJZCI6ImU5YWEwMGE4LWE4MzUtNGY4Zi04MzI1LWNiYmEyODUyOGUyMCJ9';
+ const accessKey = 'SC|uEsXtFNjUbf1LEgAGeUhC3.uDeF.a43706ca-9647-40e4-aeae-7dcaa54bbab3.MC4CAQAwBQYDK2VwBCIEIE99LFw2c3DCiYwrY/Qkg1nIDiagoHtdCwb88RxarVYA';
const tokenProvider = new ServiceClientTokenProvider(accessKey);
const initialToken = await tokenProvider();
- await new Promise(resolve => setTimeout(resolve, 1500));
const secondToken = await tokenProvider();
expect(secondToken).to.eql(initialToken);
});
@@ -28,5 +27,26 @@ describe('serviceClientTokenProvider.js', () => {
expect(secondToken).to.eql(initialToken);
});
});
+
+ describe('getToken() function', () => {
+ it('Validate cache tokens work', async () => {
+ const accessKey = 'SC|uEsXtFNjUbf1LEgAGeUhC3.uDeF.a43706ca-9647-40e4-aeae-7dcaa54bbab3.MC4CAQAwBQYDK2VwBCIEIE99LFw2c3DCiYwrY/Qkg1nIDiagoHtdCwb88RxarVYA';
+ const tokenProvider = new ServiceClientTokenProvider(accessKey);
+ const initialToken = await tokenProvider.getToken();
+ const secondToken = await tokenProvider.getToken();
+ expect(secondToken).to.eql(initialToken);
+ });
+ });
+
+ describe('createAuthorizationCode()', () => {
+ it('Validate cache tokens work', async () => {
+ const accessKey = 'SC|uEsXtFNjUbf1LEgAGeUhC3.uDeF.a43706ca-9647-40e4-aeae-7dcaa54bbab3.MC4CAQAwBQYDK2VwBCIEIE99LFw2c3DCiYwrY/Qkg1nIDiagoHtdCwb88RxarVYA';
+ const tokenProvider = new ServiceClientTokenProvider(accessKey);
+ const initialToken = await tokenProvider.createAuthorizationCode('user1');
+ await new Promise(resolve => setTimeout(resolve, 1500));
+ const secondToken = await tokenProvider.createAuthorizationCode('user1');
+ expect(secondToken).to.not.eql(initialToken);
+ });
+ });
});