0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00
logto/packages/schemas/alterations/1.0.0_rc.1-1675788753-multi-tenancy-rls.ts
2024-03-18 09:59:38 +08:00

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;