Connexion via Google (OAuth 2.0)
Ajoute l'authentification Google en complément de la connexion par mot de passe. Le callback OAuth émet le même JWT que la connexion classique, donc le middleware d'authentification et ProtectedRoute restent inchangés. Backend - stratégie passport-google-oauth20 dans src/config/passport.js - routes GET /api/auth/google et /google/callback, documentées dans Swagger - un compte existant avec le même email est rattaché automatiquement au compte Google, et son email passe vérifié - les routes répondent 503 si les identifiants ne sont pas configurés, plutôt que de faire échouer le démarrage du serveur - login et changePassword ne comparent plus un hash inexistant sur les comptes sans mot de passe Base de données - password devient nullable - colonnes provider, providerId et avatarUrl, index unique sur le couple provider/providerId Frontend - page /auth/callback qui récupère le token et redirige selon le rôle - boutons Google de Connexion et Inscription branchés - boutons Facebook désactivés en attendant les identifiants Meta Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
82
backend/src/config/passport.js
Normal file
82
backend/src/config/passport.js
Normal file
@@ -0,0 +1,82 @@
|
||||
const passport = require("passport");
|
||||
const GoogleStrategy = require("passport-google-oauth20").Strategy;
|
||||
const prisma = require("./prisma");
|
||||
|
||||
// Authentification stateless : le callback OAuth émet directement notre JWT,
|
||||
// donc pas de session Express ni de serialize/deserialize à brancher.
|
||||
|
||||
const googleEnabled =
|
||||
Boolean(process.env.GOOGLE_CLIENT_ID) && Boolean(process.env.GOOGLE_CLIENT_SECRET);
|
||||
|
||||
if (googleEnabled) {
|
||||
passport.use(
|
||||
new GoogleStrategy(
|
||||
{
|
||||
clientID: process.env.GOOGLE_CLIENT_ID,
|
||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
||||
callbackURL: `${process.env.APP_URL || "http://localhost:5000"}/api/auth/google/callback`,
|
||||
scope: ["profile", "email"],
|
||||
},
|
||||
async (accessToken, refreshToken, profile, done) => {
|
||||
try {
|
||||
const email = profile.emails?.[0]?.value;
|
||||
|
||||
if (!email) {
|
||||
return done(null, false, {
|
||||
message: "Aucune adresse email n'est associée à ce compte Google.",
|
||||
});
|
||||
}
|
||||
|
||||
// 1. Compte déjà lié à ce compte Google : connexion directe.
|
||||
const byProvider = await prisma.user.findUnique({
|
||||
where: {
|
||||
provider_providerId: { provider: "GOOGLE", providerId: profile.id },
|
||||
},
|
||||
});
|
||||
|
||||
if (byProvider) return done(null, byProvider);
|
||||
|
||||
// 2. Un compte existe avec le même email : on le rattache à Google.
|
||||
// L'email étant déjà validé par Google, on en profite pour lever
|
||||
// la vérification si elle était en attente.
|
||||
const byEmail = await prisma.user.findUnique({ where: { email } });
|
||||
|
||||
if (byEmail) {
|
||||
const linked = await prisma.user.update({
|
||||
where: { id: byEmail.id },
|
||||
data: {
|
||||
provider: "GOOGLE",
|
||||
providerId: profile.id,
|
||||
isEmailVerified: true,
|
||||
emailVerifyToken: null,
|
||||
emailVerifyExpires: null,
|
||||
avatarUrl: byEmail.avatarUrl || profile.photos?.[0]?.value || null,
|
||||
},
|
||||
});
|
||||
return done(null, linked);
|
||||
}
|
||||
|
||||
// 3. Première connexion : création du compte.
|
||||
const created = await prisma.user.create({
|
||||
data: {
|
||||
firstname: profile.name?.givenName || profile.displayName || "Utilisateur",
|
||||
lastname: profile.name?.familyName || "",
|
||||
email,
|
||||
password: null,
|
||||
provider: "GOOGLE",
|
||||
providerId: profile.id,
|
||||
avatarUrl: profile.photos?.[0]?.value || null,
|
||||
isEmailVerified: true,
|
||||
},
|
||||
});
|
||||
|
||||
return done(null, created);
|
||||
} catch (error) {
|
||||
return done(error);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = { passport, googleEnabled };
|
||||
Reference in New Issue
Block a user