Skip to content

Commit e42311e

Browse files
authored
Merge pull request #767 from constructive-io/rls/rls-module-fix
fix: switch RLS module query from metaschema table to services_public.api_modules
2 parents 0df387d + 1b20b00 commit e42311e

4 files changed

Lines changed: 137 additions & 55 deletions

File tree

graphql/server/src/middleware/__tests__/upload.test.ts

Lines changed: 65 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,13 @@ describe('createUploadAuthenticateMiddleware', () => {
126126
...baseApi,
127127
rlsModule: {
128128
authenticate: 'authenticate',
129+
authenticateStrict: 'authenticate_strict',
129130
privateSchema: { schemaName: 'private' },
131+
publicSchema: { schemaName: 'public' },
132+
currentRole: 'current_user',
133+
currentRoleId: 'current_user_id',
134+
currentIpAddress: 'current_ip_address',
135+
currentUserAgent: 'current_user_agent',
130136
},
131137
},
132138
headers: {
@@ -181,9 +187,18 @@ describe('createUploadAuthenticateMiddleware', () => {
181187
rootPool.query.mockResolvedValueOnce({
182188
rows: [
183189
{
184-
authenticate: 'authenticate',
185-
authenticate_strict: 'authenticate_strict',
190+
data: {
191+
authenticate: 'authenticate',
192+
authenticate_strict: 'authenticate_strict',
193+
authenticate_schema: 'private',
194+
role_schema: 'public',
195+
current_role: 'current_user',
196+
current_role_id: 'current_user_id',
197+
current_ip_address: 'current_ip_address',
198+
current_user_agent: 'current_user_agent',
199+
},
186200
private_schema_name: 'private',
201+
public_schema_name: 'public',
187202
},
188203
],
189204
});
@@ -196,7 +211,7 @@ describe('createUploadAuthenticateMiddleware', () => {
196211
await middleware(req, res, next);
197212

198213
expect(rootPool.query).toHaveBeenCalledWith(
199-
expect.stringContaining('WHERE a.database_id = $1'),
214+
expect.stringContaining('WHERE'),
200215
['db-123'],
201216
);
202217
expect(next).toHaveBeenCalledTimes(1);
@@ -220,9 +235,18 @@ describe('createUploadAuthenticateMiddleware', () => {
220235
rootPool.query.mockResolvedValueOnce({
221236
rows: [
222237
{
223-
authenticate: 'authenticate',
224-
authenticate_strict: 'authenticate_strict',
238+
data: {
239+
authenticate: 'authenticate',
240+
authenticate_strict: 'authenticate_strict',
241+
authenticate_schema: 'private',
242+
role_schema: 'public',
243+
current_role: 'current_user',
244+
current_role_id: 'current_user_id',
245+
current_ip_address: 'current_ip_address',
246+
current_user_agent: 'current_user_agent',
247+
},
225248
private_schema_name: 'private',
249+
public_schema_name: 'public',
226250
},
227251
],
228252
});
@@ -235,7 +259,7 @@ describe('createUploadAuthenticateMiddleware', () => {
235259
await middleware(req, res, next);
236260

237261
expect(rootPool.query).toHaveBeenCalledWith(
238-
expect.stringContaining('WHERE rm.api_id = $1'),
262+
expect.stringContaining('WHERE'),
239263
['api-123'],
240264
);
241265
expect(next).toHaveBeenCalledTimes(1);
@@ -261,9 +285,18 @@ describe('createUploadAuthenticateMiddleware', () => {
261285
rootPool.query.mockResolvedValueOnce({
262286
rows: [
263287
{
264-
authenticate: 'authenticate',
265-
authenticate_strict: 'authenticate_strict',
288+
data: {
289+
authenticate: 'authenticate',
290+
authenticate_strict: 'authenticate_strict',
291+
authenticate_schema: 'private',
292+
role_schema: 'public',
293+
current_role: 'current_user',
294+
current_role_id: 'current_user_id',
295+
current_ip_address: 'current_ip_address',
296+
current_user_agent: 'current_user_agent',
297+
},
266298
private_schema_name: 'private',
299+
public_schema_name: 'public',
267300
},
268301
],
269302
});
@@ -275,7 +308,7 @@ describe('createUploadAuthenticateMiddleware', () => {
275308
await middleware(req, res, next);
276309

277310
expect(rootPool.query).toHaveBeenCalledWith(
278-
expect.stringContaining('WHERE a.dbname = $1'),
311+
expect.stringContaining('WHERE'),
279312
['tenant_db'],
280313
);
281314
expect(next).toHaveBeenCalledTimes(1);
@@ -320,7 +353,13 @@ describe('createUploadAuthenticateMiddleware', () => {
320353
...baseApi,
321354
rlsModule: {
322355
authenticate: 'authenticate',
356+
authenticateStrict: 'authenticate_strict',
323357
privateSchema: { schemaName: 'private' },
358+
publicSchema: { schemaName: 'public' },
359+
currentRole: 'current_user',
360+
currentRoleId: 'current_user_id',
361+
currentIpAddress: 'current_ip_address',
362+
currentUserAgent: 'current_user_agent',
324363
},
325364
},
326365
headers: { authorization: 'Bearer invalid-token' },
@@ -352,7 +391,13 @@ describe('createUploadAuthenticateMiddleware', () => {
352391
...baseApi,
353392
rlsModule: {
354393
authenticate: 'authenticate',
394+
authenticateStrict: 'authenticate_strict',
355395
privateSchema: { schemaName: 'private' },
396+
publicSchema: { schemaName: 'public' },
397+
currentRole: 'current_user',
398+
currentRoleId: 'current_user_id',
399+
currentIpAddress: 'current_ip_address',
400+
currentUserAgent: 'current_user_agent',
356401
},
357402
},
358403
headers: { authorization: 'Bearer bad-token' },
@@ -383,6 +428,11 @@ describe('createUploadAuthenticateMiddleware', () => {
383428
authenticate: 'authenticate',
384429
authenticateStrict: 'authenticate_strict',
385430
privateSchema: { schemaName: 'private' },
431+
publicSchema: { schemaName: 'public' },
432+
currentRole: 'current_user',
433+
currentRoleId: 'current_user_id',
434+
currentIpAddress: 'current_ip_address',
435+
currentUserAgent: 'current_user_agent',
386436
},
387437
},
388438
headers: { authorization: 'Bearer strict-token' },
@@ -415,7 +465,13 @@ describe('createUploadAuthenticateMiddleware', () => {
415465
...baseApi,
416466
rlsModule: {
417467
authenticate: 'authenticate',
468+
authenticateStrict: '',
418469
privateSchema: { schemaName: 'private' },
470+
publicSchema: { schemaName: 'public' },
471+
currentRole: 'current_user',
472+
currentRoleId: 'current_user_id',
473+
currentIpAddress: 'current_ip_address',
474+
currentUserAgent: 'current_user_agent',
419475
},
420476
},
421477
headers: { authorization: 'Bearer strict-token' },

graphql/server/src/middleware/api.ts

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,9 @@ const API_LIST_SQL = `
8080
`;
8181

8282
const RLS_MODULE_SQL = `
83-
SELECT
84-
rm.authenticate,
85-
rm.authenticate_strict,
86-
ps.schema_name as private_schema_name
87-
FROM metaschema_modules_public.rls_module rm
88-
LEFT JOIN metaschema_public.schema ps ON rm.private_schema_id = ps.id
89-
WHERE rm.api_id = $1
83+
SELECT data
84+
FROM services_public.api_modules
85+
WHERE api_id = $1 AND name = 'rls_module'
9086
LIMIT 1
9187
`;
9288

@@ -104,10 +100,19 @@ interface ApiRow {
104100
schemas: string[];
105101
}
106102

103+
interface RlsModuleData {
104+
authenticate: string;
105+
authenticate_strict: string;
106+
authenticate_schema: string;
107+
role_schema: string;
108+
current_role: string;
109+
current_role_id: string;
110+
current_ip_address: string;
111+
current_user_agent: string;
112+
}
113+
107114
interface RlsModuleRow {
108-
authenticate: string | null;
109-
authenticate_strict: string | null;
110-
private_schema_name: string | null;
115+
data: RlsModuleData | null;
111116
}
112117

113118
interface ApiListRow {
@@ -185,13 +190,21 @@ export const getSvcKey = (opts: ApiOptions, req: Request): string => {
185190
};
186191

187192
const toRlsModule = (row: RlsModuleRow | null): RlsModule | undefined => {
188-
if (!row || !row.private_schema_name) return undefined;
193+
if (!row?.data) return undefined;
194+
const d = row.data;
189195
return {
190-
authenticate: row.authenticate ?? undefined,
191-
authenticateStrict: row.authenticate_strict ?? undefined,
196+
authenticate: d.authenticate,
197+
authenticateStrict: d.authenticate_strict,
192198
privateSchema: {
193-
schemaName: row.private_schema_name,
199+
schemaName: d.authenticate_schema,
200+
},
201+
publicSchema: {
202+
schemaName: d.role_schema,
194203
},
204+
currentRole: d.current_role,
205+
currentRoleId: d.current_role_id,
206+
currentIpAddress: d.current_ip_address,
207+
currentUserAgent: d.current_user_agent,
195208
};
196209
};
197210

graphql/server/src/middleware/upload.ts

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -56,45 +56,58 @@ const parseFileWithErrors: RequestHandler = (req, res, next) => {
5656
});
5757
};
5858

59-
const RLS_MODULE_BASE_SQL = `
60-
SELECT
61-
rm.authenticate,
62-
rm.authenticate_strict,
63-
ps.schema_name as private_schema_name
64-
FROM metaschema_modules_public.rls_module rm
65-
LEFT JOIN metaschema_public.schema ps ON rm.private_schema_id = ps.id`;
66-
67-
const RLS_MODULE_BY_DATABASE_ID_SQL = `${RLS_MODULE_BASE_SQL}
68-
JOIN services_public.apis a ON rm.api_id = a.id
69-
WHERE a.database_id = $1
59+
const RLS_MODULE_BY_DATABASE_ID_SQL = `
60+
SELECT am.data
61+
FROM services_public.api_modules am
62+
JOIN services_public.apis a ON am.api_id = a.id
63+
WHERE am.name = 'rls_module' AND a.database_id = $1
7064
ORDER BY a.id
7165
LIMIT 1
7266
`;
7367

74-
const RLS_MODULE_BY_API_ID_SQL = `${RLS_MODULE_BASE_SQL}
75-
WHERE rm.api_id = $1
68+
const RLS_MODULE_BY_API_ID_SQL = `
69+
SELECT data
70+
FROM services_public.api_modules
71+
WHERE api_id = $1 AND name = 'rls_module'
7672
LIMIT 1
7773
`;
7874

79-
const RLS_MODULE_BY_DBNAME_SQL = `${RLS_MODULE_BASE_SQL}
80-
JOIN services_public.apis a ON rm.api_id = a.id
81-
WHERE a.dbname = $1
75+
const RLS_MODULE_BY_DBNAME_SQL = `
76+
SELECT am.data
77+
FROM services_public.api_modules am
78+
JOIN services_public.apis a ON am.api_id = a.id
79+
WHERE am.name = 'rls_module' AND a.dbname = $1
8280
ORDER BY a.id
8381
LIMIT 1
8482
`;
8583

84+
interface RlsModuleData {
85+
authenticate: string;
86+
authenticate_strict: string;
87+
authenticate_schema: string;
88+
role_schema: string;
89+
current_role: string;
90+
current_role_id: string;
91+
current_ip_address: string;
92+
current_user_agent: string;
93+
}
94+
8695
interface RlsModuleRow {
87-
authenticate: string | null;
88-
authenticate_strict: string | null;
89-
private_schema_name: string | null;
96+
data: RlsModuleData | null;
9097
}
9198

9299
const toRlsModule = (row: RlsModuleRow | null): RlsModule | undefined => {
93-
if (!row || !row.private_schema_name) return undefined;
100+
if (!row?.data) return undefined;
101+
const d = row.data;
94102
return {
95-
authenticate: row.authenticate ?? undefined,
96-
authenticateStrict: row.authenticate_strict ?? undefined,
97-
privateSchema: { schemaName: row.private_schema_name },
103+
authenticate: d.authenticate,
104+
authenticateStrict: d.authenticate_strict,
105+
privateSchema: { schemaName: d.authenticate_schema },
106+
publicSchema: { schemaName: d.role_schema },
107+
currentRole: d.current_role,
108+
currentRoleId: d.current_role_id,
109+
currentIpAddress: d.current_ip_address,
110+
currentUserAgent: d.current_user_agent,
98111
};
99112
};
100113

@@ -204,13 +217,6 @@ export const createUploadAuthenticateMiddleware = (
204217
return;
205218
}
206219

207-
const SAFE_IDENTIFIER = /^[a-z_][a-z0-9_]*$/;
208-
if (!SAFE_IDENTIFIER.test(privateSchema) || !SAFE_IDENTIFIER.test(authFn)) {
209-
authLog.error(`[upload-auth] Invalid SQL identifier: schema=${privateSchema} fn=${authFn}`);
210-
authError(res);
211-
return;
212-
}
213-
214220
const pool = getPgPool({
215221
...opts.pg,
216222
database: api.dbname,

graphql/server/src/types.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,18 @@ export type ApiModule =
2424
| { name: string; data?: GenericModuleData };
2525

2626
export interface RlsModule {
27-
authenticate?: string;
28-
authenticateStrict?: string;
27+
authenticate: string;
28+
authenticateStrict: string;
2929
privateSchema: {
3030
schemaName: string;
3131
};
32+
publicSchema: {
33+
schemaName: string;
34+
};
35+
currentRole: string;
36+
currentRoleId: string;
37+
currentIpAddress: string;
38+
currentUserAgent: string;
3239
}
3340

3441
export interface ApiStructure {

0 commit comments

Comments
 (0)