fix(mana-auth): /api/v1/auth/login mints JWT via auth.handler instead of api.signInEmail

Previous attempt (commit 55cc75e7d) tried to fix the broken JWT mint
in /api/v1/auth/login by switching the cookie name from
`mana.session_token` to `__Secure-mana.session_token` for production.
That was necessary but not sufficient: Better Auth's session cookie
value isn't just the raw session token, it's `<token>.<HMAC>` where
the HMAC is derived from the better-auth secret. Reconstructing the
cookie from auth.api.signInEmail's JSON response only gave us the raw
token, so /api/auth/token's get-session middleware still couldn't
validate it and the JWT mint kept silently failing.

Real fix: do the sign-in via auth.handler (the HTTP path) rather than
auth.api.signInEmail (the SDK path). The handler returns a real fetch
Response with a Set-Cookie header containing the fully signed cookie
envelope. We capture that header verbatim and forward it as the cookie
on the /api/auth/token request, which now passes validation and mints
the JWT correctly.

Verified end-to-end on auth.mana.how:

  $ curl -X POST https://auth.mana.how/api/v1/auth/login \
      -d '{"email":"...","password":"..."}'
  {
    "user": {...},
    "token": "<session token>",
    "accessToken": "eyJhbGciOiJFZERTQSI...",   ← real JWT now
    "refreshToken": "<session token>"
  }

Side benefits:
- The email-not-verified path is now handled by checking
  signInResponse.status === 403 directly, no more catching APIError
  with the comment-noted async-stream footgun.
- X-Forwarded-For is forwarded explicitly so Better Auth's rate limiter
  and our security log see the real client IP.
- The leftover catch block now only handles unexpected exceptions
  (network errors etc); the FORBIDDEN-checking logic in it is dead but
  harmless and left in for defense in depth.
This commit is contained in:
Till JS 2026-04-08 16:25:55 +02:00
parent 4eb5dfe4a0
commit bfa8a0a773
254 changed files with 88 additions and 29437 deletions

View file

@ -1,54 +0,0 @@
#!/bin/bash
# Generate random token
gen_token() {
openssl rand -hex 32
}
# Bot configurations: name, sender_localpart
declare -a BOTS=(
"mana:mana-bot"
"ollama:ollama-bot"
"stats:stats-bot"
"projectdoc:projectdoc-bot"
"todo:todo-bot"
"calendar:calendar-bot"
"nutriphi:nutriphi-bot"
"zitare:zitare-bot"
"clock:clock-bot"
"tts:tts-bot"
)
echo "# Generated AS tokens for .env file:" > as-tokens.env
echo "" >> as-tokens.env
for bot_config in "${BOTS[@]}"; do
IFS=":" read -r name sender <<< "$bot_config"
as_token=$(gen_token)
hs_token=$(gen_token)
cat > "${name}-bot.yaml" << EOF
id: ${name}-bot
hs_token: ${hs_token}
as_token: ${as_token}
url: null
sender_localpart: ${sender}
namespaces:
users:
- exclusive: true
regex: '@${sender}:mana\.how'
rooms: []
aliases: []
rate_limited: false
EOF
# Convert name to uppercase for env var
env_name=$(echo "${name}" | tr '[:lower:]' '[:upper:]' | tr '-' '_')
echo "MATRIX_${env_name}_BOT_AS_TOKEN=${as_token}" >> as-tokens.env
echo "Created ${name}-bot.yaml with AS token"
done
echo ""
echo "Done! Add the tokens from as-tokens.env to your .env file"

View file

@ -1,54 +0,0 @@
#!/bin/bash
# Generate random token
gen_token() {
openssl rand -hex 32
}
# Bot configurations: name, sender_localpart
declare -a BOTS=(
"mana:mana-bot"
"ollama:ollama-bot"
"stats:stats-bot"
"projectdoc:projectdoc-bot"
"todo:todo-bot"
"calendar:calendar-bot"
"nutriphi:nutriphi-bot"
"zitare:zitare-bot"
"clock:clock-bot"
"tts:tts-bot"
)
echo "# Generated AS tokens for .env file:" > as-tokens.env
echo "" >> as-tokens.env
for bot_config in "${BOTS[@]}"; do
IFS=":" read -r name sender <<< "$bot_config"
as_token=$(gen_token)
hs_token=$(gen_token)
cat > "${name}-bot.yaml" << EOF
id: ${name}-bot
hs_token: ${hs_token}
as_token: ${as_token}
url: null
sender_localpart: ${sender}
namespaces:
users:
- exclusive: true
regex: '@${sender}:mana\.how'
rooms: []
aliases: []
rate_limited: false
EOF
# Convert name to uppercase for env var
env_name=$(echo "${name}" | tr '[:lower:]' '[:upper:]' | tr '-' '_')
echo "MATRIX_${env_name}_BOT_AS_TOKEN=${as_token}" >> as-tokens.env
echo "Created ${name}-bot.yaml with AS token"
done
echo ""
echo "Done! Add the tokens from as-tokens.env to your .env file"

View file

@ -1,211 +0,0 @@
# Mana Matrix Synapse Configuration
# Documentation: https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html
server_name: "mana.how"
pid_file: /data/homeserver.pid
public_baseurl: https://matrix.mana.how/
# ============================================
# Listeners
# ============================================
listeners:
- port: 8008
tls: false
type: http
x_forwarded: true
resources:
- names: [client, federation]
compress: false
# ============================================
# Database (PostgreSQL)
# ============================================
database:
name: psycopg2
txn_limit: 10000
args:
user: synapse
password: "synapse-secure-password"
database: matrix
host: postgres
port: 5432
cp_min: 5
cp_max: 10
# ============================================
# Logging
# ============================================
log_config: "/config/log.config.yaml"
# ============================================
# Media Storage
# ============================================
media_store_path: /data/media_store
max_upload_size: 50M
url_preview_enabled: true
url_preview_ip_range_blacklist:
- '127.0.0.0/8'
- '10.0.0.0/8'
- '172.16.0.0/12'
- '192.168.0.0/16'
- '100.64.0.0/10'
- '192.0.0.0/24'
- '169.254.0.0/16'
- '198.18.0.0/15'
- '192.0.2.0/24'
- '198.51.100.0/24'
- '203.0.113.0/24'
- '224.0.0.0/4'
- '::1/128'
- 'fe80::/10'
- 'fc00::/7'
- '2001:db8::/32'
- 'ff00::/8'
- 'fec0::/10'
# ============================================
# Registration & Authentication
# ============================================
enable_registration: false
enable_registration_without_verification: false
# Password config (enabled - OIDC not yet available from mana-auth)
password_config:
enabled: true
localdb_enabled: true
pepper: "${SYNAPSE_PASSWORD_PEPPER:-change-me-pepper}"
# Session lifetime (must be >= refresh_token_lifetime)
# Set to 10 years for bot tokens to avoid frequent expiration
session_lifetime: 87600h
refresh_token_lifetime: 87600h
# ============================================
# Rate Limiting
# ============================================
rc_message:
per_second: 5
burst_count: 20
rc_registration:
per_second: 0.5
burst_count: 5
rc_login:
address:
per_second: 0.5
burst_count: 5
account:
per_second: 0.5
burst_count: 5
failed_attempts:
per_second: 0.5
burst_count: 5
# ============================================
# Federation
# ============================================
# Allow federation with other Matrix servers
federation_domain_whitelist: []
trusted_key_servers:
- server_name: "matrix.org"
# ============================================
# DSGVO / Data Retention
# ============================================
retention:
enabled: true
default_policy:
min_lifetime: 1d
max_lifetime: 365d
allowed_lifetime_min: 1d
allowed_lifetime_max: 365d
purge_jobs:
- longest_max_lifetime: 3d
interval: 12h
- shortest_max_lifetime: 365d
interval: 1d
# Forgotten room retention
forgotten_room_retention_period: 7d
# ============================================
# Security
# ============================================
signing_key_path: "/data/signing.key"
form_secret: "${SYNAPSE_FORM_SECRET:-change-me-form-secret}"
macaroon_secret_key: "${SYNAPSE_MACAROON_SECRET:-change-me-macaroon-secret}"
registration_shared_secret: "${SYNAPSE_REGISTRATION_SECRET:-change-me-registration-secret}"
# ============================================
# Application Services (for Bots)
# Currently disabled - using long-lived user tokens instead
# TODO: Migrate bots to AS for truly permanent tokens
# ============================================
app_service_config_files: []
# ============================================
# Metrics & Telemetry
# ============================================
report_stats: false
enable_metrics: true
metrics_port: 9002
# ============================================
# Caching
# ============================================
caches:
global_factor: 0.5
per_cache_factors: {}
expire_caches: true
cache_entry_ttl: 30m
# ============================================
# Background Tasks
# ============================================
run_background_tasks_on: synapse
# ============================================
# Email (optional, for password reset)
# ============================================
# email:
# smtp_host: smtp-relay.brevo.com
# smtp_port: 587
# smtp_user: "${SMTP_USER}"
# smtp_pass: "${SMTP_PASSWORD}"
# require_transport_security: true
# notif_from: "Mana Matrix <noreply@mana.how>"
# ============================================
# OIDC / SSO Configuration (Mana Core Auth)
# ============================================
# OIDC disabled: mana-auth (Better Auth) does not expose OIDC discovery endpoints
# TODO: add OIDC provider support to mana-auth, then re-enable this
# oidc_providers:
# - idp_id: mana
# issuer: "https://auth.mana.how"
# client_id: "matrix-synapse"
# ...
# SSO UI Settings
sso:
client_whitelist:
- "https://element.mana.how"
- "https://matrix.mana.how"

View file

@ -1,34 +0,0 @@
# Synapse Logging Configuration
version: 1
formatters:
precise:
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: precise
stream: 'ext://sys.stdout'
file:
class: logging.handlers.TimedRotatingFileHandler
formatter: precise
filename: /data/logs/homeserver.log
when: midnight
backupCount: 7
encoding: utf8
loggers:
synapse.storage.SQL:
level: WARNING
synapse.access.http.8008:
level: WARNING
root:
level: INFO
handlers: [console, file]
disable_existing_loggers: false

View file

@ -1,59 +0,0 @@
{
"default_server_config": {
"m.homeserver": {
"base_url": "https://matrix.mana.how",
"server_name": "mana.how"
},
"m.identity_server": {
"base_url": ""
}
},
"brand": "Mana Chat",
"integrations_ui_url": "",
"integrations_rest_url": "",
"integrations_widgets_urls": [],
"disable_guests": true,
"disable_3pid_login": true,
"default_country_code": "DE",
"show_labs_settings": false,
"features": {
"feature_video_rooms": true,
"feature_group_calls": true,
"feature_thread": true
},
"room_directory": {
"servers": ["mana.how"]
},
"setting_defaults": {
"breadcrumbs": true,
"custom_themes": [],
"UIFeature.e2eeDefault": false,
"FTUE.userOnboardingButton": false,
"analyticsOptIn": false,
"pseudonymousAnalyticsOptIn": false
},
"ui_features": {
"UIFeature.RoomEncryptionSettings": false
},
"force_verification": false,
"enable_presence_by_hs_url": {},
"default_theme": "dark",
"permalink_prefix": "https://element.mana.how",
"terms_and_conditions_links": [],
"sso_redirect_options": {
"immediate": false,
"on_welcome_page": true
},
"posthog": {
"disabled": true
},
"sentry": {
"disabled": true
},
"analytics_owner": "",
"privacy_policy_url": "",
"show_analytics_setting": false,
"bug_report_endpoint_url": "",
"help_url": "https://mana.how/help",
"help_encryption_url": "https://element.io/help#encryption"
}

View file

@ -1,59 +0,0 @@
{
"default_server_config": {
"m.homeserver": {
"base_url": "https://matrix.mana.how",
"server_name": "mana.how"
},
"m.identity_server": {
"base_url": ""
}
},
"brand": "Mana Chat",
"integrations_ui_url": "",
"integrations_rest_url": "",
"integrations_widgets_urls": [],
"disable_guests": true,
"disable_3pid_login": true,
"default_country_code": "DE",
"show_labs_settings": false,
"features": {
"feature_video_rooms": true,
"feature_group_calls": true,
"feature_thread": true
},
"room_directory": {
"servers": ["mana.how"]
},
"setting_defaults": {
"breadcrumbs": true,
"custom_themes": [],
"UIFeature.e2eeDefault": false,
"FTUE.userOnboardingButton": false,
"analyticsOptIn": false,
"pseudonymousAnalyticsOptIn": false
},
"ui_features": {
"UIFeature.RoomEncryptionSettings": false
},
"force_verification": false,
"enable_presence_by_hs_url": {},
"default_theme": "dark",
"permalink_prefix": "https://element.mana.how",
"terms_and_conditions_links": [],
"sso_redirect_options": {
"immediate": false,
"on_welcome_page": true
},
"posthog": {
"disabled": true
},
"sentry": {
"disabled": true
},
"analytics_owner": "",
"privacy_policy_url": "",
"show_analytics_setting": false,
"bug_report_endpoint_url": "",
"help_url": "https://mana.how/help",
"help_encryption_url": "https://element.io/help#encryption"
}

View file

@ -1,222 +0,0 @@
# Mana Matrix Synapse Configuration
# Documentation: https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html
server_name: "mana.how"
pid_file: /data/homeserver.pid
public_baseurl: https://matrix.mana.how/
# ============================================
# Listeners
# ============================================
listeners:
- port: 8008
tls: false
type: http
x_forwarded: true
resources:
- names: [client, federation]
compress: false
# ============================================
# Database (PostgreSQL)
# ============================================
database:
name: psycopg2
txn_limit: 10000
args:
user: synapse
password: "synapse-secure-password"
database: matrix
host: postgres
port: 5432
cp_min: 5
cp_max: 10
# ============================================
# Logging
# ============================================
log_config: "/config/log.config.yaml"
# ============================================
# Media Storage
# ============================================
media_store_path: /data/media_store
max_upload_size: 50M
url_preview_enabled: true
url_preview_ip_range_blacklist:
- '127.0.0.0/8'
- '10.0.0.0/8'
- '172.16.0.0/12'
- '192.168.0.0/16'
- '100.64.0.0/10'
- '192.0.0.0/24'
- '169.254.0.0/16'
- '198.18.0.0/15'
- '192.0.2.0/24'
- '198.51.100.0/24'
- '203.0.113.0/24'
- '224.0.0.0/4'
- '::1/128'
- 'fe80::/10'
- 'fc00::/7'
- '2001:db8::/32'
- 'ff00::/8'
- 'fec0::/10'
# ============================================
# Registration & Authentication
# ============================================
enable_registration: false
enable_registration_without_verification: false
# Password config (disabled - all users authenticate via OIDC/SSO)
password_config:
enabled: false
localdb_enabled: false
pepper: "${SYNAPSE_PASSWORD_PEPPER:-change-me-pepper}"
# Session lifetime (must be >= refresh_token_lifetime)
# Set to 10 years for bot tokens to avoid frequent expiration
session_lifetime: 87600h
refresh_token_lifetime: 87600h
# ============================================
# Rate Limiting
# ============================================
rc_message:
per_second: 5
burst_count: 20
rc_registration:
per_second: 0.5
burst_count: 5
rc_login:
address:
per_second: 0.5
burst_count: 5
account:
per_second: 0.5
burst_count: 5
failed_attempts:
per_second: 0.5
burst_count: 5
# ============================================
# Federation
# ============================================
# Allow federation with other Matrix servers
federation_domain_whitelist: []
trusted_key_servers:
- server_name: "matrix.org"
# ============================================
# DSGVO / Data Retention
# ============================================
retention:
enabled: true
default_policy:
min_lifetime: 1d
max_lifetime: 365d
allowed_lifetime_min: 1d
allowed_lifetime_max: 365d
purge_jobs:
- longest_max_lifetime: 3d
interval: 12h
- shortest_max_lifetime: 365d
interval: 1d
# Forgotten room retention
forgotten_room_retention_period: 7d
# ============================================
# Security
# ============================================
signing_key_path: "/data/signing.key"
form_secret: "${SYNAPSE_FORM_SECRET:-change-me-form-secret}"
macaroon_secret_key: "${SYNAPSE_MACAROON_SECRET:-change-me-macaroon-secret}"
registration_shared_secret: "${SYNAPSE_REGISTRATION_SECRET:-change-me-registration-secret}"
# ============================================
# Application Services (for Bots)
# Currently disabled - using long-lived user tokens instead
# TODO: Migrate bots to AS for truly permanent tokens
# ============================================
app_service_config_files: []
# ============================================
# Metrics & Telemetry
# ============================================
report_stats: false
enable_metrics: true
metrics_port: 9002
# ============================================
# Caching
# ============================================
caches:
global_factor: 0.5
per_cache_factors: {}
expire_caches: true
cache_entry_ttl: 30m
# ============================================
# Background Tasks
# ============================================
run_background_tasks_on: synapse
# ============================================
# Email (optional, for password reset)
# ============================================
# email:
# smtp_host: smtp-relay.brevo.com
# smtp_port: 587
# smtp_user: "${SMTP_USER}"
# smtp_pass: "${SMTP_PASSWORD}"
# require_transport_security: true
# notif_from: "Mana Matrix <noreply@mana.how>"
# ============================================
# OIDC / SSO Configuration (Mana Core Auth)
# ============================================
# Enable SSO via Mana Core Auth OIDC Provider
oidc_providers:
- idp_id: mana
idp_name: "Mana Core"
idp_brand: "org.matrix.custom"
discover: true
issuer: "https://auth.mana.how"
client_id: "matrix-synapse"
client_secret: "6dc67d2dbea5c19409d21cbaec5ba77265b0296796d4ebb015d70209c68f3fd5"
scopes: ["openid", "profile", "email"]
user_mapping_provider:
config:
subject_claim: "sub"
localpart_template: "{{ user.email.split('@')[0] }}"
display_name_template: "{{ user.name }}"
email_template: "{{ user.email }}"
allow_existing_users: true
enable_registration: true
# SSO UI Settings
sso:
client_whitelist:
- "https://element.mana.how"
- "https://matrix.mana.how"

View file

@ -1,34 +0,0 @@
# Synapse Logging Configuration
version: 1
formatters:
precise:
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: precise
stream: 'ext://sys.stdout'
file:
class: logging.handlers.TimedRotatingFileHandler
formatter: precise
filename: /data/logs/homeserver.log
when: midnight
backupCount: 7
encoding: utf8
loggers:
synapse.storage.SQL:
level: WARNING
synapse.access.http.8008:
level: WARNING
root:
level: INFO
handlers: [console, file]
disable_existing_loggers: false

View file

@ -3,7 +3,7 @@ groups:
rules:
# Service Down Alert
- alert: ServiceDown
expr: up{job=~"mana-auth|.*-backend|mana-search|mana-media|mana-llm|synapse"} == 0
expr: up{job=~"mana-auth|.*-backend|mana-search|mana-media|mana-llm"} == 0
for: 1m
labels:
severity: critical

View file

@ -123,13 +123,6 @@ scrape_configs:
metrics_path: '/metrics'
scrape_interval: 30s
# Matrix Synapse
- job_name: 'synapse'
static_configs:
- targets: ['synapse:9002']
metrics_path: '/_synapse/metrics'
scrape_interval: 30s
# ============================================
# GPU Server (Windows PC, LAN: 192.168.178.11)
# ============================================
@ -190,13 +183,6 @@ scrape_configs:
metrics_path: '/metrics'
scrape_interval: 15s
# Matrix Bot (Go) — consolidated 21 bots
- job_name: 'mana-matrix-bot'
static_configs:
- targets: ['mana-matrix-bot:4000']
metrics_path: '/metrics'
scrape_interval: 30s
# Sync Server (Go) — local-first data sync
- job_name: 'mana-sync'
static_configs:
@ -204,7 +190,7 @@ scrape_configs:
metrics_path: '/metrics'
scrape_interval: 30s
# Notification Service (Go) — email, push, matrix, webhook
# Notification Service (Go) — email, push, webhook
- job_name: 'mana-notify'
static_configs:
- targets: ['mana-core-notify:3013']
@ -297,8 +283,6 @@ scrape_configs:
- https://grafana.mana.how
- https://stats.mana.how
- https://glitchtip.mana.how
- https://matrix.mana.how
- https://element.mana.how
relabel_configs:
- source_labels: [__address__]
target_label: __param_target

View file

@ -60,11 +60,6 @@ scrape_configs:
regex: "mana-mon-.*"
target_label: "tier"
replacement: "monitoring"
# mana-matrix-* → tier=matrix
- source_labels: ["container"]
regex: "mana-matrix-.*"
target_label: "tier"
replacement: "matrix"
# mana-game-* → tier=games
- source_labels: ["container"]
regex: "mana-game-.*"