fix(backend): allow deferred provider integrations

This commit is contained in:
jjbrunton committed 2026-08-28 15:00:57 +01:00
1 parent 96882642c7
commit 32f5214751
4 files changed
+44 -11

No files matched your search

+9 -6
View File
@@ -1,5 +1,8 @@
import { z } from 'zod'; import { z } from 'zod';
const optionalString = z.preprocess((value) => value === '' ? undefined : value, z.string().min(1).optional());
const optionalUuid = z.preprocess((value) => value === '' ? undefined : value, z.string().uuid().optional());
const baseSchema = z.object({ const baseSchema = z.object({
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'), NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
PORT: z.coerce.number().int().min(1).max(65535).default(3003), PORT: z.coerce.number().int().min(1).max(65535).default(3003),
@@ -12,13 +15,13 @@ const baseSchema = z.object({
MEDIA_ROOT: z.string().min(1).default('/data/media'), MEDIA_ROOT: z.string().min(1).default('/data/media'),
MEDIA_SIGNING_SECRET: z.string().min(32).optional(), MEDIA_SIGNING_SECRET: z.string().min(32).optional(),
MEDIA_PUBLIC_BASE_URL: z.string().url().optional(), MEDIA_PUBLIC_BASE_URL: z.string().url().optional(),
SUPABASE_AUTH_SERVICE_ROLE_KEY: z.string().min(1).optional(), SUPABASE_AUTH_SERVICE_ROLE_KEY: optionalString,
REVENUECAT_API_KEY: z.string().min(1).optional(), REVENUECAT_API_KEY: optionalString,
REVENUECAT_ENTITLEMENT_ID: z.string().min(1).default('Sauci Pro'), REVENUECAT_ENTITLEMENT_ID: z.string().min(1).default('Sauci Pro'),
REVENUECAT_WEBHOOK_SECRET: z.string().min(1).optional(), REVENUECAT_WEBHOOK_SECRET: optionalString,
ADMIN_API_SERVICE_TOKEN: z.string().min(32).optional(), ADMIN_API_SERVICE_TOKEN: z.preprocess((value) => value === '' ? undefined : value, z.string().min(32).optional()),
ADMIN_API_SERVICE_USER_ID: z.string().uuid().optional(), ADMIN_API_SERVICE_USER_ID: optionalUuid,
ADMIN_PRIVATE_KEY_JWK: z.string().optional(), ADMIN_PRIVATE_KEY_JWK: optionalString,
}); });
export interface AppConfig { export interface AppConfig {
+24
View File
@@ -61,4 +61,28 @@ describe('Supabase Auth JWT verification', () => {
AUTH_TEST_JWKS: jwks, AUTH_TEST_JWKS: jwks,
})).toThrow('AUTH_TEST_JWKS is forbidden'); })).toThrow('AUTH_TEST_JWKS is forbidden');
}); });
it('treats empty optional deployment integrations as disabled', async () => {
const { jwks } = await signedToken();
const config = loadConfig({
NODE_ENV: 'test',
DATABASE_URL: 'postgresql://localhost/sauci_test',
SUPABASE_AUTH_ISSUER: issuer,
AUTH_TEST_JWKS: jwks,
SUPABASE_AUTH_SERVICE_ROLE_KEY: '',
REVENUECAT_API_KEY: '',
REVENUECAT_WEBHOOK_SECRET: '',
ADMIN_API_SERVICE_TOKEN: '',
ADMIN_API_SERVICE_USER_ID: '',
ADMIN_PRIVATE_KEY_JWK: '',
});
expect(config).toMatchObject({
supabaseAuthServiceRoleKey: undefined,
revenueCatApiKey: undefined,
revenueCatWebhookSecret: undefined,
adminApiServiceToken: undefined,
adminApiServiceUserId: undefined,
adminPrivateKeyJwk: undefined,
});
});
}); });
+6
View File
@@ -45,6 +45,12 @@ different name `SAUCI_ADMIN_API_TOKEN` plus its independent caller-facing
`SAUCI_MCP_API_KEY`. The token does not confer Supabase authority: the API maps `SAUCI_MCP_API_KEY`. The token does not confer Supabase authority: the API maps
it to the configured administrator and applies normal permissions and auditing. it to the configured administrator and applies normal permissions and auditing.
Do not give either admin token to mobile, admin browser, or worker containers. Do not give either admin token to mobile, admin browser, or worker containers.
An environment without an imported active administrator may leave both values
empty; customer API routes still run, while MCP service authentication remains
disabled until the pair is configured. RevenueCat credentials are likewise
optional at infrastructure bootstrap, but purchase verification, webhooks, and
account-deletion provider cleanup remain unavailable until their dedicated
server-side credentials are installed.
Secret ownership is intentionally split: Secret ownership is intentionally split:
+5 -5
View File
@@ -10,12 +10,12 @@ x-api-environment: &api-environment
PORT: 3003 PORT: 3003
SUPABASE_AUTH_URL: ${SUPABASE_AUTH_URL:?set SUPABASE_AUTH_URL} SUPABASE_AUTH_URL: ${SUPABASE_AUTH_URL:?set SUPABASE_AUTH_URL}
SUPABASE_AUTH_AUDIENCE: ${SUPABASE_AUTH_AUDIENCE:-authenticated} SUPABASE_AUTH_AUDIENCE: ${SUPABASE_AUTH_AUDIENCE:-authenticated}
SUPABASE_AUTH_SERVICE_ROLE_KEY: ${SUPABASE_AUTH_SERVICE_ROLE_KEY:?set SUPABASE_AUTH_SERVICE_ROLE_KEY} SUPABASE_AUTH_SERVICE_ROLE_KEY: ${SUPABASE_AUTH_SERVICE_ROLE_KEY:-}
REVENUECAT_API_KEY: ${REVENUECAT_API_KEY:?set REVENUECAT_API_KEY} REVENUECAT_API_KEY: ${REVENUECAT_API_KEY:-}
REVENUECAT_ENTITLEMENT_ID: ${REVENUECAT_ENTITLEMENT_ID:-Sauci Pro} REVENUECAT_ENTITLEMENT_ID: ${REVENUECAT_ENTITLEMENT_ID:-Sauci Pro}
REVENUECAT_WEBHOOK_SECRET: ${REVENUECAT_WEBHOOK_SECRET:?set REVENUECAT_WEBHOOK_SECRET} REVENUECAT_WEBHOOK_SECRET: ${REVENUECAT_WEBHOOK_SECRET:-}
ADMIN_API_SERVICE_TOKEN: ${ADMIN_API_SERVICE_TOKEN:?set ADMIN_API_SERVICE_TOKEN} ADMIN_API_SERVICE_TOKEN: ${ADMIN_API_SERVICE_TOKEN:-}
ADMIN_API_SERVICE_USER_ID: ${ADMIN_API_SERVICE_USER_ID:?set ADMIN_API_SERVICE_USER_ID} ADMIN_API_SERVICE_USER_ID: ${ADMIN_API_SERVICE_USER_ID:-}
ADMIN_PRIVATE_KEY_JWK: ${ADMIN_PRIVATE_KEY_JWK:-} ADMIN_PRIVATE_KEY_JWK: ${ADMIN_PRIVATE_KEY_JWK:-}
x-worker-environment: &worker-environment x-worker-environment: &worker-environment