rubis/apps/api/config/ally.ts
ordinarthur 7521e1fff6 feat(auth): Microsoft 365 SSO + factorisation helper SSO partagé
Backend
- Custom Ally driver Microsoft (Oauth2Driver) — Microsoft n'est pas dans
  les providers built-in, mais le driver dérive de Oauth2Driver en quelques
  lignes. Endpoints v2.0 (Microsoft Identity Platform), Graph /me pour le
  profil, fallback userPrincipalName si mail null (comptes perso).
- Tenant configurable via MICROSOFT_TENANT (défaut 'common' — accepte
  work/school + perso ; 'organizations' pour M365 strict).
- Migration 1400 : ajout microsoft_id nullable unique sur users.
- AuthMicrosoftController : redirect + callback (même pattern que Google).
- Refacto : extraction d'un service sso_session.ts (findOrCreateUserFromSso,
  nextRouteAfterSso, emitSsoSessionAndRedirect) → AuthGoogle + AuthMicrosoft
  partagent la logique.
- Routes /api/v1/auth/microsoft/{redirect,callback}.

Frontend
- Composant SsoButton générique (provider="google"|"microsoft") avec logo
  officiel inline pour chaque. Remplace l'ancien GoogleButton.
- Login + signup : pile verticale "Continuer avec Google" + "Continuer
  avec Microsoft", puis séparateur "ou", puis form email/password.
- Route SPA renommée /auth/google/complete → /auth/sso/complete (partagée
  entre les deux providers, la callback API redirige toujours dessus).
- Erreurs SSO sur /login : ?google=... ET ?microsoft=... → toast contextuel.

K3s
- ConfigMap rubis-api-config : ajout MICROSOFT_TENANT + MICROSOFT_CALLBACK_URL.
- Secret rubis-app-secrets : ajout MICROSOFT_CLIENT_ID + MICROSOFT_CLIENT_SECRET.

Doc
- .claude/deploy-memory.md : procédure Azure / Entra ID app registration.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 09:38:38 +02:00

49 lines
1.8 KiB
TypeScript

import env from '#start/env'
import { defineConfig, services } from '@adonisjs/ally'
import { microsoftService } from '#services/ally/microsoft_driver'
/**
* Configuration des providers OAuth (Ally).
*
* V1 : Google + Microsoft 365 (cf. CLAUDE.md → Auth).
* Le callback URL pointe vers l'API (/api/v1/auth/{provider}/callback).
* En prod, le reverse proxy nginx (rubis-web) achemine /api/* vers ce
* service, donc la même URL fonctionne browser et provider.
*/
const allyConfig = defineConfig({
google: services.google({
clientId: env.get('GOOGLE_CLIENT_ID', ''),
clientSecret: env.get('GOOGLE_CLIENT_SECRET', ''),
callbackUrl: env.get(
'GOOGLE_CALLBACK_URL',
'http://localhost:3333/api/v1/auth/google/callback'
),
// Scopes minimaux : on a juste besoin de l'email + nom + photo (avatar
// optionnel V2). Pas de Drive/Calendar : on ne touche pas aux données
// Google de l'utilisateur, on s'en sert juste comme provider d'identité.
scopes: ['userinfo.email', 'userinfo.profile'],
prompt: 'select_account',
}),
microsoft: microsoftService({
clientId: env.get('MICROSOFT_CLIENT_ID', ''),
clientSecret: env.get('MICROSOFT_CLIENT_SECRET', ''),
callbackUrl: env.get(
'MICROSOFT_CALLBACK_URL',
'http://localhost:3333/api/v1/auth/microsoft/callback'
),
// tenant=common : accepte work/school (Microsoft 365) ET comptes personnels
// (Outlook, Hotmail). Pour limiter à M365 strict, mettre 'organizations'.
tenant: env.get('MICROSOFT_TENANT', 'common'),
scopes: ['openid', 'profile', 'email', 'User.Read'],
prompt: 'select_account',
}),
})
export default allyConfig
declare module '@adonisjs/ally/types' {
interface SocialProviders extends InferSocialProviders<typeof allyConfig> {}
}
import type { InferSocialProviders } from '@adonisjs/ally/types'