Fix secrets required injection for fastify

This commit is contained in:
2025-11-01 11:22:32 +07:00
parent b26c5191ee
commit bc13202762
2 changed files with 54 additions and 12 deletions

View File

@@ -12,15 +12,21 @@ declare module 'fastify' {
} }
// Docker secrets are mounted at /run/secrets/ by default in CapRover // Docker secrets are mounted at /run/secrets/ by default in CapRover
const readSecretFile = (secretName: string): string | undefined => { const readSecretFile = (secretName: string, logger?: any): string | undefined => {
const secretPath = `/run/secrets/${secretName}` const secretPath = `/run/secrets/${secretName}`
logger?.debug({ secretPath, exists: fs.existsSync(secretPath) }, `Checking secret file: ${secretName}`)
if (fs.existsSync(secretPath)) { if (fs.existsSync(secretPath)) {
try { try {
return fs.readFileSync(secretPath, 'utf8').trim() const content = fs.readFileSync(secretPath, 'utf8').trim()
logger?.info({ secretName, path: secretPath, length: content.length }, `Successfully read secret: ${secretName}`)
return content
} catch (error) { } catch (error) {
console.error(`Failed to read secret file ${secretPath}:`, error) logger?.error({ error, secretPath }, `Failed to read secret file ${secretPath}`)
return undefined return undefined
} }
} else {
logger?.warn({ secretPath }, `Secret file does not exist: ${secretPath}`)
} }
return undefined return undefined
} }
@@ -28,15 +34,39 @@ const readSecretFile = (secretName: string): string | undefined => {
export default fp(async function (fastify) { export default fp(async function (fastify) {
const isProd = process.env.NODE_ENV === 'production' const isProd = process.env.NODE_ENV === 'production'
fastify.log.info({ isProd, nodeEnv: process.env.NODE_ENV }, 'Loading Privy secrets')
// Debug: List all files in /run/secrets/ directory
const secretsDir = '/run/secrets'
if (fs.existsSync(secretsDir)) {
try {
const files = fs.readdirSync(secretsDir)
fastify.log.info({ files, dir: secretsDir }, 'Files found in /run/secrets/')
} catch (error) {
fastify.log.warn({ error }, 'Could not list /run/secrets/ directory')
}
} else {
fastify.log.warn({ dir: secretsDir }, '/run/secrets/ directory does not exist')
}
let appId: string let appId: string
let appSecret: string let appSecret: string
let authKey: string let authKey: string
if (isProd) { if (isProd) {
// In production, read from Docker secrets (mounted files) // In production, read from Docker secrets (mounted files)
appId = readSecretFile('PRIVY_APP_ID') || process.env.PRIVY_APP_ID || '' appId = readSecretFile('PRIVY_APP_ID', fastify.log) || process.env.PRIVY_APP_ID || ''
appSecret = readSecretFile('PRIVY_APP_SECRET') || process.env.PRIVY_APP_SECRET || '' appSecret = readSecretFile('PRIVY_APP_SECRET', fastify.log) || process.env.PRIVY_APP_SECRET || ''
authKey = readSecretFile('PRIVY_AUTHORIZATION_KEY') || process.env.PRIVY_AUTHORIZATION_KEY || '' authKey = readSecretFile('PRIVY_AUTHORIZATION_KEY', fastify.log) || process.env.PRIVY_AUTHORIZATION_KEY || ''
fastify.log.info({
appId: !!appId,
appSecret: !!appSecret,
authKey: !!authKey,
appIdLength: appId.length,
appSecretLength: appSecret.length,
authKeyLength: authKey.length
}, 'Privy secrets loaded from Docker secrets')
} else { } else {
// In non-production, use env vars or file paths // In non-production, use env vars or file paths
const readMaybeFile = (envKey: string, fileKey: string): string | undefined => { const readMaybeFile = (envKey: string, fileKey: string): string | undefined => {
@@ -51,13 +81,14 @@ export default fp(async function (fastify) {
} }
if (!appId || !appSecret || !authKey) { if (!appId || !appSecret || !authKey) {
fastify.log.warn('Privy secrets not fully resolved at plugin load.') fastify.log.error({
fastify.log.warn({
appId: !!appId, appId: !!appId,
appSecret: !!appSecret, appSecret: !!appSecret,
authKey: !!authKey, authKey: !!authKey,
isProd isProd,
}, 'Privy secrets status') nodeEnv: process.env.NODE_ENV
}, 'Privy secrets not fully resolved at plugin load')
throw new Error('Missing required Privy secrets. Check Docker secrets are mounted correctly.')
} }
fastify.decorate('privySecrets', { fastify.decorate('privySecrets', {
@@ -66,5 +97,9 @@ export default fp(async function (fastify) {
authKey authKey
}) })
fastify.log.info('Privy secrets decorated on Fastify instance') fastify.log.info({
appId: appId.substring(0, 10) + '...',
appSecret: appSecret.substring(0, 10) + '...',
authKey: authKey.substring(0, 20) + '...'
}, 'Privy secrets decorated on Fastify instance')
}, { name: 'privy-secrets' }) }, { name: 'privy-secrets' })

View File

@@ -86,9 +86,16 @@ export const autoConfig = {
* @see {@link https://github.com/fastify/fastify-env} * @see {@link https://github.com/fastify/fastify-env}
*/ */
export default fp(async (fastify) => { export default fp(async (fastify) => {
// In production, Privy secrets come from Docker secrets (mounted files), not env vars
// Make them optional in the schema to avoid validation errors
const isProd = process.env.NODE_ENV === 'production'
const required = isProd
? ['COOKIE_SECRET'] // In production, only require COOKIE_SECRET from env
: ['PRIVY_APP_ID', 'PRIVY_APP_SECRET', 'PRIVY_AUTHORIZATION_KEY', 'COOKIE_SECRET']
const schema = { const schema = {
type: 'object', type: 'object',
required: ['PRIVY_APP_ID', 'PRIVY_APP_SECRET', 'PRIVY_AUTHORIZATION_KEY', 'COOKIE_SECRET'], required: required,
properties: { properties: {
PRIVY_APP_ID: { PRIVY_APP_ID: {
type: 'string' type: 'string'