Skip to content

Commit

Permalink
Add getAuthorizationCode for credentialed AS.
Browse files Browse the repository at this point in the history
  • Loading branch information
wparad committed Nov 4, 2021
1 parent 8c7cb68 commit d43336a
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 11 deletions.
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"allowModules": ["jose"]
}],

"@typescript-eslint/no-var-requires": "off"
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/ban-ts-comment": "off"
}
}
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
22 changes: 20 additions & 2 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1243,20 +1243,22 @@ export interface UserPermissionsApi {
/**
* <i class=\"far fa-money-bill-alt text-primary\"></i> <span class=\"text-primary\">Billable</span> 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, &#x27;*&#x27; and scoped permissions can also be checked here.
* @throws {ArgumentRequiredError}
* @throws {UnauthorizedError}
*/
authorizeUser(userId: string, resourceUri: string, permission: string): Promise<Response<void>>;
// @ts-ignore
authorizeUser(userId?: string, resourceUri: string, permission: string): Promise<Response<void>>;
/**
* <i class=\"far fa-money-bill-alt text-primary\"></i> <span class=\"text-primary\">Billable</span> 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
* @param {string} [userId] The user to create an impersonation token for.
* @param {string} tokenId The relevant token identifier
* @throws {ArgumentRequiredError}
*/
// @ts-ignore
disableUserToken(userId?: string, tokenId: string): Promise<Response<void>>;
/**
* <i class=\"far fa-money-bill-alt text-primary\"></i> <span class=\"text-primary\">Billable</span> Get a summary of the permissions a user has to a particular resource.
Expand All @@ -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<Response<UserPermissions>>;
/**
* <i class="far fa-money-bill-alt text-primary"></i> <span class="text-primary">Billable</span> 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.<br><span class="badge badge-outline-secondary">READ: Authress:UserPermissions/{userId}</span>
Expand All @@ -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<Response<UserRoleCollection>>;
/**
* <i class=\"far fa-money-bill-alt text-primary\"></i> <span class=\"text-primary\">Billable</span> 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.
Expand All @@ -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<Response<UserToken>>;
}

Expand Down Expand Up @@ -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<Promise<void>>}
*/
getToken(): Promise<string>;

/**
* @summary Create an Authress auth code assertion to exchange with the Authress identity API to generate a JWT for the user
* @type {Function<Promise<void>>}
* @param {string} userId The user to generate a JWT for.
*/
createAuthorizationCode(userId: string): Promise<string>;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/httpClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}`
Expand Down
19 changes: 14 additions & 5 deletions src/serviceClientTokenProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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;
};
24 changes: 22 additions & 2 deletions tests/serviceClientTokenProvider.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand All @@ -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);
});
});
});

0 comments on commit d43336a

Please sign in to comment.