File size: 3,146 Bytes
943fe08
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import { randomBytes } from 'node:crypto'
import fs from 'node:fs'
import path from 'node:path'
import { config, ensureDirExists } from './config'
import { logger } from './logger'

function getGeneratedFilePath(): string {
  return path.join(config.dataDir, '.auto-generated')
}

interface PersistedValues {
  AUTH_SECRET?: string
  API_KEY?: string
}

function readPersisted(): PersistedValues {
  try {
    if (!fs.existsSync(getGeneratedFilePath())) return {}
    const raw = fs.readFileSync(getGeneratedFilePath(), 'utf8')
    const values: PersistedValues = {}
    for (const line of raw.split('\n')) {
      const trimmed = line.trim()
      if (!trimmed || trimmed.startsWith('#')) continue
      const eqIdx = trimmed.indexOf('=')
      if (eqIdx < 0) continue
      const key = trimmed.slice(0, eqIdx).trim()
      const value = trimmed.slice(eqIdx + 1).trim()
      if (key === 'AUTH_SECRET' || key === 'API_KEY') {
        values[key] = value
      }
    }
    return values
  } catch {
    return {}
  }
}

function writePersisted(values: PersistedValues): void {
  try {
    ensureDirExists(config.dataDir)
    const lines = [
      '# Auto-generated values. Overridden by env vars when set.',
    ]
    if (values.AUTH_SECRET) lines.push(`AUTH_SECRET=${values.AUTH_SECRET}`)
    if (values.API_KEY) lines.push(`API_KEY=${values.API_KEY}`)
    fs.writeFileSync(getGeneratedFilePath(), lines.join('\n') + '\n', { mode: 0o600 })
  } catch (err) {
    logger.warn({ err }, 'Failed to persist auto-generated values')
  }
}

function generate(): string {
  return randomBytes(32).toString('hex')
}

// Known placeholder values from .env.example that should be replaced
const PLACEHOLDER_AUTH_SECRETS = new Set([
  'random-secret-for-legacy-cookies',
])
const PLACEHOLDER_API_KEYS = new Set([
  'generate-a-random-key',
])

/**
 * Ensure AUTH_SECRET and API_KEY are available.
 * Priority: env var > persisted file > auto-generate + persist.
 * Sets process.env so downstream code picks them up.
 */
export function ensureAutoGeneratedCredentials(): void {
  if (process.env.NEXT_PHASE === 'phase-production-build') return

  const persisted = readPersisted()
  let dirty = false

  // AUTH_SECRET
  const currentAuthSecret = (process.env.AUTH_SECRET || '').trim()
  if (!currentAuthSecret || PLACEHOLDER_AUTH_SECRETS.has(currentAuthSecret)) {
    if (persisted.AUTH_SECRET) {
      process.env.AUTH_SECRET = persisted.AUTH_SECRET
    } else {
      const val = generate()
      process.env.AUTH_SECRET = val
      persisted.AUTH_SECRET = val
      dirty = true
      logger.info('Auto-generated AUTH_SECRET (persisted to .data/.auto-generated)')
    }
  }

  // API_KEY
  const currentApiKey = (process.env.API_KEY || '').trim()
  if (!currentApiKey || PLACEHOLDER_API_KEYS.has(currentApiKey)) {
    if (persisted.API_KEY) {
      process.env.API_KEY = persisted.API_KEY
    } else {
      const val = generate()
      process.env.API_KEY = val
      persisted.API_KEY = val
      dirty = true
      logger.info('Auto-generated API_KEY (persisted to .data/.auto-generated)')
    }
  }

  if (dirty) {
    writePersisted(persisted)
  }
}