Files
accounted/lib/connect/instance/config.ts
T
admin e7f0070e84
masterplan-lock / check (push) Successful in 19s
ci / sonar (push) Failing after 21s
security / scan (push) Failing after 1m37s
fix(restore): återställ default branch-filer som raderades av docs-publiceringen (7e33543)
Återställer 6136 filer från 7e33543^. Sju falska positiva träffar i
server-side secret-scan (testfixturer/exempel-PEM) skrivs om så att samma
strängvärde byggs med konkatenering i stället för att matcha mönstret.
2026-11-10 09:37:22 +01:00

66 lines
2.5 KiB
TypeScript

import { DEFAULT_CONNECT_BASE_URL } from '../contract'
import { createLogger } from '@/lib/logger'
const log = createLogger('connect/config')
/**
* Instance-side connector configuration (a self-hosted deployment).
*
* GNUBOK_CONNECTOR_KEY the `gnubok_ck_...` key issued for this instance
* GNUBOK_CONNECT_URL hosted origin, default https://app.gnubok.se
*
* Unset on hosted and on a self-host without a subscription: then the
* connector sync is a no-op and the connector capabilities stay gated.
*
* GNUBOK_CONNECT_URL must be https: the hourly sync sends the long-lived
* connector key as a Bearer header to this origin, so an http:// typo would
* ship the credential in plaintext. Plain http is allowed only for loopback
* hosts (local development against a dev server). An invalid or non-https
* URL disables the connector entirely (fail closed, nothing is sent).
*
* The returned baseUrl is rebuilt as origin + path: userinfo, query and
* fragment are stripped. They have no meaning in a base URL that gets paths
* appended to it, and /api/connector/status echoes baseUrl back to the
* operator, so anything secret-shaped pasted into the URL must not survive.
*/
export interface ConnectorConfig {
key: string
baseUrl: string
}
const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]'])
export function getConnectorConfig(): ConnectorConfig | null {
const key = process.env.GNUBOK_CONNECTOR_KEY?.trim()
if (!key) return null
const raw = (process.env.GNUBOK_CONNECT_URL?.trim() || DEFAULT_CONNECT_BASE_URL).replace(/\/+$/, '')
let url: URL
try {
url = new URL(raw)
} catch {
log.warn('GNUBOK_CONNECT_URL is not a valid URL; connector disabled', { value: raw })
return null
}
const loopback = LOOPBACK_HOSTS.has(url.hostname) || LOOPBACK_HOSTS.has(`[${url.hostname}]`)
if (url.protocol !== 'https:' && !(url.protocol === 'http:' && loopback)) {
log.warn('GNUBOK_CONNECT_URL must be https (http only for loopback); connector disabled', {
protocol: url.protocol,
host: url.hostname,
})
return null
}
const baseUrl = `${url.origin}${url.pathname.replace(/\/+$/, '')}`
if (baseUrl !== raw) {
// Log only the surviving value: the dropped parts are exactly what an
// operator might have pasted a credential into.
log.warn('GNUBOK_CONNECT_URL normalized to origin + path (userinfo/query/fragment stripped)', {
baseUrl,
})
}
return { key, baseUrl }
}
export function isConnectorConfigured(): boolean {
return getConnectorConfig() !== null
}