Add comprehensive admin dashboard to view and manage user data across all projects:
Backend:
- Add admin endpoints to Chat, Todo, Contacts, Calendar, Picture, Zitare, Presi
- Each backend exposes GET/DELETE /api/v1/admin/user-data/:userId
- Service-to-service auth via X-Service-Key header
Aggregation (mana-core-auth):
- GET /api/v1/admin/users - Paginated user list with search
- GET /api/v1/admin/users/:userId/data - Aggregated data from all backends
- DELETE /api/v1/admin/users/:userId/data - GDPR deletion across all projects
Frontend (ManaCore web):
- New User Data tab in admin navigation
- User search page at /admin/user-data
- User detail page with ProjectDataCard components
- GDPR deletion dialog with email confirmation
Presi:
- Migrate user_id from UUID to TEXT for Better Auth compatibility
- Add SQL migration script
When users log into Matrix via OIDC (Sign in with Mana Core), their
Matrix user ID is now automatically linked to their Mana account.
This enables automatic bot authentication without requiring a
separate !login command.
- Add autoLinkOnOidcLogin() method to MatrixSessionService
- Hook into OIDC userinfo endpoint to create links automatically
- Calculate Matrix user ID from email using Synapse's template
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Better Auth signs JWTs with issuer=BASE_URL (https://auth.mana.how) for OIDC
compatibility, but validation was using jwt.issuer config which defaults to
'manacore'. This caused "unexpected iss claim value" errors.
Fixed in:
- better-auth.service.ts validateToken()
- jwt-auth.guard.ts
- optional-auth.guard.ts
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The refresh endpoint was using manual jwt.sign with RSA keys, but the
server doesn't have JWT_PRIVATE_KEY configured. Changed to use Better
Auth's signJWT method which uses the JWKS/EdDSA keys from the database.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Same issue as sessionToToken - the signIn method was returning the
session cookie token as refreshToken, but the /api/v1/auth/refresh
endpoint expects the actual refreshToken field from the sessions table.
Now signIn:
- Fetches the session from database after Better Auth creates it
- Uses existing refreshToken if available
- Generates and stores a new refreshToken if missing
- Returns the actual refreshToken that works with token refresh
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The get-session endpoint needs to be accessible at /api/auth/get-session
(without the /api/v1 prefix) for SSO to work.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The SSO flow in client apps calls /api/auth/get-session with cookies
to check if the user has a valid session. This endpoint was missing
from the NestJS passthrough controller.
Now the endpoint forwards the request with cookies to Better Auth's
native handler and returns the session data.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The sessionToToken method was incorrectly returning the session cookie
token instead of the actual refreshToken from the database. This caused
"No refresh token available" errors when users logged in via SSO
(cross-domain cookie) because the /api/v1/auth/refresh endpoint expects
the refreshToken field from the sessions table, not the cookie token.
Now the method:
- Fetches the session from database by cookie token
- Uses existing refreshToken if available
- Generates and stores a new refreshToken if missing
- Returns the actual refreshToken that works with token refresh
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Activate Redis session storage in both bots for cross-bot SSO
- Update SessionHelper to async methods for Redis-backed SessionService
- Fix async/await issues in todo-bot and calendar-bot matrix.service.ts
- Remove unused imports from calendar-api and todo-api services
- Add CALENDAR_BACKEND_URL and MANA_CORE_SERVICE_KEY to .env.development
Note: SessionService methods are now async (Redis-backed). Other bots
need their matrix.service.ts updated to await these async calls.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add Single Sign-On (SSO) support across all mana.how subdomains:
- Add trySSO() method to @manacore/shared-auth that exchanges session
cookies for JWT tokens
- Add /api/v1/auth/session-to-token endpoint to mana-core-auth service
- Update all 15 web apps to try SSO during auth initialization
SSO Flow:
1. User logs in on any app (e.g., calendar.mana.how)
2. Session cookie is set with Domain=.mana.how
3. When visiting another app (e.g., todo.mana.how), it checks for
local tokens first
4. If no local tokens, tries SSO via session cookie
5. Session cookie is exchanged for JWT tokens via new endpoint
6. User is automatically authenticated
Apps updated: calendar, chat, clock, contacts, manacore, manadeck,
nutriphi, picture, planta, presi, questions, skilltree, storage,
todo, zitare
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Configure Better Auth with crossSubDomainCookies for .mana.how domain
- Add COOKIE_DOMAIN environment variable (production: .mana.how)
- Sync trustedOrigins with all production subdomains
- Users now login once and are authenticated across all apps
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix LoggerService mock in better-auth.service.spec.ts
- Fix name assertion in auth.controller.spec.ts (empty string fallback)
- Fix createRemoteJWKSet mock in jwt-auth.guard.spec.ts
- Add Grafana dashboard for Auth Service monitoring
- Add 10 auth-specific Prometheus alert rules
- Update production readiness plan to 100% complete
All 199 unit tests passing.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Build matrix-mana-bot only for linux/amd64 (arm64 fails due to QEMU)
- Move pnpm overrides for cpu-features and ssh2 to root package.json
- These native deps cause illegal instruction errors under QEMU emulation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
OIDC providers like Synapse expect the JWT issuer claim to match the
discovery document's issuer URL. Changed JWT plugin config from
JWT_ISSUER to BASE_URL to ensure consistency.
Also adds:
- @manacore/credit-operations package with operation definitions
- @manacore/shared-credit-ui package with React Native and Svelte components
- CreditInterceptor and @UseCredits decorator in nestjs-integration
- Credit system integration in chat backend
Set useJWTPlugin: true so id_tokens are signed with EdDSA keys
from JWKS instead of HS256. This fixes Synapse OIDC integration
which verifies tokens via JWKS endpoint.
- Removed debug logging that exposed sensitive client_secret in production logs
- The body-parser middleware in main.ts correctly handles form-urlencoded token requests
- handleOidcRequest properly converts parsed body to URLSearchParams for Better Auth
- Add t@t.de with password +üp+üp+üp to DEV_USERS array
- Refactor seed script to iterate over multiple users
- Ensures test user is always available after db:seed:dev
Documents that the token endpoint accepts both JSON and form-urlencoded
bodies per OAuth2 spec, with form data parsed by body-parser middleware.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Configure Matrix Synapse as a trusted client that skips the consent screen.
This enables seamless SSO login without requiring user consent for each login.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Better Auth expects 'redirectUrls' (lowercase 'urls') but schema had
'redirectURLs' (uppercase 'URLs'). This caused the redirect URI validation
to fail because Drizzle returned the wrong property name.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Better Auth's OIDC provider expects redirect_urls to be a comma-separated
string, not a JSON array. Updated seed script and schema documentation.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The OIDC request handler was not properly forwarding sign-in requests.
Added a dedicated handler that:
- Directly calls Better Auth's handler
- Properly handles Set-Cookie headers for session cookies
- Exposed getHandler() method from BetterAuthService
- Added trustedOrigins configuration to allow cross-origin requests
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The OIDC login page was using our custom /api/v1/auth/login endpoint
which returns tokens but doesn't set session cookies. Better Auth's
OIDC provider needs session cookies to recognize logged-in users.
Changes:
- Update login page to use /api/auth/sign-in/email (Better Auth native)
- Add sign-in endpoint handler in oidc.controller.ts
- Add route exclusion in main.ts for the sign-in path
This fixes the infinite redirect loop where users would log in but
then be sent back to login because the OAuth2 authorize endpoint
couldn't detect the session.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When redirected from authorization endpoint, the client_id is encoded
in the returnUrl parameter, not directly in query params. This fix
extracts it properly to display the correct application name.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The login page uses inline JavaScript for the form submission handler.
Helmet's default CSP was blocking this, preventing users from logging in
via OIDC/SSO flows (e.g., Matrix Synapse).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add ability to resend verification email when login fails with
"Email not verified" error. Implemented across all 14 apps using
Mana Core Auth.
Changes:
- Add POST /api/v1/auth/resend-verification endpoint to mana-core-auth
- Add resendVerificationEmail method to shared-auth client
- Update LoginPage component with resend UI and translations
- Add resendVerificationEmail to all app auth stores
- Add translations for de, en, fr, es, it
- Add PlantaLogo to shared-branding
- Migrate planta login to shared LoginPage component
Add a simple login page at /login for OIDC authorization flows.
When users access the authorization endpoint without being logged in,
Better Auth redirects them to this page. After successful login,
users are redirected back to continue the authorization flow.
- Create OidcLoginController with login page HTML
- Add controller to AuthModule
- Exclude /login from global prefix
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Better Auth's OIDC discovery document advertises endpoints at
/api/auth/oauth2/* paths. Add routes for these native paths to
ensure Matrix Synapse and other OIDC clients can complete the
authorization flow.
Routes added:
- GET /api/auth/oauth2/authorize
- POST /api/auth/oauth2/token
- GET /api/auth/oauth2/userinfo
- GET /api/auth/jwks
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add explicit path exclusions and path-to-regexp wildcard patterns
to ensure /api/auth/jwks and other OIDC routes are excluded from
the /api/v1 global prefix. This fixes JWKS endpoint accessibility
for Matrix Synapse OIDC integration.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Better Auth's discovery document points to /api/auth/jwks,
so we need to expose this route directly in NestJS.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use { path, method } syntax for NestJS global prefix excludes to ensure
OIDC routes (.well-known/*, api/oidc/*) are properly excluded from the
/api/v1 prefix.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add OidcController to expose Better Auth OIDC Provider endpoints
- Add handleOidcRequest method to BetterAuthService
- Exclude OIDC routes from global /api/v1 prefix
- Register OidcController in AuthModule
Endpoints:
- GET /.well-known/openid-configuration
- GET /api/oidc/authorize
- POST /api/oidc/token
- GET /api/oidc/userinfo
- GET /api/oidc/jwks
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add OIDC Provider plugin to Better Auth configuration
- Add OIDC database tables (oauth_applications, oauth_access_tokens,
oauth_authorization_codes, oauth_consents)
- Configure Synapse as OIDC client in homeserver.yaml
- Update Element Web config for SSO support
- Add seed script for OIDC clients (db:seed:oidc)
- Update Cloudflare tunnel config with Matrix URLs
This enables Single Sign-On between Mana Core Auth and Matrix/Synapse,
allowing users to authenticate via their existing Mana account.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>