mirror of
https://github.com/logto-io/logto.git
synced 2025-04-07 23:01:25 -05:00
feat(core): scope name within the same resource should be unique (#2861)
This commit is contained in:
parent
550cbe3436
commit
34aab882c3
13 changed files with 90 additions and 1 deletions
|
@ -44,6 +44,18 @@ export const createScopeQueries = (pool: CommonQueryMethods) => {
|
|||
${buildResourceConditions(search)}
|
||||
`);
|
||||
|
||||
const findScopeByNameAndResourceId = async (
|
||||
name: string,
|
||||
resourceId: string,
|
||||
excludeScopeId?: string
|
||||
) =>
|
||||
pool.maybeOne<Scope>(sql`
|
||||
select ${sql.join(Object.values(fields), sql`, `)}
|
||||
from ${table}
|
||||
where ${fields.resourceId}=${resourceId} and ${fields.name}=${name}
|
||||
${conditionalSql(excludeScopeId, (id) => sql`and ${fields.id}<>${id}`)}
|
||||
`);
|
||||
|
||||
const findScopesByResourceId = async (resourceId: string) =>
|
||||
pool.any<Scope>(sql`
|
||||
select ${sql.join(Object.values(fields), sql`, `)}
|
||||
|
@ -94,6 +106,7 @@ export const createScopeQueries = (pool: CommonQueryMethods) => {
|
|||
return {
|
||||
findScopes,
|
||||
countScopes,
|
||||
findScopeByNameAndResourceId,
|
||||
findScopesByResourceId,
|
||||
findScopesByResourceIds,
|
||||
findScopesByIds,
|
||||
|
@ -109,6 +122,7 @@ export const createScopeQueries = (pool: CommonQueryMethods) => {
|
|||
export const {
|
||||
findScopes,
|
||||
countScopes,
|
||||
findScopeByNameAndResourceId,
|
||||
findScopesByResourceId,
|
||||
findScopesByResourceIds,
|
||||
findScopesByIds,
|
||||
|
|
|
@ -38,6 +38,7 @@ const { insertScope, updateScopeById } = await mockEsmWithActual('#src/queries/s
|
|||
insertScope: jest.fn(async () => mockScope),
|
||||
updateScopeById: jest.fn(async () => mockScope),
|
||||
deleteScopeById: jest.fn(),
|
||||
findScopeByNameAndResourceId: jest.fn(),
|
||||
}));
|
||||
|
||||
mockEsm('@logto/core-kit', () => ({
|
||||
|
|
|
@ -19,10 +19,12 @@ import {
|
|||
import {
|
||||
countScopes,
|
||||
deleteScopeById,
|
||||
findScopeByNameAndResourceId,
|
||||
findScopes,
|
||||
insertScope,
|
||||
updateScopeById,
|
||||
} from '#src/queries/scope.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
import { parseSearchParamsForSearch } from '#src/utils/search.js';
|
||||
|
||||
import type { AuthedRouter, RouterInitArgs } from './types.js';
|
||||
|
@ -185,6 +187,15 @@ export default function resourceRoutes<T extends AuthedRouter>(...[router]: Rout
|
|||
body,
|
||||
} = ctx.guard;
|
||||
|
||||
assertThat(
|
||||
!(await findScopeByNameAndResourceId(body.name, resourceId)),
|
||||
new RequestError({
|
||||
code: 'scope.name_exists',
|
||||
name: body.name,
|
||||
status: 422,
|
||||
})
|
||||
);
|
||||
|
||||
ctx.body = await insertScope({
|
||||
...body,
|
||||
id: scopeId(),
|
||||
|
@ -203,10 +214,21 @@ export default function resourceRoutes<T extends AuthedRouter>(...[router]: Rout
|
|||
}),
|
||||
async (ctx, next) => {
|
||||
const {
|
||||
params: { scopeId },
|
||||
params: { scopeId, resourceId },
|
||||
body,
|
||||
} = ctx.guard;
|
||||
|
||||
if (body.name) {
|
||||
assertThat(
|
||||
!(await findScopeByNameAndResourceId(body.name, resourceId, scopeId)),
|
||||
new RequestError({
|
||||
code: 'scope.name_exists',
|
||||
name: body.name,
|
||||
status: 422,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
ctx.body = await updateScopeById(scopeId, body);
|
||||
|
||||
return next();
|
||||
|
|
|
@ -181,6 +181,9 @@ const errors = {
|
|||
scope_exists: 'The scope id {{scopeId}} has already been added to this role', // UNTRANSLATED
|
||||
user_exists: 'The user id {{userId}} is already been added to this role', // UNTRANSLATED
|
||||
},
|
||||
scope: {
|
||||
name_exists: 'The scope name {{name}} is already in use', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
export default errors;
|
||||
|
|
|
@ -180,6 +180,9 @@ const errors = {
|
|||
scope_exists: 'The scope id {{scopeId}} has already been added to this role',
|
||||
user_exists: 'The user id {{userId}} is already been added to this role',
|
||||
},
|
||||
scope: {
|
||||
name_exists: 'The scope name {{name}} is already in use',
|
||||
},
|
||||
};
|
||||
|
||||
export default errors;
|
||||
|
|
|
@ -187,6 +187,9 @@ const errors = {
|
|||
scope_exists: 'The scope id {{scopeId}} has already been added to this role', // UNTRANSLATED
|
||||
user_exists: 'The user id {{userId}} is already been added to this role', // UNTRANSLATED
|
||||
},
|
||||
scope: {
|
||||
name_exists: 'The scope name {{name}} is already in use', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
export default errors;
|
||||
|
|
|
@ -174,6 +174,9 @@ const errors = {
|
|||
scope_exists: 'The scope id {{scopeId}} has already been added to this role', // UNTRANSLATED
|
||||
user_exists: 'The user id {{userId}} is already been added to this role', // UNTRANSLATED
|
||||
},
|
||||
scope: {
|
||||
name_exists: 'The scope name {{name}} is already in use', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
export default errors;
|
||||
|
|
|
@ -188,6 +188,9 @@ const errors = {
|
|||
scope_exists: 'The scope id {{scopeId}} has already been added to this role', // UNTRANSLATED
|
||||
user_exists: 'The user id {{userId}} is already been added to this role', // UNTRANSLATED
|
||||
},
|
||||
scope: {
|
||||
name_exists: 'The scope name {{name}} is already in use', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
export default errors;
|
||||
|
|
|
@ -182,6 +182,9 @@ const errors = {
|
|||
scope_exists: 'The scope id {{scopeId}} has already been added to this role', // UNTRANSLATED
|
||||
user_exists: 'The user id {{userId}} is already been added to this role', // UNTRANSLATED
|
||||
},
|
||||
scope: {
|
||||
name_exists: 'The scope name {{name}} is already in use', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
export default errors;
|
||||
|
|
|
@ -182,6 +182,9 @@ const errors = {
|
|||
scope_exists: 'The scope id {{scopeId}} has already been added to this role', // UNTRANSLATED
|
||||
user_exists: 'The user id {{userId}} is already been added to this role', // UNTRANSLATED
|
||||
},
|
||||
scope: {
|
||||
name_exists: 'The scope name {{name}} is already in use', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
export default errors;
|
||||
|
|
|
@ -163,6 +163,9 @@ const errors = {
|
|||
scope_exists: 'The scope id {{scopeId}} has already been added to this role', // UNTRANSLATED
|
||||
user_exists: 'The user id {{userId}} is already been added to this role', // UNTRANSLATED
|
||||
},
|
||||
scope: {
|
||||
name_exists: 'The scope name {{name}} is already in use', // UNTRANSLATED
|
||||
},
|
||||
};
|
||||
|
||||
export default errors;
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import { sql } from 'slonik';
|
||||
|
||||
import type { AlterationScript } from '../lib/types/alteration.js';
|
||||
|
||||
const alteration: AlterationScript = {
|
||||
up: async (pool) => {
|
||||
await pool.query(sql`
|
||||
create index scopes__resource_id_name
|
||||
on scopes (
|
||||
resource_id,
|
||||
name
|
||||
);
|
||||
`);
|
||||
},
|
||||
down: async (pool) => {
|
||||
await pool.query(sql`
|
||||
drop index scopes__resource_id_name
|
||||
`);
|
||||
},
|
||||
};
|
||||
|
||||
export default alteration;
|
|
@ -6,3 +6,9 @@ create table scopes (
|
|||
created_at timestamptz not null default(now()),
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
create index scopes__resource_id_name
|
||||
on scopes (
|
||||
resource_id,
|
||||
name
|
||||
);
|
||||
|
|
Loading…
Add table
Reference in a new issue