Files
pl_mail/compose.yaml
T
b453b79eb4 Single sign-on only: hide the password form and refuse password sign-ins (#210)
* Single sign-on only: hide the password form and refuse password sign-ins

Adds Admin -> SSO 'Single sign-on only' (and OIDC_DISABLE_PASSWORD_LOGIN).
The login page drops the password form and LoginFormAuthenticator refuses a
password sign-in before looking the user up. It only applies while a whole
provider is in force, the saving administrator must already be linked to the
provider, and app:sso:allow-password-login brings the form back from the
console. Config restore leaves it off. Off by default; one boolean column on
oidc_config.

Closes #169

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Single sign-on only: require a link to the provider being saved

The admin form let the box be ticked by an administrator linked to any
provider, including the one the page held before. Switching providers and
ticking it in one save made a provider they had never used the only way in.
It now compares the stored issuer with the one on the page, without a
trailing slash, as OidcDiscovery does.

Tests for what had none: the console command (clears the setting, keeps the
provider), a restore over a row that had it on, the link to another
provider, and a control showing the same password signs in with the setting
off. The "right credentials" test now posts the right password.

Docs: there is no iOS app.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Docs: put the iOS app back in the single sign-on page

The previous commit dropped it on the wrong assumption that there is no
iOS app. There is one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Paul Lützner <karatektus@gmail.com>
2026-10-19 13:09:12 +02:00

163 lines
4.7 KiB
YAML

# plMail on the published image: web, worker and Postgres.
# Secrets are generated on first start; see docs/install/docker.md.
x-app-image: &app-image ghcr.io/karatektus/pl_mail:latest
x-app-env: &app-env
APP_ENV: prod
APP_SECRET: ${APP_SECRET:-}
APP_ENCRYPTION_KEY: ${APP_ENCRYPTION_KEY:-}
DATABASE_URL: ${DATABASE_URL:-}
POSTGRES_USER: ${POSTGRES_USER:-app}
POSTGRES_DB: ${POSTGRES_DB:-app}
POSTGRES_VERSION: ${POSTGRES_VERSION:-18}
MESSENGER_TRANSPORT_DSN: ${MESSENGER_TRANSPORT_DSN:-doctrine://default?auto_setup=0}
MERCURE_URL: ${MERCURE_URL:-http://mercure/.well-known/mercure}
MERCURE_PUBLIC_URL: ${MERCURE_PUBLIC_URL:-https://localhost/.well-known/mercure}
MERCURE_COOKIE_NAME: ${MERCURE_COOKIE_NAME:-}
OIDC_ISSUER: ${OIDC_ISSUER:-}
OIDC_CLIENT_ID: ${OIDC_CLIENT_ID:-}
OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET:-}
OIDC_BUTTON_LABEL: ${OIDC_BUTTON_LABEL:-}
OIDC_SCOPES: ${OIDC_SCOPES:-openid email profile}
OIDC_AUTO_CREATE: ${OIDC_AUTO_CREATE:-false}
OIDC_REQUIRE_VERIFIED_EMAIL: ${OIDC_REQUIRE_VERIFIED_EMAIL:-true}
OIDC_DISABLE_PASSWORD_LOGIN: ${OIDC_DISABLE_PASSWORD_LOGIN:-false}
x-app-storage: &app-storage
- app_secrets:/app/var/secrets
- app_attachments:/app/var/attachments
- app_raw:/app/var/raw
- app_uploads:/app/var/uploads
services:
secrets-init:
image: *app-image
restart: "no"
entrypoint: ["generate-secrets"]
environment:
APP_SECRET: ${APP_SECRET:-}
APP_ENCRYPTION_KEY: ${APP_ENCRYPTION_KEY:-}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-}
MERCURE_JWT_SECRET: ${MERCURE_JWT_SECRET:-}
volumes:
- app_secrets:/app/var/secrets
php:
image: *app-image
restart: unless-stopped
environment:
<<: *app-env
SERVER_NAME: ${SERVER_NAME:-localhost, :443}, php:80
MAILER_DSN: ${MAILER_DSN:-null://null}
APP_CONTAINER_NAME: web
volumes:
- caddy_data:/data
- caddy_config:/config
- app_secrets:/app/var/secrets
- app_attachments:/app/var/attachments
- app_raw:/app/var/raw
- app_uploads:/app/var/uploads
ports:
- target: 80
published: ${HTTP_PORT:-80}
protocol: tcp
- target: 443
published: ${HTTPS_PORT:-443}
protocol: tcp
- target: 443
published: ${HTTP3_PORT:-443}
protocol: udp
depends_on:
secrets-init:
condition: service_completed_successfully
database:
condition: service_healthy
worker:
image: *app-image
restart: unless-stopped
command: php bin/console app:work
init: true
stop_grace_period: 30s
environment:
<<: *app-env
APP_CONTAINER_NAME: worker
networks:
default:
aliases: [mercure]
volumes:
- mercure_data:/data
- mercure_config:/config
- app_secrets:/app/var/secrets
- app_attachments:/app/var/attachments
- app_raw:/app/var/raw
- app_uploads:/app/var/uploads
healthcheck:
disable: true
depends_on:
secrets-init:
condition: service_completed_successfully
database:
condition: service_healthy
database:
image: postgres:${POSTGRES_VERSION:-18}-alpine
environment:
POSTGRES_DB: ${POSTGRES_DB:-app}
POSTGRES_USER: ${POSTGRES_USER:-app}
POSTGRES_PASSWORD_FILE: /run/plmail/postgres_password
depends_on:
secrets-init:
condition: service_completed_successfully
healthcheck:
test: ["CMD", "pg_isready", "-d", "${POSTGRES_DB:-app}", "-U", "${POSTGRES_USER:-app}"]
interval: 5s
timeout: 5s
retries: 30
start_period: 60s
volumes:
- database_data:/var/lib/postgresql
- app_secrets:/run/plmail:ro
command:
- "postgres"
- "-c"
- "shared_preload_libraries=pg_stat_statements"
- "-c"
- "pg_stat_statements.track=top"
ntfy:
image: binwiederhier/ntfy
profiles: [push]
restart: unless-stopped
command: serve
environment:
NTFY_BASE_URL: ${NTFY_BASE_URL:-http://${SERVER_NAME:-localhost}:${NTFY_PORT:-8090}}
NTFY_LISTEN_HTTP: ':80'
NTFY_CACHE_FILE: /var/lib/ntfy/cache.db
NTFY_AUTH_FILE: /var/lib/ntfy/auth.db
NTFY_AUTH_DEFAULT_ACCESS: ${NTFY_AUTH_DEFAULT_ACCESS:-read-write}
NTFY_VISITOR_REQUEST_LIMIT_BURST: '60'
NTFY_VISITOR_REQUEST_LIMIT_REPLENISH: '5s'
ports:
- "${NTFY_PORT:-8090}:80"
volumes:
- ntfy_data:/var/lib/ntfy
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost/v1/health"]
timeout: 5s
retries: 5
start_period: 20s
volumes:
app_secrets:
app_attachments:
app_raw:
app_uploads:
caddy_data:
caddy_config:
database_data:
mercure_data:
mercure_config:
ntfy_data: