mirror of
https://github.com/logto-io/logto.git
synced 2024-12-23 20:33:16 -05:00
Merge pull request #520 from logto-io/gao-remove-resource-scopes
refactor(core): remove resource scopes
This commit is contained in:
commit
91c79bb44c
9 changed files with 8 additions and 225 deletions
|
@ -8,7 +8,6 @@ import { Provider, errors } from 'oidc-provider';
|
|||
|
||||
import postgresAdapter from '@/oidc/adapter';
|
||||
import { findResourceByIndicator } from '@/queries/resource';
|
||||
import { findAllScopesWithResourceId } from '@/queries/scope';
|
||||
import { findUserById } from '@/queries/user';
|
||||
import { routes } from '@/routes/consts';
|
||||
|
||||
|
@ -47,20 +46,18 @@ export default async function initOidc(app: Koa): Promise<Provider> {
|
|||
// Disable the auto use of authorization_code granted resource feature
|
||||
// https://github.com/panva/node-oidc-provider/blob/main/docs/README.md#usegrantedresource
|
||||
useGrantedResource: () => false,
|
||||
getResourceServerInfo: async (ctx, indicator) => {
|
||||
getResourceServerInfo: async (_, indicator) => {
|
||||
const resourceServer = await findResourceByIndicator(indicator);
|
||||
|
||||
if (!resourceServer) {
|
||||
throw new errors.InvalidTarget();
|
||||
}
|
||||
|
||||
const { id, accessTokenTtl: accessTokenTTL } = resourceServer;
|
||||
const scopes = await findAllScopesWithResourceId(id);
|
||||
const scope = scopes.map(({ name }) => name).join(' ');
|
||||
const { accessTokenTtl: accessTokenTTL } = resourceServer;
|
||||
|
||||
return {
|
||||
accessTokenFormat: 'jwt',
|
||||
scope,
|
||||
scope: '',
|
||||
accessTokenTTL,
|
||||
};
|
||||
},
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
import { ResourceScopes } from '@logto/schemas';
|
||||
import { createMockPool, createMockQueryResult, sql } from 'slonik';
|
||||
|
||||
import { convertToIdentifiers, convertToPrimitiveOrSql } from '@/database/utils';
|
||||
import { DeletionError } from '@/errors/SlonikError';
|
||||
import { mockScope } from '@/utils/mock';
|
||||
import { expectSqlAssert, QueryType } from '@/utils/test-utils';
|
||||
|
||||
import { findAllScopesWithResourceId, insertScope, deleteScopeById } from './scope';
|
||||
|
||||
const mockQuery: jest.MockedFunction<QueryType> = jest.fn();
|
||||
|
||||
jest.mock('@/database/pool', () =>
|
||||
createMockPool({
|
||||
query: async (sql, values) => {
|
||||
return mockQuery(sql, values);
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
describe('scope query', () => {
|
||||
const { table, fields } = convertToIdentifiers(ResourceScopes);
|
||||
|
||||
it('findAllScopesWithResourceId', async () => {
|
||||
const expectSql = sql`
|
||||
select ${sql.join(Object.values(fields), sql`, `)}
|
||||
from ${table}
|
||||
where ${fields.resourceId}=$1
|
||||
`;
|
||||
|
||||
mockQuery.mockImplementationOnce(async (sql, values) => {
|
||||
expectSqlAssert(sql, expectSql.sql);
|
||||
expect(values).toEqual([mockScope.resourceId]);
|
||||
|
||||
return createMockQueryResult([mockScope]);
|
||||
});
|
||||
|
||||
await expect(findAllScopesWithResourceId(mockScope.resourceId)).resolves.toEqual([mockScope]);
|
||||
});
|
||||
|
||||
it('insertScope', async () => {
|
||||
const expectSql = sql`
|
||||
insert into ${table} (${sql.join(Object.values(fields), sql`, `)})
|
||||
values (${sql.join(
|
||||
Object.values(fields).map((_, index) => `$${index + 1}`),
|
||||
sql`, `
|
||||
)})
|
||||
returning *
|
||||
`;
|
||||
|
||||
mockQuery.mockImplementationOnce(async (sql, values) => {
|
||||
expectSqlAssert(sql, expectSql.sql);
|
||||
|
||||
expect(values).toEqual(
|
||||
ResourceScopes.fieldKeys.map((k) => convertToPrimitiveOrSql(k, mockScope[k]))
|
||||
);
|
||||
|
||||
return createMockQueryResult([mockScope]);
|
||||
});
|
||||
|
||||
await expect(insertScope(mockScope)).resolves.toEqual(mockScope);
|
||||
});
|
||||
|
||||
it('deleteScopeById', async () => {
|
||||
const id = 'foo';
|
||||
const expectSql = sql`
|
||||
delete from ${table}
|
||||
where ${fields.id}=$1
|
||||
`;
|
||||
|
||||
mockQuery.mockImplementationOnce(async (sql, values) => {
|
||||
expectSqlAssert(sql, expectSql.sql);
|
||||
expect(values).toEqual([id]);
|
||||
|
||||
return createMockQueryResult([mockScope]);
|
||||
});
|
||||
|
||||
await deleteScopeById(id);
|
||||
});
|
||||
|
||||
it('deleteScopeById throw error if return row count is 0', async () => {
|
||||
const id = 'foo';
|
||||
const expectSql = sql`
|
||||
delete from ${table}
|
||||
where ${fields.id}=$1
|
||||
`;
|
||||
|
||||
mockQuery.mockImplementationOnce(async (sql, values) => {
|
||||
expectSqlAssert(sql, expectSql.sql);
|
||||
expect(values).toEqual([id]);
|
||||
|
||||
return createMockQueryResult([]);
|
||||
});
|
||||
|
||||
await expect(deleteScopeById(id)).rejects.toMatchError(
|
||||
new DeletionError(ResourceScopes.table, id)
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,35 +0,0 @@
|
|||
import { ResourceScope, CreateResourceScope, ResourceScopes } from '@logto/schemas';
|
||||
import { sql } from 'slonik';
|
||||
|
||||
import { buildInsertInto } from '@/database/insert-into';
|
||||
import pool from '@/database/pool';
|
||||
import { convertToIdentifiers } from '@/database/utils';
|
||||
import { DeletionError } from '@/errors/SlonikError';
|
||||
|
||||
const { table, fields } = convertToIdentifiers(ResourceScopes);
|
||||
|
||||
export const findAllScopesWithResourceId = async (resourceId: string) =>
|
||||
pool.any<ResourceScope>(sql`
|
||||
select ${sql.join(Object.values(fields), sql`, `)}
|
||||
from ${table}
|
||||
where ${fields.resourceId}=${resourceId}
|
||||
`);
|
||||
|
||||
export const insertScope = buildInsertInto<CreateResourceScope, ResourceScope>(
|
||||
pool,
|
||||
ResourceScopes,
|
||||
{
|
||||
returning: true,
|
||||
}
|
||||
);
|
||||
|
||||
export const deleteScopeById = async (id: string) => {
|
||||
const { rowCount } = await pool.query(sql`
|
||||
delete from ${table}
|
||||
where ${fields.id}=${id}
|
||||
`);
|
||||
|
||||
if (rowCount < 1) {
|
||||
throw new DeletionError(ResourceScopes.table, id);
|
||||
}
|
||||
};
|
|
@ -1,6 +1,6 @@
|
|||
import { Resource, CreateResource } from '@logto/schemas';
|
||||
|
||||
import { mockResource, mockScope } from '@/utils/mock';
|
||||
import { mockResource } from '@/utils/mock';
|
||||
import { createRequester } from '@/utils/test-utils';
|
||||
|
||||
import resourceRoutes from './resource';
|
||||
|
@ -24,10 +24,6 @@ jest.mock('@/queries/resource', () => ({
|
|||
deleteResourceById: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@/queries/scope', () => ({
|
||||
findAllScopesWithResourceId: jest.fn(async () => [mockScope]),
|
||||
}));
|
||||
|
||||
jest.mock('@/utils/id', () => ({
|
||||
// eslint-disable-next-line unicorn/consistent-function-scoping
|
||||
buildIdGenerator: jest.fn(() => () => 'randomId'),
|
||||
|
@ -83,7 +79,6 @@ describe('resource routes', () => {
|
|||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual({
|
||||
...mockResource,
|
||||
scopes: [mockScope],
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -102,7 +97,6 @@ describe('resource routes', () => {
|
|||
name,
|
||||
indicator,
|
||||
accessTokenTtl,
|
||||
scopes: [mockScope],
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ import {
|
|||
updateResourceById,
|
||||
deleteResourceById,
|
||||
} from '@/queries/resource';
|
||||
import { findAllScopesWithResourceId } from '@/queries/scope';
|
||||
import { buildIdGenerator } from '@/utils/id';
|
||||
|
||||
import { AuthedRouter } from './types';
|
||||
|
@ -60,12 +59,8 @@ export default function resourceRoutes<T extends AuthedRouter>(router: T) {
|
|||
params: { id },
|
||||
} = ctx.guard;
|
||||
|
||||
const [resource, scopes] = await Promise.all([
|
||||
findResourceById(id),
|
||||
findAllScopesWithResourceId(id),
|
||||
]);
|
||||
|
||||
ctx.body = { ...resource, scopes };
|
||||
const resource = await findResourceById(id);
|
||||
ctx.body = resource;
|
||||
|
||||
return next();
|
||||
}
|
||||
|
@ -83,12 +78,8 @@ export default function resourceRoutes<T extends AuthedRouter>(router: T) {
|
|||
body,
|
||||
} = ctx.guard;
|
||||
|
||||
const [scopes, resource] = await Promise.all([
|
||||
findAllScopesWithResourceId(id),
|
||||
updateResourceById(id, body),
|
||||
]);
|
||||
|
||||
ctx.body = { ...resource, scopes };
|
||||
const resource = await updateResourceById(id, body);
|
||||
ctx.body = resource;
|
||||
|
||||
return next();
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import {
|
|||
Application,
|
||||
ApplicationType,
|
||||
Resource,
|
||||
ResourceScope,
|
||||
Role,
|
||||
Setting,
|
||||
SignInExperience,
|
||||
|
@ -141,13 +140,6 @@ export const mockResource: Resource = {
|
|||
accessTokenTtl: 3600,
|
||||
};
|
||||
|
||||
export const mockScope: ResourceScope = {
|
||||
id: 'foo',
|
||||
name: 'read:user',
|
||||
description: 'read:user',
|
||||
resourceId: 'logto_api',
|
||||
};
|
||||
|
||||
export const mockRole: Role = {
|
||||
name: 'admin',
|
||||
description: 'admin',
|
||||
|
|
|
@ -5,7 +5,6 @@ export * from './application';
|
|||
export * from './connector';
|
||||
export * from './oidc-model-instance';
|
||||
export * from './passcode';
|
||||
export * from './resource-scope';
|
||||
export * from './resource';
|
||||
export * from './role';
|
||||
export * from './setting';
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
import { GeneratedSchema, Guard } from '../foundations';
|
||||
|
||||
export type CreateResourceScope = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
resourceId: string;
|
||||
};
|
||||
|
||||
export type ResourceScope = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
resourceId: string;
|
||||
};
|
||||
|
||||
const createGuard: Guard<CreateResourceScope> = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
resourceId: z.string(),
|
||||
});
|
||||
|
||||
export const ResourceScopes: GeneratedSchema<CreateResourceScope> = Object.freeze({
|
||||
table: 'resource_scopes',
|
||||
tableSingular: 'resource_scope',
|
||||
fields: {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
description: 'description',
|
||||
resourceId: 'resource_id',
|
||||
},
|
||||
fieldKeys: ['id', 'name', 'description', 'resourceId'],
|
||||
createGuard,
|
||||
});
|
|
@ -1,17 +0,0 @@
|
|||
create table resource_scopes (
|
||||
id varchar(24) not null,
|
||||
name varchar(64) not null,
|
||||
description text not null,
|
||||
resource_id varchar(24) not null,
|
||||
primary key (id),
|
||||
constraint fk__resource_scopes__resource_id
|
||||
foreign key (resource_id)
|
||||
references resources(id)
|
||||
on delete cascade
|
||||
);
|
||||
|
||||
create unique index resource_scopes__resource_id_name
|
||||
on resource_scopes (
|
||||
resource_id,
|
||||
name
|
||||
);
|
Loading…
Reference in a new issue