fix(api): CORS через isReturnToAllowed, мягкая проверка JWT_SECRET, 404 для /assets, better-sqlite3 в api, healthcheck на /ready
This commit is contained in:
1 parent
5cbf4d29d7
commit
08be310e6b
12 files changed
+93
-91
No files matched your search
+1
-1
@@ -50,7 +50,7 @@ import {
|
||||
import { createOriginChecker } from './lib/cors.js'
|
||||
|
||||
/** Пути сервера: для них SPA-fallback не применяется (404 JSON вместо index.html). */
|
||||
const API_PREFIXES = ['/api', '/oauth', '/.well-known']
|
||||
const API_PREFIXES = ['/api', '/oauth', '/.well-known', '/assets/']
|
||||
const HASHED_ASSET_RE = /[\\/]assets[\\/]/
|
||||
const INDEX_HTML_RE = /[\\/]index\.html$/
|
||||
|
||||
|
||||
@@ -65,12 +65,18 @@ const WEAK_JWT_SECRETS = new Set([
|
||||
'changeme',
|
||||
])
|
||||
|
||||
/** В production запрещены известные/слабые значения JWT_SECRET. */
|
||||
/** В production запрещены известные дефолты JWT_SECRET; короткий секрет — только предупреждение. */
|
||||
function assertProductionSecrets(config: AppConfig): void {
|
||||
if (!config.isProd) return
|
||||
if (WEAK_JWT_SECRETS.has(config.jwtSecret) || config.jwtSecret.length < 16) {
|
||||
if (WEAK_JWT_SECRETS.has(config.jwtSecret)) {
|
||||
throw new Error(
|
||||
'JWT_SECRET: в production нужен уникальный секрет длиной не менее 16 символов',
|
||||
'JWT_SECRET: в production нельзя использовать значение по умолчанию — задайте уникальный секрет',
|
||||
)
|
||||
}
|
||||
if (config.jwtSecret.length < 16) {
|
||||
// Не роняем старые установки с коротким, но уникальным секретом.
|
||||
process.emitWarning(
|
||||
'JWT_SECRET короче 16 символов — смените на более длинный (смена разлогинит пользователей)',
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isReturnToAllowed } from '@authportal/shared'
|
||||
import type { AppConfig } from '../config.js'
|
||||
import { oidcIssuerFromConfig } from '../config.js'
|
||||
|
||||
@@ -11,8 +12,8 @@ function originOf(url: string): string | null {
|
||||
|
||||
/**
|
||||
* Проверка Origin для CORS с credentials. Разрешены: CORS_ORIGINS, issuer,
|
||||
* приложения из App Switcher и хосты из RETURN_TO_ALLOWLIST
|
||||
* (".suffix" — суффикс хоста, "localhost", точные хосты). '*' в CORS_ORIGINS —
|
||||
* приложения из App Switcher и всё, что разрешает RETURN_TO_ALLOWLIST
|
||||
* (та же isReturnToAllowed, что для return_to: ".suffix", localhost, полные origin, private). '*' в CORS_ORIGINS —
|
||||
* разрешить всё (только осознанно).
|
||||
*/
|
||||
export function createOriginChecker(
|
||||
@@ -34,21 +35,13 @@ export function createOriginChecker(
|
||||
const o = originOf(u)
|
||||
if (o) explicit.add(o)
|
||||
}
|
||||
const tokens = config.returnToAllowlist
|
||||
.split(',')
|
||||
.map((t) => t.trim().toLowerCase())
|
||||
.filter(Boolean)
|
||||
|
||||
return (origin: string) => {
|
||||
if (allowAll) return true
|
||||
const o = originOf(origin)
|
||||
if (!o) return false
|
||||
if (explicit.has(o)) return true
|
||||
const host = new URL(o).hostname.toLowerCase()
|
||||
for (const t of tokens) {
|
||||
if (t === 'private') continue
|
||||
if (t.startsWith('.') ? host.endsWith(t) : host === t) return true
|
||||
}
|
||||
// Тот же allowlist, что у return_to (.suffix, localhost, полные origin, private).
|
||||
if (isReturnToAllowed(o, config.returnToAllowlist)) return true
|
||||
for (const u of switcherUrls()) {
|
||||
if (originOf(u) === o) return true
|
||||
}
|
||||
|
||||
@@ -216,10 +216,10 @@ export async function authRoutes(app: FastifyInstance): Promise<void> {
|
||||
)
|
||||
|
||||
/** Public — apps chrome (CFDM/VPS) fetch switcher URLs without portal JWT. */
|
||||
app.get('/api/v1/app-switcher', async (_request, reply) => {
|
||||
reply.header('Cache-Control', 'public, max-age=30')
|
||||
return publicAppSwitcherConfig(getAppSwitcherConfig(app.db))
|
||||
})
|
||||
// Без Cache-Control: после сохранения в админке SPA должна сразу видеть новый конфиг.
|
||||
app.get('/api/v1/app-switcher', async () =>
|
||||
publicAppSwitcherConfig(getAppSwitcherConfig(app.db)),
|
||||
)
|
||||
|
||||
app.get(
|
||||
'/api/v1/auth/me',
|
||||
|
||||
Reference in new issue
Block a user