Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/openapi/types/resource-role-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ export interface ResourceRoleCreate {
* @memberof ResourceRoleCreate
*/
attributes?: object;
/**
* list of role keys that define what roles this role extends. In other words: this role will automatically inherit all the permissions of the given roles in this list.
* @type {Array<string>}
* @memberof ResourceRoleCreate
*/
extends?: Array<string>;
/**
*
* @type {GrantedTo1}
Expand Down
6 changes: 6 additions & 0 deletions src/openapi/types/resource-role-read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ export interface ResourceRoleRead {
* @memberof ResourceRoleRead
*/
attributes?: object;
/**
* list of role keys that define what roles this role extends. In other words: this role will automatically inherit all the permissions of the given roles in this list.
* @type {Array<string>}
* @memberof ResourceRoleRead
*/
extends?: Array<string>;
/**
*
* @type {GrantedTo2}
Expand Down
6 changes: 6 additions & 0 deletions src/openapi/types/resource-role-update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ export interface ResourceRoleUpdate {
* @memberof ResourceRoleUpdate
*/
attributes?: object;
/**
* list of role keys that define what roles this role extends. In other words: this role will automatically inherit all the permissions of the given roles in this list.
* @type {Array<string>}
* @memberof ResourceRoleUpdate
*/
extends?: Array<string>;
/**
*
* @type {GrantedTo1}
Expand Down
6 changes: 6 additions & 0 deletions src/openapi/types/role-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ export interface RoleCreate {
* @memberof RoleCreate
*/
attributes?: object;
/**
* list of role keys that define what roles this role extends. In other words: this role will automatically inherit all the permissions of the given roles in this list.
* @type {Array<string>}
* @memberof RoleCreate
*/
extends?: Array<string>;
Comment thread
zeevmoney marked this conversation as resolved.
/**
*
* @type {GrantedTo1}
Expand Down
6 changes: 6 additions & 0 deletions src/openapi/types/role-read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ export interface RoleRead {
* @memberof RoleRead
*/
attributes?: object;
/**
* list of role keys that define what roles this role extends. In other words: this role will automatically inherit all the permissions of the given roles in this list.
* @type {Array<string>}
* @memberof RoleRead
*/
extends?: Array<string>;
Comment thread
zeevmoney marked this conversation as resolved.
Comment thread
zeevmoney marked this conversation as resolved.
/**
*
* @type {GrantedTo}
Expand Down
6 changes: 6 additions & 0 deletions src/openapi/types/role-update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ export interface RoleUpdate {
* @memberof RoleUpdate
*/
attributes?: object;
/**
* list of role keys that define what roles this role extends. In other words: this role will automatically inherit all the permissions of the given roles in this list.
* @type {Array<string>}
* @memberof RoleUpdate
*/
extends?: Array<string>;
Comment thread
zeevmoney marked this conversation as resolved.
/**
*
* @type {GrantedTo1}
Expand Down
142 changes: 142 additions & 0 deletions src/tests/unit/role-extends.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import test from 'ava';

import { Permit } from '../../index';

type RoleCreate = Parameters<Permit['api']['roles']['create']>[0];
type RoleRead = Awaited<ReturnType<Permit['api']['roles']['get']>>;
type RoleUpdate = Parameters<Permit['api']['roles']['update']>[1];
type ResourceRoleCreate = Parameters<Permit['api']['resourceRoles']['create']>[1];
type ResourceRoleRead = Awaited<ReturnType<Permit['api']['resourceRoles']['get']>>;
type ResourceRoleUpdate = Parameters<Permit['api']['resourceRoles']['update']>[2];

type IsExact<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2
? true
: false;
type Assert<T extends true> = T;
type Inheritance = { extends?: Array<string> };

// yarn test:unit runs build:types before AVA. Pick preserves the optional modifier, and exact
// equality rejects any, null, non-array values, and a required property (even with undefined).
export type RoleInheritanceContracts = [
Assert<IsExact<Pick<RoleCreate, 'extends'>, Inheritance>>,
Assert<IsExact<Pick<RoleRead, 'extends'>, Inheritance>>,
Assert<IsExact<Pick<RoleUpdate, 'extends'>, Inheritance>>,
Assert<IsExact<Pick<ResourceRoleCreate, 'extends'>, Inheritance>>,
Assert<IsExact<Pick<ResourceRoleRead, 'extends'>, Inheritance>>,
Assert<IsExact<Pick<ResourceRoleUpdate, 'extends'>, Inheritance>>,
];

function createClient(inheritance: Inheritance) {
const permit = new Permit({
token: 'test',
apiUrl: 'https://api.permit.io',
log: { level: 'silent' },
});
const requests: Array<{ method: string | undefined; url: string | undefined; body: unknown }> =
[];
const role: ResourceRoleRead = {
key: 'editor',
name: 'Editor',
id: 'role-id',
organization_id: 'org',
project_id: 'proj',
environment_id: 'env',
resource_id: 'resource-id',
resource: 'doc',
created_at: '2026-01-01T00:00:00Z',
updated_at: '2026-01-01T00:00:00Z',
...inheritance,
};
permit.config.axiosInstance.defaults.adapter = async (config) => {
const isScopeRequest = config.url === 'https://api.permit.io/v2/api-key/scope';
if (!isScopeRequest) {
requests.push({
method: config.method,
url: config.url,
body: config.data === undefined ? undefined : JSON.parse(config.data),
});
}
return {
status: 200,
statusText: 'OK',
headers: {},
config,
data: isScopeRequest
? { organization_id: 'org', project_id: 'proj', environment_id: 'env' }
: { ...role },
};
};
return { permit, requests };
}

const cases: Array<{ name: string; inheritance: Inheritance }> = [
{ name: 'multiple inherited roles', inheritance: { extends: ['viewer', 'auditor'] } },
{ name: 'empty inheritance', inheritance: { extends: [] } },
{ name: 'omitted inheritance', inheritance: {} },
];

for (const { name, inheritance } of cases) {
test(`roles create/update/get preserve ${name}`, async (t) => {
const { permit, requests } = createClient(inheritance);
const created = await permit.api.roles.create({
key: 'editor',
name: 'Editor',
...inheritance,
});
const updated = await permit.api.roles.update('editor', { ...inheritance });
const fetched = await permit.api.roles.get('editor');

t.deepEqual(created.extends, inheritance.extends);
t.deepEqual(updated.extends, inheritance.extends);
t.deepEqual(fetched.extends, inheritance.extends);
t.deepEqual(requests, [
{
method: 'post',
url: 'https://api.permit.io/v2/schema/proj/env/roles',
body: { key: 'editor', name: 'Editor', ...inheritance },
},
{
method: 'patch',
url: 'https://api.permit.io/v2/schema/proj/env/roles/editor',
body: { ...inheritance },
},
{
method: 'get',
url: 'https://api.permit.io/v2/schema/proj/env/roles/editor',
body: undefined,
},
]);
});

test(`resource roles create/update/get preserve ${name}`, async (t) => {
const { permit, requests } = createClient(inheritance);
const created = await permit.api.resourceRoles.create('doc', {
key: 'editor',
name: 'Editor',
...inheritance,
});
const updated = await permit.api.resourceRoles.update('doc', 'editor', { ...inheritance });
const fetched = await permit.api.resourceRoles.get('doc', 'editor');

t.deepEqual(created.extends, inheritance.extends);
t.deepEqual(updated.extends, inheritance.extends);
t.deepEqual(fetched.extends, inheritance.extends);
t.deepEqual(requests, [
{
method: 'post',
url: 'https://api.permit.io/v2/schema/proj/env/resources/doc/roles',
body: { key: 'editor', name: 'Editor', ...inheritance },
},
{
method: 'patch',
url: 'https://api.permit.io/v2/schema/proj/env/resources/doc/roles/editor',
body: { ...inheritance },
},
{
method: 'get',
url: 'https://api.permit.io/v2/schema/proj/env/resources/doc/roles/editor',
body: undefined,
},
]);
});
}
Loading