mirror of
https://github.com/logto-io/logto.git
synced 2025-01-27 21:39:16 -05:00
feat(schema): add resource and scope table (#145)
* feat(schema): add resource and scope table implement resource-servers and resource-scopes table with some basic queries * fix(schema): remove scope update querie not allowed to update scope once created * feat(schema): add resourceId foreign key to the scope table add resourceId foreign key to the scope table * fix(schema): remove trailling comma remove trailling comma * fix(schema): cr fix replace resource-servers table name using resources replace resource-servers table name using resources
This commit is contained in:
parent
838ae3fad9
commit
18a142ab65
9 changed files with 221 additions and 3 deletions
70
packages/core/src/queries/resources.ts
Normal file
70
packages/core/src/queries/resources.ts
Normal file
|
@ -0,0 +1,70 @@
|
|||
import { ResourceDBEntry, Resources } from '@logto/schemas';
|
||||
import { sql } from 'slonik';
|
||||
|
||||
import { buildInsertInto } from '@/database/insert-into';
|
||||
import pool from '@/database/pool';
|
||||
import { buildUpdateWhere } from '@/database/update-where';
|
||||
import { convertToIdentifiers, OmitAutoSetFields } from '@/database/utils';
|
||||
import RequestError from '@/errors/RequestError';
|
||||
|
||||
const { table, fields } = convertToIdentifiers(Resources);
|
||||
|
||||
export const findAllResources = async () =>
|
||||
pool.many<ResourceDBEntry>(sql`
|
||||
select ${sql.join(Object.values(fields), sql`,`)}
|
||||
from ${table}
|
||||
`);
|
||||
|
||||
export const hasResource = async (indentifier: string) =>
|
||||
pool.exists(sql`
|
||||
select ${fields.id}
|
||||
from ${table}
|
||||
where ${fields.identifier}=${indentifier}
|
||||
`);
|
||||
|
||||
export const hasResourceWithId = async (id: string) =>
|
||||
pool.exists(sql`
|
||||
select ${fields.id}
|
||||
from ${table}
|
||||
where ${fields.id}=${id}
|
||||
`);
|
||||
|
||||
export const findResourceByIdentifier = async (indentifier: string) =>
|
||||
pool.one<ResourceDBEntry>(sql`
|
||||
select ${sql.join(Object.values(fields), sql`,`)}
|
||||
from ${table}
|
||||
where ${fields.identifier}=${indentifier}
|
||||
`);
|
||||
|
||||
export const findResourceById = async (id: string) =>
|
||||
pool.one<ResourceDBEntry>(sql`
|
||||
select ${sql.join(Object.values(fields), sql`,`)}
|
||||
from ${table}
|
||||
where ${fields.id}=${id}
|
||||
`);
|
||||
|
||||
export const insertResource = buildInsertInto<ResourceDBEntry>(pool, Resources, {
|
||||
returning: true,
|
||||
});
|
||||
|
||||
const updateResource = buildUpdateWhere<ResourceDBEntry>(pool, Resources, true);
|
||||
|
||||
export const updateResourceById = async (
|
||||
id: string,
|
||||
set: Partial<OmitAutoSetFields<ResourceDBEntry>>
|
||||
) => updateResource({ set, where: { id } });
|
||||
|
||||
export const deleteResourceById = async (id: string) => {
|
||||
const { rowCount } = await pool.query(sql`
|
||||
delete from ${table}
|
||||
where id=${id}
|
||||
`);
|
||||
if (rowCount < 1) {
|
||||
throw new RequestError({
|
||||
code: 'entity.not_exists_with_id',
|
||||
name: Resources.tableSingular,
|
||||
id,
|
||||
status: 404,
|
||||
});
|
||||
}
|
||||
};
|
35
packages/core/src/queries/scopes.ts
Normal file
35
packages/core/src/queries/scopes.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { ResourceScopeDBEntry, 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 RequestError from '@/errors/RequestError';
|
||||
|
||||
const { table, fields } = convertToIdentifiers(ResourceScopes);
|
||||
|
||||
export const findAllScopesWithResourceId = async (resourceId: string) =>
|
||||
pool.many<ResourceScopeDBEntry>(sql`
|
||||
select ${sql.join(Object.values(fields), sql`,`)}
|
||||
from ${table}
|
||||
where ${fields.resourceId}=${resourceId}
|
||||
`);
|
||||
|
||||
export const insertScope = buildInsertInto<ResourceScopeDBEntry>(pool, ResourceScopes, {
|
||||
returning: true,
|
||||
});
|
||||
|
||||
export const deleteScopeById = async (id: string) => {
|
||||
const { rowCount } = await pool.query(sql`
|
||||
delete from ${table}
|
||||
where id=${id}
|
||||
`);
|
||||
if (rowCount < 1) {
|
||||
throw new RequestError({
|
||||
code: 'entity.not_exists_with_id',
|
||||
name: ResourceScopes.tableSingular,
|
||||
id,
|
||||
status: 404,
|
||||
});
|
||||
}
|
||||
};
|
|
@ -5,6 +5,12 @@ export enum ApplicationType {
|
|||
SPA = 'SPA',
|
||||
Traditional = 'Traditional',
|
||||
}
|
||||
export enum SignAlgorithmType {
|
||||
RS256 = 'RS256',
|
||||
}
|
||||
export enum AccessTokenFormatType {
|
||||
jwt = 'jwt',
|
||||
}
|
||||
export enum UserLogType {
|
||||
SignInUsernameAndPassword = 'SignInUsernameAndPassword',
|
||||
ExchangeAccessToken = 'ExchangeAccessToken',
|
||||
|
|
|
@ -3,5 +3,7 @@
|
|||
export * from './custom-types';
|
||||
export * from './application';
|
||||
export * from './oidc-model-instance';
|
||||
export * from './resource-scope';
|
||||
export * from './resource';
|
||||
export * from './user-log';
|
||||
export * from './user';
|
||||
|
|
32
packages/schemas/src/db-entries/resource-scope.ts
Normal file
32
packages/schemas/src/db-entries/resource-scope.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
import { GeneratedSchema, Guard } from '../foundations';
|
||||
|
||||
export type ResourceScopeDBEntry = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
resourceId: string;
|
||||
};
|
||||
|
||||
const guard: Guard<ResourceScopeDBEntry> = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
resourceId: z.string(),
|
||||
});
|
||||
|
||||
export const ResourceScopes: GeneratedSchema<ResourceScopeDBEntry> = Object.freeze({
|
||||
table: 'resource_scopes',
|
||||
tableSingular: 'resource_scope',
|
||||
fields: {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
description: 'description',
|
||||
resourceId: 'resource_id',
|
||||
},
|
||||
fieldKeys: ['id', 'name', 'description', 'resourceId'],
|
||||
guard,
|
||||
});
|
39
packages/schemas/src/db-entries/resource.ts
Normal file
39
packages/schemas/src/db-entries/resource.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
import { GeneratedSchema, Guard } from '../foundations';
|
||||
import { AccessTokenFormatType, SignAlgorithmType } from './custom-types';
|
||||
|
||||
export type ResourceDBEntry = {
|
||||
id: string;
|
||||
name: string;
|
||||
identifier: string;
|
||||
accessTokenTtl: number;
|
||||
accessTokenFormat: AccessTokenFormatType;
|
||||
signAlgorithm: SignAlgorithmType;
|
||||
};
|
||||
|
||||
const guard: Guard<ResourceDBEntry> = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
identifier: z.string(),
|
||||
accessTokenTtl: z.number(),
|
||||
accessTokenFormat: z.nativeEnum(AccessTokenFormatType),
|
||||
signAlgorithm: z.nativeEnum(SignAlgorithmType),
|
||||
});
|
||||
|
||||
export const Resources: GeneratedSchema<ResourceDBEntry> = Object.freeze({
|
||||
table: 'resources',
|
||||
tableSingular: 'resource',
|
||||
fields: {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
identifier: 'identifier',
|
||||
accessTokenTtl: 'access_token_ttl',
|
||||
accessTokenFormat: 'access_token_format',
|
||||
signAlgorithm: 'sign_algorithm',
|
||||
},
|
||||
fieldKeys: ['id', 'name', 'identifier', 'accessTokenTtl', 'accessTokenFormat', 'signAlgorithm'],
|
||||
guard,
|
||||
});
|
|
@ -45,9 +45,15 @@ const generate = async () => {
|
|||
.split(',')
|
||||
.map((value) => normalizeWhitespaces(value))
|
||||
.filter((value) =>
|
||||
['primary', 'foreign', 'unique', 'exclude', 'check'].every(
|
||||
(constraint) => !value.toLowerCase().startsWith(constraint + ' ')
|
||||
)
|
||||
[
|
||||
'primary',
|
||||
'foreign',
|
||||
'unique',
|
||||
'exclude',
|
||||
'check',
|
||||
'constraint',
|
||||
'references',
|
||||
].every((constraint) => !value.toLowerCase().startsWith(constraint + ' '))
|
||||
)
|
||||
.map<Field>((value) => {
|
||||
const [nameRaw, typeRaw, ...rest] = value.split(' ');
|
||||
|
|
16
packages/schemas/tables/resource_scopes.sql
Normal file
16
packages/schemas/tables/resource_scopes.sql
Normal file
|
@ -0,0 +1,16 @@
|
|||
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
|
||||
foreign key (resource_id)
|
||||
references resources(id)
|
||||
);
|
||||
|
||||
create unique index resource_scopes__resource_id_name
|
||||
on resource_scopes (
|
||||
resource_id,
|
||||
name,
|
||||
);
|
12
packages/schemas/tables/resources.sql
Normal file
12
packages/schemas/tables/resources.sql
Normal file
|
@ -0,0 +1,12 @@
|
|||
create type sign_algorithm_type as enum ('RS256');
|
||||
create type access_token_format_type as enum ('jwt');
|
||||
|
||||
create table resources (
|
||||
id varchar(24) not null,
|
||||
name text not null,
|
||||
identifier text not null unique, /* resource indicator also used as audience */
|
||||
access_token_ttl bigint not null default(86400), /* expiration value in seconds, default is 24h */
|
||||
access_token_format access_token_format_type not null default('jwt'),
|
||||
sign_algorithm sign_algorithm_type not null default('RS256'),
|
||||
primary key (id)
|
||||
);
|
Loading…
Add table
Reference in a new issue