Summary
Routes without rlsModule (e.g., meta-schema routes for database provisioning) skip authentication entirely, causing jwt.claims.principal_id to not be set. This violates the agent-principal-identity.md specification which states:
Both claims are always set.
Affected Code
graphql/server/src/middleware/auth.ts (lines 57-79):
if (!rlsModule) {
log.info('[auth] No RLS module configured, skipping auth');
return next(); // Skips auth even if Bearer token is present
}
graphql/server/src/middleware/graphile.ts (lines 320-330):
// When req.token is not set, falls back to anonSettings
const anonSettings: Record<string, string> = {
role: anonRole,
...context, // Does not include principal_id
};
Impact
When a request hits a route without rlsModule:
auth.ts skips authentication entirely, even if Bearer token is present
req.token is undefined
graphile.ts uses anonSettings which has no principal_id
jwt.claims.principal_id is not set in PostgreSQL
- Any SPRT policy using
current_principal_id() fails
Specification Reference
From constructive-db/docs/spec/agent-principal-identity.md:
Section 3.1 (Two JWT Claims, Always Set):
// graphile.ts middleware
pgSettings['jwt.claims.principal_id'] = req.token.principal_id ?? req.token.user_id;
Section 8.1 (Normal User flow):
Bearer token -> authenticate() -> {user_id: human, principal_id: NULL}
Middleware:
jwt.claims.principal_id = human (fallback: ?? user_id)
The spec assumes authenticate() is always called when a Bearer token is present.
Suggested Fix
In auth.ts, when there's no rlsModule but a Bearer token is present, attempt platform-level authentication:
if (!rlsModule) {
const { authorization = '' } = req.headers;
const [authType, authToken] = authorization.split(' ');
if (authType?.toLowerCase() === 'bearer' && authToken) {
try {
const authQuery = `SELECT * FROM constructive_auth_private.authenticate($1)`;
const result = await pool.query(authQuery, [authToken]);
if (result?.rowCount && result.rowCount > 0) {
req.token = result.rows[0];
}
} catch (e: any) {
log.warn(`[auth] Platform auth failed: ${e.message}`);
}
}
return next();
}
Affected Routes
- Meta-schema routes (X-Meta-Schema header)
- Database provisioning endpoints
- Any route without a configured
rlsModule
Summary
Routes without
rlsModule(e.g., meta-schema routes for database provisioning) skip authentication entirely, causingjwt.claims.principal_idto not be set. This violates theagent-principal-identity.mdspecification which states:Affected Code
graphql/server/src/middleware/auth.ts(lines 57-79):graphql/server/src/middleware/graphile.ts(lines 320-330):Impact
When a request hits a route without
rlsModule:auth.tsskips authentication entirely, even if Bearer token is presentreq.tokenis undefinedgraphile.tsusesanonSettingswhich has noprincipal_idjwt.claims.principal_idis not set in PostgreSQLcurrent_principal_id()failsSpecification Reference
From
constructive-db/docs/spec/agent-principal-identity.md:Section 3.1 (Two JWT Claims, Always Set):
Section 8.1 (Normal User flow):
The spec assumes
authenticate()is always called when a Bearer token is present.Suggested Fix
In
auth.ts, when there's norlsModulebut a Bearer token is present, attempt platform-level authentication:Affected Routes
rlsModule