Backend:
- New AUTH_PASSWORD_ENABLED flag (default true). When false, email/password
registration and login return 403; the server enforces OIDC-only login.
- New OIDC_GENERIC_DISPLAY_NAME so the app can show 'Authentik'/'Keycloak'
instead of a generic 'OIDC' label.
- New public endpoint GET /api/config returns which auth methods the
server offers (password_enabled + per-provider OIDC capabilities).
No auth required, so the login screen can query it before logging in.
- .env.example and docker-compose.yml expose the new env vars.
App:
- DTOs + MitbringslApi.getServerConfig() for /api/config.
- AuthViewModel: new 'connect' flow. The user enters the server URL,
taps 'Verbinden', and the app fetches /api/config. The returned
ServerAuthConfig drives which login options are shown:
* password-only -> email/password form
* OIDC-only -> OIDC token form
* both -> toggle between the two
If the server offers no method, a clear error is shown.
- AuthScreen: split into ConnectView (server URL) and LoginView (the
login form matching the server's capabilities). The mode toggle only
appears when the server offers more than one method.
101 lines
3.8 KiB
Go
101 lines
3.8 KiB
Go
// Package config holds all runtime configuration for the backend.
|
|
// Values are parsed from environment variables via struct tags.
|
|
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/caarlos0/env/v11"
|
|
)
|
|
|
|
// Config is the single source of runtime configuration.
|
|
type Config struct {
|
|
// HTTPAddr is the address the HTTP server listens on, e.g. ":8080".
|
|
HTTPAddr string `env:"HTTP_ADDR" envDefault:":8080"`
|
|
|
|
// AppEnv: "development" or "production".
|
|
AppEnv string `env:"APP_ENV" envDefault:"development"`
|
|
|
|
// LogLevel: debug | info | warn | error.
|
|
LogLevel string `env:"LOG_LEVEL" envDefault:"info"`
|
|
|
|
// PublicBaseURL is the externally reachable URL (scheme + host), no trailing slash.
|
|
// Used e.g. for OIDC redirect URIs. Example: "https://mitbringsl.example.com".
|
|
PublicBaseURL string `env:"PUBLIC_BASE_URL" envDefault:"http://localhost:8080"`
|
|
|
|
// Database
|
|
DatabaseURL string `env:"DATABASE_URL,required"`
|
|
DBMaxOpenConns int32 `env:"DB_MAX_OPEN_CONNS" envDefault:"25"`
|
|
DBMaxIdleConns int32 `env:"DB_MAX_IDLE_CONNS" envDefault:"5"`
|
|
DBMaxLifetime time.Duration `env:"DB_MAX_CONN_LIFETIME" envDefault:"30m"`
|
|
|
|
// Session token config (opaque tokens issued by this backend after login).
|
|
SessionTokenBytes int `env:"SESSION_TOKEN_BYTES" envDefault:"32"` // 256-bit
|
|
SessionTokenTTL time.Duration `env:"SESSION_TOKEN_TTL" envDefault:"720h"` // 30 days
|
|
SessionCookieName string `env:"SESSION_COOKIE_NAME" envDefault:"mitbringsl_session"`
|
|
|
|
// AuthPasswordEnabled controls whether email/password registration and
|
|
// login are offered. Set to false to enforce OIDC-only login.
|
|
AuthPasswordEnabled bool `env:"AUTH_PASSWORD_ENABLED" envDefault:"true"`
|
|
|
|
// OIDC providers. Both optional; enable per provider.
|
|
GoogleOIDC GoogleOIDCConfig
|
|
GenericOIDC GenericOIDCConfig
|
|
|
|
// CORS allowed origins (comma-separated). Empty = no CORS headers.
|
|
CORSAllowedOrigins []string `env:"CORS_ALLOWED_ORIGINS" envSeparator:","`
|
|
}
|
|
|
|
// GoogleOIDCConfig for "Sign in with Google".
|
|
type GoogleOIDCConfig struct {
|
|
Enabled bool `env:"OIDC_GOOGLE_ENABLED" envDefault:"false"`
|
|
ClientID string `env:"OIDC_GOOGLE_CLIENT_ID"` // the audience the verifier accepts
|
|
Issuer string `env:"OIDC_GOOGLE_ISSUER" envDefault:"https://accounts.google.com"`
|
|
}
|
|
|
|
// GenericOIDCConfig for any standards-compliant OIDC IdP (Keycloak, Authentik, ...).
|
|
type GenericOIDCConfig struct {
|
|
Enabled bool `env:"OIDC_GENERIC_ENABLED" envDefault:"false"`
|
|
Issuer string `env:"OIDC_GENERIC_ISSUER"`
|
|
ClientID string `env:"OIDC_GENERIC_CLIENT_ID"`
|
|
// DisplayName is shown to users in the app, e.g. "Authentik" or "Keycloak".
|
|
// Defaults to "OIDC" when empty.
|
|
DisplayName string `env:"OIDC_GENERIC_DISPLAY_NAME" envDefault:"OIDC"`
|
|
}
|
|
|
|
// Load reads configuration from environment variables and validates basic invariants.
|
|
func Load() (*Config, error) {
|
|
var cfg Config
|
|
if err := env.Parse(&cfg); err != nil {
|
|
return nil, fmt.Errorf("parse env: %w", err)
|
|
}
|
|
if err := cfg.validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
return &cfg, nil
|
|
}
|
|
|
|
// validate enforces invariants that env parsing alone can't express.
|
|
func (c *Config) validate() error {
|
|
if c.GoogleOIDC.Enabled {
|
|
if c.GoogleOIDC.ClientID == "" {
|
|
return fmt.Errorf("OIDC_GOOGLE_ENABLED=true requires OIDC_GOOGLE_CLIENT_ID")
|
|
}
|
|
if c.GoogleOIDC.Issuer == "" {
|
|
return fmt.Errorf("OIDC_GOOGLE_ENABLED=true requires OIDC_GOOGLE_ISSUER")
|
|
}
|
|
}
|
|
if c.GenericOIDC.Enabled {
|
|
if c.GenericOIDC.ClientID == "" {
|
|
return fmt.Errorf("OIDC_GENERIC_ENABLED=true requires OIDC_GENERIC_CLIENT_ID")
|
|
}
|
|
if c.GenericOIDC.Issuer == "" {
|
|
return fmt.Errorf("OIDC_GENERIC_ENABLED=true requires OIDC_GENERIC_ISSUER")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// IsProduction reports whether the app runs in production mode.
|
|
func (c *Config) IsProduction() bool { return c.AppEnv == "production" }
|