feat(auth): implement TOTP two-factor authentication with enrollment and verification

Adds TOTP (Time-based One-Time Password) two-factor authentication support including:
- New TOTP service with secret generation, QR code provisioning, and code verification
- New auth endpoints for enrollment, verification, status, and backup code management
- New TOTP authentication method type and user methods for TOTP management
- Backup codes generation and verification for account recovery
- Updated OIDC endpoints with timezone-aware datetime handling and RFC-compliant responses
- Added "roles" scope support for OIDC userinfo and ID tokens
- New pyotp dependency for TOTP operations
- Comprehensive unit tests for TOTP service
This commit is contained in:
2026-01-14 18:06:17 +10:30
parent 977abf66df
commit cfd79190ee
26 changed files with 2176 additions and 263 deletions
+8
View File
@@ -0,0 +1,8 @@
<!doctype html>
<html>
<body>
<h1>OK - protected</h1>
<p>User: __USER__</p>
<p>Email: __EMAIL__</p>
</body>
</html>
+92
View File
@@ -0,0 +1,92 @@
version: "3.9"
services:
nginx:
image: nginx:1.27-alpine
container_name: app-nginx
volumes:
- ./app:/usr/share/nginx/html:ro
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
expose:
- "80"
# oauth2-proxy:
# image: quay.io/oauth2-proxy/oauth2-proxy:v7.7.1
# container_name: oauth2-proxy
# depends_on:
# - nginx
# ports:
# - "8086:4180"
# environment:
# # ----- Logging -----
# OAUTH2_PROXY_LOG_LEVEL: "debug"
# OAUTH2_PROXY_AUTH_LOGGING: "true"
# OAUTH2_PROXY_REQUEST_LOGGING: "true"
# OAUTH2_PROXY_STANDARD_LOGGING: "true"
# # ----- Gatehouse OIDC -----
# OAUTH2_PROXY_PROVIDER: oidc
# OAUTH2_PROXY_OIDC_ISSUER_URL: "http://192.168.64.7:8888"
# OAUTH2_PROXY_CLIENT_ID: "acme-portal-001"
# OAUTH2_PROXY_CLIENT_SECRET: "acme_secret_portal_2024"
# OAUTH2_PROXY_REDIRECT_URL: "http://192.168.64.7:8086/oauth2/callback"
# # ----- Session -----
# OAUTH2_PROXY_COOKIE_SECRET: "afhXcfftf5qKezJ217qhED7U4UeVyqBHd7lhISNGpXo="
# OAUTH2_PROXY_COOKIE_SECURE: "false"
# OAUTH2_PROXY_COOKIE_SAMESITE: "lax"
# OAUTH2_PROXY_EMAIL_DOMAINS: "*"
# OAUTH2_PROXY_HTTP_ADDRESS: "0.0.0.0:4180"
# # ----- Upstream -----
# OAUTH2_PROXY_UPSTREAMS: "http://nginx:80"
# # ----- Identity headers -----
# OAUTH2_PROXY_SET_XAUTHREQUEST: "true"
# OAUTH2_PROXY_PASS_ACCESS_TOKEN: "true"
# OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER: "true"
# # ----- Claim mapping (Gatehouse) -----
# OAUTH2_PROXY_OIDC_EMAIL_CLAIM: "email"
# OAUTH2_PROXY_USER_ID_CLAIM: "preferred_username"
# OAUTH2_PROXY_OIDC_GROUPS_CLAIM: "roles"
# OAUTH2_PROXY_SKIP_PROVIDER_BUTTON: "true"
# OAUTH2_PROXY_SCOPE: "openid email profile"
# # OAUTH2_PROXY_OIDC_EMAIL_CLAIM: "email"
# OAUTH2_PROXY_INSECURE_OIDC_ALLOW_UNVERIFIED_EMAIL: "true"
oauth2-proxy:
image: quay.io/oauth2-proxy/oauth2-proxy:v7.7.1
container_name: oauth2-proxy
depends_on: [nginx]
ports:
- "8086:4180"
command:
- --provider=oidc
- --oidc-issuer-url=http://192.168.64.7:8888
- --client-id=acme-portal-001
- --client-secret=acme_secret_portal_2024
- --redirect-url=http://192.168.64.7:8086/oauth2/callback
- --scope=openid email profile
- --email-domain=*
- --upstream=http://nginx:80
- --http-address=0.0.0.0:4180
- --cookie-secret=afhXcfftf5qKezJ217qhED7U4UeVyqBHd7lhISNGpXo=
- --cookie-secure=false
- --cookie-samesite=lax
- --skip-provider-button=true
- --standard-logging=true
- --request-logging=true
- --auth-logging=true
- --set-xauthrequest=true
- --pass-authorization-header=true # optional (token passthrough)
- --pass-access-token=true # optional (token passthrough)
- --pass-user-headers=true
+23
View File
@@ -0,0 +1,23 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
location = /whoami {
default_type text/plain;
return 200
"user: $http_x_auth_request_user
email: $http_x_auth_request_email
preferred_username: $http_x_forwarded_preferred_username
x-forwarded-user: $http_x_forwarded_user
x-forwarded-email: $http_x_forwarded_email
authorization: $http_authorization
";
}
}