Merge pull request #298 from jubnl/dev
feat: Adds 2 environment variables to control initial admin user credentials, adds 1 environment variable to control OIDC scope
This commit is contained in:
@@ -154,8 +154,11 @@ services:
|
|||||||
# - OIDC_ONLY=false # Set to true to disable local password auth entirely (SSO only)
|
# - OIDC_ONLY=false # Set to true to disable local password auth entirely (SSO only)
|
||||||
# - OIDC_ADMIN_CLAIM=groups # OIDC claim used to identify admin users
|
# - OIDC_ADMIN_CLAIM=groups # OIDC claim used to identify admin users
|
||||||
# - OIDC_ADMIN_VALUE=app-trek-admins # Value of the OIDC claim that grants admin role
|
# - OIDC_ADMIN_VALUE=app-trek-admins # Value of the OIDC claim that grants admin role
|
||||||
|
# - OIDC_SCOPE=openid email profile groups # Space-separated OIDC scopes to request (must include scopes for any claim used by OIDC_ADMIN_CLAIM)
|
||||||
# - OIDC_DISCOVERY_URL= # Override the OIDC discovery endpoint for providers with non-standard paths (e.g. Authentik)
|
# - OIDC_DISCOVERY_URL= # Override the OIDC discovery endpoint for providers with non-standard paths (e.g. Authentik)
|
||||||
# - DEMO_MODE=false # Enable demo mode (resets data hourly)
|
# - DEMO_MODE=false # Enable demo mode (resets data hourly)
|
||||||
|
# - ADMIN_EMAIL=admin@trek.local # Initial admin e-mail — only used on first boot when no users exist
|
||||||
|
# - ADMIN_PASSWORD=changeme # Initial admin password — only used on first boot when no users exist
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/app/data
|
- ./data:/app/data
|
||||||
- ./uploads:/app/uploads
|
- ./uploads:/app/uploads
|
||||||
@@ -287,7 +290,13 @@ trek.yourdomain.com {
|
|||||||
| `OIDC_CLIENT_SECRET` | OIDC client secret | — |
|
| `OIDC_CLIENT_SECRET` | OIDC client secret | — |
|
||||||
| `OIDC_DISPLAY_NAME` | Label shown on the SSO login button | `SSO` |
|
| `OIDC_DISPLAY_NAME` | Label shown on the SSO login button | `SSO` |
|
||||||
| `OIDC_ONLY` | Disable local password auth entirely (first SSO login becomes admin) | `false` |
|
| `OIDC_ONLY` | Disable local password auth entirely (first SSO login becomes admin) | `false` |
|
||||||
|
| `OIDC_ADMIN_CLAIM` | OIDC claim used to identify admin users | — |
|
||||||
|
| `OIDC_ADMIN_VALUE` | Value of the OIDC claim that grants admin role | — |
|
||||||
|
| `OIDC_SCOPE` | Space-separated OIDC scopes to request. Must include scopes for any claim used by `OIDC_ADMIN_CLAIM` (e.g. add `groups` for group-based admin mapping) | `openid email profile groups` |
|
||||||
| `OIDC_DISCOVERY_URL` | Override the auto-constructed OIDC discovery endpoint. Useful for providers that expose it at a non-standard path (e.g. Authentik: `https://auth.example.com/application/o/trek/.well-known/openid-configuration`) | — |
|
| `OIDC_DISCOVERY_URL` | Override the auto-constructed OIDC discovery endpoint. Useful for providers that expose it at a non-standard path (e.g. Authentik: `https://auth.example.com/application/o/trek/.well-known/openid-configuration`) | — |
|
||||||
|
| **Initial Setup** | | |
|
||||||
|
| `ADMIN_EMAIL` | Email for the first admin account created on initial boot. Must be set together with `ADMIN_PASSWORD`. If either is omitted a random password is generated and printed to the server log. Has no effect once any user exists. | `admin@trek.local` |
|
||||||
|
| `ADMIN_PASSWORD` | Password for the first admin account created on initial boot. Must be set together with `ADMIN_EMAIL`. | random |
|
||||||
| **Other** | | |
|
| **Other** | | |
|
||||||
| `DEMO_MODE` | Enable demo mode (hourly data resets) | `false` |
|
| `DEMO_MODE` | Enable demo mode (hourly data resets) | `false` |
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,18 @@ spec:
|
|||||||
name: {{ default (printf "%s-secret" (include "trek.fullname" .)) .Values.existingSecret }}
|
name: {{ default (printf "%s-secret" (include "trek.fullname" .)) .Values.existingSecret }}
|
||||||
key: {{ .Values.existingSecretKey | default "ENCRYPTION_KEY" }}
|
key: {{ .Values.existingSecretKey | default "ENCRYPTION_KEY" }}
|
||||||
optional: true
|
optional: true
|
||||||
|
- name: ADMIN_EMAIL
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ default (printf "%s-secret" (include "trek.fullname" .)) .Values.existingSecret }}
|
||||||
|
key: ADMIN_EMAIL
|
||||||
|
optional: true
|
||||||
|
- name: ADMIN_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ default (printf "%s-secret" (include "trek.fullname" .)) .Values.existingSecret }}
|
||||||
|
key: ADMIN_PASSWORD
|
||||||
|
optional: true
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: data
|
- name: data
|
||||||
mountPath: /app/data
|
mountPath: /app/data
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ metadata:
|
|||||||
type: Opaque
|
type: Opaque
|
||||||
data:
|
data:
|
||||||
{{ .Values.existingSecretKey | default "ENCRYPTION_KEY" }}: {{ .Values.secretEnv.ENCRYPTION_KEY | b64enc | quote }}
|
{{ .Values.existingSecretKey | default "ENCRYPTION_KEY" }}: {{ .Values.secretEnv.ENCRYPTION_KEY | b64enc | quote }}
|
||||||
|
{{- if .Values.secretEnv.ADMIN_EMAIL }}
|
||||||
|
ADMIN_EMAIL: {{ .Values.secretEnv.ADMIN_EMAIL | b64enc | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.secretEnv.ADMIN_PASSWORD }}
|
||||||
|
ADMIN_PASSWORD: {{ .Values.secretEnv.ADMIN_PASSWORD | b64enc | quote }}
|
||||||
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{- if and (not .Values.existingSecret) (.Values.generateEncryptionKey) }}
|
{{- if and (not .Values.existingSecret) (.Values.generateEncryptionKey) }}
|
||||||
@@ -26,4 +32,10 @@ stringData:
|
|||||||
{{- else }}
|
{{- else }}
|
||||||
{{ .Values.existingSecretKey | default "ENCRYPTION_KEY" }}: {{ randAlphaNum 32 }}
|
{{ .Values.existingSecretKey | default "ENCRYPTION_KEY" }}: {{ randAlphaNum 32 }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- if .Values.secretEnv.ADMIN_EMAIL }}
|
||||||
|
ADMIN_EMAIL: {{ .Values.secretEnv.ADMIN_EMAIL }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.secretEnv.ADMIN_PASSWORD }}
|
||||||
|
ADMIN_PASSWORD: {{ .Values.secretEnv.ADMIN_PASSWORD }}
|
||||||
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ env:
|
|||||||
# Set to "false" to allow session cookies over plain HTTP (e.g. no ingress TLS). Not recommended for production.
|
# Set to "false" to allow session cookies over plain HTTP (e.g. no ingress TLS). Not recommended for production.
|
||||||
# OIDC_DISCOVERY_URL: ""
|
# OIDC_DISCOVERY_URL: ""
|
||||||
# Override the OIDC discovery endpoint for providers with non-standard paths (e.g. Authentik).
|
# Override the OIDC discovery endpoint for providers with non-standard paths (e.g. Authentik).
|
||||||
|
# OIDC_SCOPE: "openid email profile groups"
|
||||||
|
# Space-separated OIDC scopes to request. Must include scopes for any claim used by OIDC_ADMIN_CLAIM.
|
||||||
|
|
||||||
|
|
||||||
# Secret environment variables stored in a Kubernetes Secret.
|
# Secret environment variables stored in a Kubernetes Secret.
|
||||||
@@ -36,6 +38,11 @@ secretEnv:
|
|||||||
# 1. data/.jwt_secret (existing installs — encrypted data stays readable after upgrade)
|
# 1. data/.jwt_secret (existing installs — encrypted data stays readable after upgrade)
|
||||||
# 2. data/.encryption_key auto-generated on first start (fresh installs)
|
# 2. data/.encryption_key auto-generated on first start (fresh installs)
|
||||||
ENCRYPTION_KEY: ""
|
ENCRYPTION_KEY: ""
|
||||||
|
# Initial admin account — only used on first boot when no users exist yet.
|
||||||
|
# If both values are non-empty the admin account is created with these credentials.
|
||||||
|
# If either is empty a random password is generated and printed to the server log.
|
||||||
|
ADMIN_EMAIL: ""
|
||||||
|
ADMIN_PASSWORD: ""
|
||||||
|
|
||||||
# If true, a random ENCRYPTION_KEY is generated at install and preserved across upgrades
|
# If true, a random ENCRYPTION_KEY is generated at install and preserved across upgrades
|
||||||
generateEncryptionKey: false
|
generateEncryptionKey: false
|
||||||
|
|||||||
@@ -79,7 +79,9 @@ export default function App() {
|
|||||||
const { loadSettings } = useSettingsStore()
|
const { loadSettings } = useSettingsStore()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadUser()
|
if (!location.pathname.startsWith('/shared/')) {
|
||||||
|
loadUser()
|
||||||
|
}
|
||||||
authApi.getAppConfig().then(async (config: { demo_mode?: boolean; has_maps_key?: boolean; version?: string; timezone?: string; require_mfa?: boolean; trip_reminders_enabled?: boolean; permissions?: Record<string, PermissionLevel> }) => {
|
authApi.getAppConfig().then(async (config: { demo_mode?: boolean; has_maps_key?: boolean; version?: string; timezone?: string; require_mfa?: boolean; trip_reminders_enabled?: boolean; permissions?: Record<string, PermissionLevel> }) => {
|
||||||
if (config?.demo_mode) setDemoMode(true)
|
if (config?.demo_mode) setDemoMode(true)
|
||||||
if (config?.has_maps_key !== undefined) setHasMapsKey(config.has_maps_key)
|
if (config?.has_maps_key !== undefined) setHasMapsKey(config.has_maps_key)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ apiClient.interceptors.response.use(
|
|||||||
(response) => response,
|
(response) => response,
|
||||||
(error) => {
|
(error) => {
|
||||||
if (error.response?.status === 401 && (error.response?.data as { code?: string } | undefined)?.code === 'AUTH_REQUIRED') {
|
if (error.response?.status === 401 && (error.response?.data as { code?: string } | undefined)?.code === 'AUTH_REQUIRED') {
|
||||||
if (!window.location.pathname.includes('/login') && !window.location.pathname.includes('/register')) {
|
if (!window.location.pathname.includes('/login') && !window.location.pathname.includes('/register') && !window.location.pathname.startsWith('/shared/')) {
|
||||||
window.location.href = '/login'
|
window.location.href = '/login'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ export default function SettingsPage(): React.ReactElement {
|
|||||||
setSaving(s => ({ ...s, immich: true }))
|
setSaving(s => ({ ...s, immich: true }))
|
||||||
try {
|
try {
|
||||||
const saveRes = await apiClient.put('/integrations/immich/settings', { immich_url: immichUrl, immich_api_key: immichApiKey || undefined })
|
const saveRes = await apiClient.put('/integrations/immich/settings', { immich_url: immichUrl, immich_api_key: immichApiKey || undefined })
|
||||||
if (saveRes.data.warning) toast.warn(saveRes.data.warning)
|
if (saveRes.data.warning) toast.warning(saveRes.data.warning)
|
||||||
toast.success(t('memories.saved'))
|
toast.success(t('memories.saved'))
|
||||||
const res = await apiClient.get('/integrations/immich/status')
|
const res = await apiClient.get('/integrations/immich/status')
|
||||||
setImmichConnected(res.data.connected)
|
setImmichConnected(res.data.connected)
|
||||||
|
|||||||
@@ -31,7 +31,12 @@ services:
|
|||||||
# - OIDC_CLIENT_SECRET=supersecret # OpenID Connect client secret
|
# - OIDC_CLIENT_SECRET=supersecret # OpenID Connect client secret
|
||||||
# - OIDC_DISPLAY_NAME=SSO # Label shown on the SSO login button
|
# - OIDC_DISPLAY_NAME=SSO # Label shown on the SSO login button
|
||||||
# - OIDC_ONLY=false # Set true to disable local password auth entirely (SSO only)
|
# - OIDC_ONLY=false # Set true to disable local password auth entirely (SSO only)
|
||||||
|
# - OIDC_ADMIN_CLAIM=groups # OIDC claim used to identify admin users
|
||||||
|
# - OIDC_ADMIN_VALUE=app-trek-admins # Value of the OIDC claim that grants admin role
|
||||||
|
# - OIDC_SCOPE=openid email profile groups # Space-separated OIDC scopes to request (must include scopes for any claim used by OIDC_ADMIN_CLAIM)
|
||||||
# - OIDC_DISCOVERY_URL= # Override the OIDC discovery endpoint for providers with non-standard paths (e.g. Authentik)
|
# - OIDC_DISCOVERY_URL= # Override the OIDC discovery endpoint for providers with non-standard paths (e.g. Authentik)
|
||||||
|
# - ADMIN_EMAIL=admin@trek.local # Initial admin e-mail — only used on first boot when no users exist
|
||||||
|
# - ADMIN_PASSWORD=changeme # Initial admin password — only used on first boot when no users exist
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/app/data
|
- ./data:/app/data
|
||||||
- ./uploads:/app/uploads
|
- ./uploads:/app/uploads
|
||||||
|
|||||||
@@ -24,5 +24,12 @@ OIDC_ONLY=true # Disable local password auth entirely (SSO only)
|
|||||||
OIDC_ADMIN_CLAIM=groups # OIDC claim used to identify admin users
|
OIDC_ADMIN_CLAIM=groups # OIDC claim used to identify admin users
|
||||||
OIDC_ADMIN_VALUE=app-trek-admins # Value of the OIDC claim that grants admin role
|
OIDC_ADMIN_VALUE=app-trek-admins # Value of the OIDC claim that grants admin role
|
||||||
OIDC_DISCOVERY_URL= # Override the auto-constructed OIDC discovery endpoint. Useful for providers (e.g. Authentik) that expose it at a non-standard path. Example: https://auth.example.com/application/o/trek/.well-known/openid-configuration
|
OIDC_DISCOVERY_URL= # Override the auto-constructed OIDC discovery endpoint. Useful for providers (e.g. Authentik) that expose it at a non-standard path. Example: https://auth.example.com/application/o/trek/.well-known/openid-configuration
|
||||||
|
OIDC_SCOPE=openid email profile groups # Space-separated OIDC scopes to request (must include scopes for any claim used by OIDC_ADMIN_CLAIM)
|
||||||
|
|
||||||
DEMO_MODE=false # Demo mode - resets data hourly
|
DEMO_MODE=false # Demo mode - resets data hourly
|
||||||
|
|
||||||
|
# Initial admin account — only used on first boot when no users exist yet.
|
||||||
|
# If both are set the admin account is created with these credentials.
|
||||||
|
# If either is omitted a random password is generated and printed to the server log.
|
||||||
|
# ADMIN_EMAIL=admin@trek.local
|
||||||
|
# ADMIN_PASSWORD=changeme
|
||||||
|
|||||||
@@ -22,9 +22,21 @@ function seedAdminAccount(db: Database.Database): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const bcrypt = require('bcryptjs');
|
const bcrypt = require('bcryptjs');
|
||||||
const password = crypto.randomBytes(12).toString('base64url');
|
|
||||||
|
const env_admin_email = process.env.ADMIN_EMAIL;
|
||||||
|
const env_admin_pw = process.env.ADMIN_PASSWORD;
|
||||||
|
|
||||||
|
let password;
|
||||||
|
let email;
|
||||||
|
if (env_admin_email && env_admin_pw) {
|
||||||
|
password = env_admin_pw;
|
||||||
|
email = env_admin_email;
|
||||||
|
} else {
|
||||||
|
password = crypto.randomBytes(12).toString('base64url');
|
||||||
|
email = 'admin@trek.local';
|
||||||
|
}
|
||||||
|
|
||||||
const hash = bcrypt.hashSync(password, 12);
|
const hash = bcrypt.hashSync(password, 12);
|
||||||
const email = 'admin@trek.local';
|
|
||||||
const username = 'admin';
|
const username = 'admin';
|
||||||
|
|
||||||
db.prepare('INSERT INTO users (username, email, password_hash, role, must_change_password) VALUES (?, ?, ?, ?, 1)').run(username, email, hash, 'admin');
|
db.prepare('INSERT INTO users (username, email, password_hash, role, must_change_password) VALUES (?, ?, ?, ?, 1)').run(username, email, hash, 'admin');
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ app.use(helmet({
|
|||||||
"https://*.basemaps.cartocdn.com", "https://*.tile.openstreetmap.org",
|
"https://*.basemaps.cartocdn.com", "https://*.tile.openstreetmap.org",
|
||||||
"https://unpkg.com", "https://open-meteo.com", "https://api.open-meteo.com",
|
"https://unpkg.com", "https://open-meteo.com", "https://api.open-meteo.com",
|
||||||
"https://geocoding-api.open-meteo.com", "https://api.exchangerate-api.com",
|
"https://geocoding-api.open-meteo.com", "https://api.exchangerate-api.com",
|
||||||
|
"https://raw.githubusercontent.com/nvkelso/natural-earth-vector/master/geojson/ne_50m_admin_0_countries.geojson"
|
||||||
],
|
],
|
||||||
fontSrc: ["'self'", "https://fonts.gstatic.com", "data:"],
|
fontSrc: ["'self'", "https://fonts.gstatic.com", "data:"],
|
||||||
objectSrc: ["'none'"],
|
objectSrc: ["'none'"],
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ router.get('/login', async (req: Request, res: Response) => {
|
|||||||
response_type: 'code',
|
response_type: 'code',
|
||||||
client_id: config.clientId,
|
client_id: config.clientId,
|
||||||
redirect_uri: redirectUri,
|
redirect_uri: redirectUri,
|
||||||
scope: 'openid email profile',
|
scope: process.env.OIDC_SCOPE || 'openid email profile groups',
|
||||||
state,
|
state,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user