diff --git a/services/mana-core-auth/docs/MATRIX_SSO_INTEGRATION.md b/services/mana-core-auth/docs/MATRIX_SSO_INTEGRATION.md new file mode 100644 index 000000000..0904cfa30 --- /dev/null +++ b/services/mana-core-auth/docs/MATRIX_SSO_INTEGRATION.md @@ -0,0 +1,191 @@ +# Matrix SSO Integration + +This document describes how Mana Core Auth provides Single Sign-On (SSO) for Matrix/Synapse using OpenID Connect (OIDC). + +## Overview + +Mana Core Auth acts as an **OIDC Provider** (Identity Provider), allowing Matrix Synapse to authenticate users via SSO. Users can sign in to Matrix using their Mana Core credentials. + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Matrix Client │────▶│ Synapse │────▶│ Mana Core Auth │ +│ (Element) │ │ (matrix.mana.how) │ │ (auth.mana.how) │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ + │ │ │ + │ 1. Click "SSO" │ │ + │─────────────────────▶│ │ + │ │ 2. Redirect to │ + │ │ OIDC authorize │ + │ │──────────────────────▶│ + │ │ │ + │ │ 3. Show login page │ + │◀─────────────────────────────────────────────│ + │ │ │ + │ 4. User logs in │ │ + │─────────────────────────────────────────────▶│ + │ │ │ + │ │ 5. Redirect with │ + │ │ auth code │ + │ │◀──────────────────────│ + │ │ │ + │ │ 6. Exchange code │ + │ │ for tokens │ + │ │──────────────────────▶│ + │ │ │ + │ 7. Login complete │◀──────────────────────│ + │◀─────────────────────│ │ +``` + +## OIDC Endpoints + +Mana Core Auth exposes the following OIDC endpoints: + +| Endpoint | URL | Description | +|----------|-----|-------------| +| Discovery | `https://auth.mana.how/.well-known/openid-configuration` | OIDC discovery document | +| Authorize | `https://auth.mana.how/api/auth/oauth2/authorize` | Authorization endpoint | +| Token | `https://auth.mana.how/api/auth/oauth2/token` | Token endpoint | +| UserInfo | `https://auth.mana.how/api/auth/oauth2/userinfo` | User info endpoint | +| JWKS | `https://auth.mana.how/api/auth/jwks` | JSON Web Key Set | +| Login | `https://auth.mana.how/login` | SSO login page | + +## Synapse Configuration + +The Matrix Synapse server is configured with OIDC in `docker/matrix/homeserver.yaml`: + +```yaml +oidc_providers: + - idp_id: manacore + idp_name: "Mana Core" + idp_brand: "org.matrix.custom" + discover: true + issuer: "https://auth.mana.how" + client_id: "matrix-synapse" + client_secret: "" + 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 }}" +``` + +## OAuth Application Registration + +The Matrix Synapse client is registered in the auth database: + +```sql +INSERT INTO auth.oauth_applications ( + id, name, client_id, client_secret, redirect_urls, type +) VALUES ( + 'matrix-synapse-client', + 'Matrix Synapse', + 'matrix-synapse', + '', + '["https://matrix.mana.how/_synapse/client/oidc/callback"]', + 'web' +); +``` + +## Authentication Flow + +1. **User initiates SSO**: User clicks "Sign in with Mana Core" on Element/Matrix client +2. **Synapse redirects**: Synapse redirects to Mana Core Auth's authorization endpoint +3. **Login page**: If not logged in, user sees the Mana Core login page +4. **User authenticates**: User enters email and password +5. **Authorization**: After successful login, user is redirected back to authorization endpoint +6. **Token exchange**: Synapse exchanges the authorization code for tokens +7. **User mapping**: Synapse creates/links the Matrix user based on OIDC claims +8. **Login complete**: User is logged into Matrix + +## Claims Provided + +The OIDC tokens include the following claims: + +| Claim | Description | +|-------|-------------| +| `sub` | User ID | +| `email` | User's email address | +| `email_verified` | Whether email is verified | +| `name` | User's display name | + +## Testing the Integration + +### Test OIDC Discovery + +```bash +curl https://auth.mana.how/.well-known/openid-configuration | jq +``` + +### Test Matrix SSO Redirect + +```bash +curl -I "https://matrix.mana.how/_matrix/client/v3/login/sso/redirect/oidc-manacore?redirectUrl=https://element.mana.how" +``` + +### Check Matrix Login Methods + +```bash +curl https://matrix.mana.how/_matrix/client/v3/login | jq '.flows[] | select(.type | contains("sso"))' +``` + +Expected output: +```json +{ + "type": "m.login.sso", + "identity_providers": [ + { + "id": "oidc-manacore", + "name": "Mana Core", + "brand": "org.matrix.custom" + } + ] +} +``` + +## Troubleshooting + +### JWKS Fetch Fails + +If Synapse can't fetch JWKS: +1. Check JWKS endpoint: `curl https://auth.mana.how/api/auth/jwks` +2. Verify Synapse can reach auth service (network/DNS) +3. Check Synapse logs for OIDC errors + +### Login Page Not Found + +If the login page returns 404: +1. Check that `/login` is excluded from global prefix in `main.ts` +2. Verify `OidcLoginController` is registered in `AuthModule` + +### Authorization Fails + +If authorization returns errors: +1. Check client_id matches registered OAuth application +2. Verify redirect_uri matches exactly (including trailing slash) +3. Check that required scopes are requested + +### Token Exchange Fails + +If token exchange fails: +1. Check client_secret is correct +2. Verify token endpoint is accessible +3. Check Synapse logs for detailed error messages + +## Security Considerations + +1. **Client Secret**: The OAuth client secret is stored securely and should never be exposed +2. **HTTPS Only**: All OIDC endpoints use HTTPS +3. **Token Expiry**: ID tokens expire after 15 minutes +4. **PKCE**: Authorization code flow uses PKCE for added security + +## Related Files + +| File | Purpose | +|------|---------| +| `src/auth/better-auth.config.ts` | OIDC Provider plugin configuration | +| `src/auth/oidc.controller.ts` | OIDC endpoint routing | +| `src/auth/oidc-login.controller.ts` | SSO login page | +| `src/db/schema/auth.schema.ts` | OAuth tables (oauth_applications, etc.) | +| `docker/matrix/homeserver.yaml` | Synapse OIDC configuration | diff --git a/services/mana-core-auth/src/auth/oidc.controller.ts b/services/mana-core-auth/src/auth/oidc.controller.ts index a45a65e43..046fc4323 100644 --- a/services/mana-core-auth/src/auth/oidc.controller.ts +++ b/services/mana-core-auth/src/auth/oidc.controller.ts @@ -129,10 +129,8 @@ export class OidcController { * Handle OIDC request by forwarding to Better Auth */ private async handleOidcRequest(req: Request, res: Response) { - console.log('[OIDC Controller] Handling request:', req.method, req.originalUrl); try { const response = await this.betterAuthService.handleOidcRequest(req); - console.log('[OIDC Controller] Better Auth response status:', response.status); // Set status code res.status(response.status || HttpStatus.OK); diff --git a/services/mana-core-auth/src/auth/services/better-auth.service.ts b/services/mana-core-auth/src/auth/services/better-auth.service.ts index 94bc234d4..e071d8b19 100644 --- a/services/mana-core-auth/src/auth/services/better-auth.service.ts +++ b/services/mana-core-auth/src/auth/services/better-auth.service.ts @@ -1211,7 +1211,6 @@ export class BetterAuthService { headers: Record; body: unknown; }> { - console.log('[handleOidcRequest] Received request:', req.method, req.originalUrl); try { // Map incoming paths to Better Auth's expected paths let mappedPath = req.originalUrl; @@ -1229,15 +1228,12 @@ export class BetterAuthService { mappedPath = mappedPath.replace('/api/oidc/', '/api/auth/oauth2/'); } - console.log('[handleOidcRequest] Mapped path:', mappedPath); - // Convert Express request to Fetch Request const url = new URL( mappedPath, this.configService.get('BASE_URL') || `http://localhost:${this.configService.get('PORT') || 3001}` ); - console.log('[handleOidcRequest] Constructed URL:', url.toString()); const headers = new Headers(); for (const [key, value] of Object.entries(req.headers)) { @@ -1255,11 +1251,6 @@ export class BetterAuthService { // Call Better Auth's handler const response = await this.auth.handler(fetchRequest); - console.log('[handleOidcRequest] Better Auth status:', response.status); - console.log( - '[handleOidcRequest] Better Auth headers:', - Object.fromEntries(response.headers.entries()) - ); // Convert Response to our format const responseHeaders: Record = {}; @@ -1267,18 +1258,15 @@ export class BetterAuthService { responseHeaders[key] = value; }); - // Get body - handle empty responses + // Get body - handle empty responses gracefully let body: unknown; const contentType = response.headers.get('content-type'); const textBody = await response.text(); - console.log('[handleOidcRequest] Response body length:', textBody.length); - console.log('[handleOidcRequest] Response body preview:', textBody.substring(0, 500)); if (contentType?.includes('application/json') && textBody.length > 0) { try { body = JSON.parse(textBody); } catch { - console.warn('[handleOidcRequest] Failed to parse JSON, using text body'); body = textBody; } } else {