mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
180 lines
4.7 KiB
TypeScript
180 lines
4.7 KiB
TypeScript
import { generateStandardId } from '@logto/shared/universal';
|
|
import type { CommonQueryMethods } from '@silverhand/slonik';
|
|
import { sql } from '@silverhand/slonik';
|
|
|
|
import type { AlterationScript } from '../lib/types/alteration.js';
|
|
|
|
const tables: string[] = [
|
|
'applications_roles',
|
|
'applications',
|
|
'connectors',
|
|
'custom_phrases',
|
|
'logs',
|
|
'logto_configs',
|
|
'oidc_model_instances',
|
|
'passcodes',
|
|
'resources',
|
|
'roles_scopes',
|
|
'roles',
|
|
'scopes',
|
|
'sign_in_experiences',
|
|
'users_roles',
|
|
'users',
|
|
'hooks',
|
|
];
|
|
|
|
const defaultTenantId = 'default';
|
|
|
|
const getId = (value: string) => sql.identifier([value]);
|
|
|
|
const getDatabaseName = async (pool: CommonQueryMethods) => {
|
|
const { currentDatabase } = await pool.one<{ currentDatabase: string }>(sql`
|
|
select current_database();
|
|
`);
|
|
|
|
return currentDatabase.replaceAll('-', '_');
|
|
};
|
|
|
|
const alteration: AlterationScript = {
|
|
up: async (pool) => {
|
|
const database = await getDatabaseName(pool);
|
|
|
|
// Alter hooks table for multi-tenancy (missed before)
|
|
await pool.query(sql`
|
|
alter table hooks
|
|
add column tenant_id varchar(21) not null default 'default'
|
|
references tenants (id) on update cascade on delete cascade,
|
|
alter column id type varchar(21); -- OK to downsize since we use length 21 for ID generation in core
|
|
|
|
alter table hooks
|
|
alter column tenant_id drop default;
|
|
|
|
create index hooks__id on hooks (tenant_id, id);
|
|
|
|
drop index hooks__event;
|
|
create index hooks__event on hooks (tenant_id, event);
|
|
|
|
create trigger set_tenant_id before insert on hooks
|
|
for each row execute procedure set_tenant_id();
|
|
`);
|
|
|
|
// Add db_user column to tenants table
|
|
await pool.query(sql`
|
|
alter table tenants
|
|
add column db_user varchar(128),
|
|
add constraint tenants__db_user
|
|
unique (db_user);
|
|
`);
|
|
|
|
// Create role and setup privileges
|
|
const baseRole = `logto_tenant_${database}`;
|
|
const baseRoleId = getId(baseRole);
|
|
|
|
// See `_after_all.sql` for comments
|
|
await pool.query(sql`
|
|
create role ${baseRoleId} noinherit;
|
|
|
|
grant select, insert, update, delete
|
|
on all tables
|
|
in schema public
|
|
to ${baseRoleId};
|
|
|
|
revoke all privileges
|
|
on table tenants
|
|
from ${baseRoleId};
|
|
|
|
grant select (id, db_user)
|
|
on table tenants
|
|
to ${baseRoleId};
|
|
|
|
alter table tenants enable row level security;
|
|
|
|
create policy tenants_tenant_id on tenants
|
|
to ${baseRoleId}
|
|
using (db_user = current_user);
|
|
|
|
revoke all privileges
|
|
on table systems
|
|
from ${baseRoleId};
|
|
`);
|
|
|
|
// Enable RLS
|
|
await Promise.all(
|
|
tables.map(async (tableName) =>
|
|
pool.query(sql`
|
|
alter table ${getId(tableName)} enable row level security;
|
|
|
|
create policy ${getId(`${tableName}_tenant_id`)} on ${getId(tableName)}
|
|
to ${baseRoleId}
|
|
using (tenant_id = (select id from tenants where db_user = current_user));
|
|
`)
|
|
)
|
|
);
|
|
|
|
// Create database role for default tenant
|
|
const role = `logto_tenant_${database}_${defaultTenantId}`;
|
|
const password = generateStandardId(32);
|
|
|
|
await pool.query(sql`
|
|
update tenants
|
|
set db_user=${role}, db_user_password=${password}
|
|
where id=${defaultTenantId};
|
|
`);
|
|
await pool.query(sql`
|
|
create role ${sql.identifier([role])} with inherit login
|
|
password '${sql.raw(password)}'
|
|
in role ${sql.identifier([baseRole])};
|
|
`);
|
|
},
|
|
down: async (pool) => {
|
|
const database = await getDatabaseName(pool);
|
|
const baseRoleId = getId(`logto_tenant_${database}`);
|
|
const role = `logto_tenant_${database}_${defaultTenantId}`;
|
|
|
|
// Disable RLS
|
|
await Promise.all(
|
|
tables.map(async (tableName) =>
|
|
pool.query(sql`
|
|
drop policy ${getId(`${tableName}_tenant_id`)} on ${getId(tableName)};
|
|
alter table ${getId(tableName)} disable row level security;
|
|
`)
|
|
)
|
|
);
|
|
|
|
// Drop role
|
|
await pool.query(sql`
|
|
drop role ${getId(role)};
|
|
|
|
revoke all privileges
|
|
on all tables
|
|
in schema public
|
|
from ${baseRoleId};
|
|
|
|
drop policy tenants_tenant_id on tenants;
|
|
alter table tenants disable row level security;
|
|
|
|
drop role ${baseRoleId};
|
|
`);
|
|
|
|
// Drop db_user column from tenants table
|
|
await pool.query(sql`
|
|
alter table tenants
|
|
drop column db_user;
|
|
`);
|
|
|
|
// Revert hooks table from multi-tenancy
|
|
await pool.query(sql`
|
|
drop index hooks__id;
|
|
|
|
alter table hooks
|
|
drop column tenant_id,
|
|
alter column id type varchar(32);
|
|
|
|
create index hooks__event on hooks (event);
|
|
|
|
drop trigger set_tenant_id on hooks;
|
|
`);
|
|
},
|
|
};
|
|
|
|
export default alteration;
|