rls-guard
Version:
A CLI tool for managing PostgreSQL Row Level Security (RLS) policies as code
205 lines (166 loc) • 4.33 kB
text/typescript
// RLS Guard - Configuration Library (CommonJS)
// This file contains the types and builders for defining RLS policies
interface Policy {
name: string;
table: string;
command: string;
expression: string;
roles: string[];
permissive?: boolean;
}
interface DatabaseConfig {
url?: string;
host?: string;
port?: number;
database?: string;
username?: string;
password?: string;
ssl?: boolean;
}
export interface Config {
database: DatabaseConfig;
policies: Policy[];
}
class PolicyBuilder {
private policy: Partial<Policy>;
constructor() {
this.policy = {};
}
name(name: string) {
this.policy.name = name;
return this;
}
onTable(table: string) {
this.policy.table = table;
return this;
}
forCommand(command: string) {
this.policy.command = command;
return this;
}
withExpression(expression: string) {
this.policy.expression = expression;
return this;
}
forRoles(...roles: string[]) {
this.policy.roles = roles;
return this;
}
asPermissive() {
this.policy.permissive = true;
return this;
}
asRestrictive() {
this.policy.permissive = false;
return this;
}
build(): Policy {
if (!this.policy.name || !this.policy.table || !this.policy.command ||
!this.policy.expression || !this.policy.roles) {
throw new Error('Policy is missing required fields');
}
return this.policy as Policy;
}
}
class DatabaseBuilder {
private config: Partial<DatabaseConfig>;
constructor() {
this.config = {};
}
connectionUrl(url: string) {
this.config.url = url;
return this;
}
host(host: string) {
this.config.host = host;
return this;
}
port(port: number) {
this.config.port = port;
return this;
}
database(database: string) {
this.config.database = database;
return this;
}
username(username: string) {
this.config.username = username;
return this;
}
password(password: string) {
this.config.password = password;
return this;
}
ssl(enabled: boolean = true) {
this.config.ssl = enabled;
return this;
}
build(): DatabaseConfig {
if (!this.config.url && !this.config.host) {
throw new Error('Either connection URL or host must be specified');
}
return this.config as DatabaseConfig;
}
}
class ConfigBuilder {
private config: Config;
constructor() {
this.config = { database: '', policies: [] } as Config;
}
database(builder: (db: DatabaseBuilder) => DatabaseBuilder) {
this.config.database = builder(new DatabaseBuilder()).build();
return this;
}
addPolicy(builder: (pb: PolicyBuilder) => PolicyBuilder) {
const policy = builder(new PolicyBuilder()).build();
this.config.policies.push(policy);
return this;
}
build(): Config {
if (!this.config.database) {
throw new Error('Database configuration is required');
}
return this.config;
}
export(filename: string = 'rls.config.json') {
const fs = require('fs');
const config = this.build();
fs.writeFileSync(filename, JSON.stringify(config, null, 2));
}
}
// Helper functions for common RLS patterns
const currentUserId = (column: string = 'user_id') =>
`${column} = current_setting('app.current_user_id')::uuid`;
const tenantId = (column: string = 'tenant_id') =>
`${column} = current_setting('app.tenant_id')::uuid`;
const recentData = (column: string = 'created_at', days: number = 90) =>
`${column} >= current_date - interval '${days} days'`;
const ownerOnly = (userColumn: string = 'user_id', ownerColumn: string = 'owner_id') =>
`${userColumn} = ${ownerColumn}`;
const roleCheck = (role: string) =>
`current_setting('app.user_role') = '${role}'`;
const timeWindow = (column: string, hours: number) =>
`${column} >= now() - interval '${hours} hours'`;
const publicAccess = () => 'true';
const noAccess = () => 'false';
// Convenience functions
const policy = () => new PolicyBuilder();
const database = () => new DatabaseBuilder();
const config = () => new ConfigBuilder();
// Export everything
export default {
PolicyBuilder,
DatabaseBuilder,
ConfigBuilder,
currentUserId,
tenantId,
recentData,
ownerOnly,
roleCheck,
timeWindow,
publicAccess,
noAccess,
policy,
database,
config,
};