diff --git a/server/src/db/migrations.ts b/server/src/db/migrations.ts index 541f18e..37e5c4f 100644 --- a/server/src/db/migrations.ts +++ b/server/src/db/migrations.ts @@ -1,4 +1,5 @@ import Database from 'better-sqlite3'; +import { encrypt_api_key } from '../services/apiKeyCrypto'; function runMigrations(db: Database.Database): void { db.exec('CREATE TABLE IF NOT EXISTS schema_version (version INTEGER NOT NULL)'); @@ -448,6 +449,13 @@ function runMigrations(db: Database.Database): void { () => { try { db.exec('ALTER TABLE trips ADD COLUMN reminder_days INTEGER DEFAULT 3'); } catch (err: any) { if (!err.message?.includes('duplicate column name')) throw err; } }, + // Encrypt any plaintext oidc_client_secret left in app_settings + () => { + const row = db.prepare("SELECT value FROM app_settings WHERE key = 'oidc_client_secret'").get() as { value: string } | undefined; + if (row?.value && !row.value.startsWith('enc:v1:')) { + db.prepare("UPDATE app_settings SET value = ? WHERE key = 'oidc_client_secret'").run(encrypt_api_key(row.value)); + } + }, ]; if (currentVersion < migrations.length) { diff --git a/server/src/routes/admin.ts b/server/src/routes/admin.ts index eac08dd..e100165 100644 --- a/server/src/routes/admin.ts +++ b/server/src/routes/admin.ts @@ -9,6 +9,7 @@ import { AuthRequest, User, Addon } from '../types'; import { writeAudit, getClientIp, logInfo } from '../services/auditLog'; import { getAllPermissions, savePermissions, PERMISSION_ACTIONS } from '../services/permissions'; import { revokeUserSessions } from '../mcp'; +import { maybe_encrypt_api_key, decrypt_api_key } from '../services/apiKeyCrypto'; const router = express.Router(); @@ -232,7 +233,7 @@ router.get('/audit-log', (req: Request, res: Response) => { router.get('/oidc', (_req: Request, res: Response) => { const get = (key: string) => (db.prepare("SELECT value FROM app_settings WHERE key = ?").get(key) as { value: string } | undefined)?.value || ''; - const secret = get('oidc_client_secret'); + const secret = decrypt_api_key(get('oidc_client_secret')); res.json({ issuer: get('oidc_issuer'), client_id: get('oidc_client_id'), @@ -247,7 +248,7 @@ router.put('/oidc', (req: Request, res: Response) => { const set = (key: string, val: string) => db.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES (?, ?)").run(key, val || ''); set('oidc_issuer', issuer); set('oidc_client_id', client_id); - if (client_secret !== undefined) set('oidc_client_secret', client_secret); + if (client_secret !== undefined) set('oidc_client_secret', maybe_encrypt_api_key(client_secret) ?? ''); set('oidc_display_name', display_name); set('oidc_only', oidc_only ? 'true' : 'false'); const authReq = req as AuthRequest; diff --git a/server/src/routes/oidc.ts b/server/src/routes/oidc.ts index f600237..7f38dd8 100644 --- a/server/src/routes/oidc.ts +++ b/server/src/routes/oidc.ts @@ -5,6 +5,7 @@ import jwt from 'jsonwebtoken'; import { db } from '../db/database'; import { JWT_SECRET } from '../config'; import { User } from '../types'; +import { decrypt_api_key } from '../services/apiKeyCrypto'; interface OidcDiscoveryDoc { authorization_endpoint: string; @@ -57,7 +58,7 @@ function getOidcConfig() { const get = (key: string) => (db.prepare("SELECT value FROM app_settings WHERE key = ?").get(key) as { value: string } | undefined)?.value || null; const issuer = process.env.OIDC_ISSUER || get('oidc_issuer'); const clientId = process.env.OIDC_CLIENT_ID || get('oidc_client_id'); - const clientSecret = process.env.OIDC_CLIENT_SECRET || get('oidc_client_secret'); + const clientSecret = process.env.OIDC_CLIENT_SECRET || decrypt_api_key(get('oidc_client_secret')); const displayName = process.env.OIDC_DISPLAY_NAME || get('oidc_display_name') || 'SSO'; if (!issuer || !clientId || !clientSecret) return null; return { issuer: issuer.replace(/\/+$/, ''), clientId, clientSecret, displayName };