add google sso

This commit is contained in:
Arthur Barre 2025-03-11 13:00:49 +01:00
parent 3e85733f4a
commit 191b8a4da2
13 changed files with 386 additions and 17 deletions

View File

@ -18,6 +18,7 @@
"dotenv": "^16.3.1",
"fastify": "^4.19.0",
"fastify-plugin": "^4.5.0",
"google-auth-library": "^9.15.1",
"openai": "^4.0.0",
"stripe": "^12.12.0"
},

138
backend/pnpm-lock.yaml generated
View File

@ -32,6 +32,9 @@ importers:
fastify-plugin:
specifier: ^4.5.0
version: 4.5.1
google-auth-library:
specifier: ^9.15.1
version: 9.15.1
openai:
specifier: ^4.0.0
version: 4.86.2
@ -133,6 +136,10 @@ packages:
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
engines: {node: '>= 6.0.0'}
agent-base@7.1.3:
resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==}
engines: {node: '>= 14'}
agentkeepalive@4.6.0:
resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==}
engines: {node: '>= 8.0.0'}
@ -188,10 +195,16 @@ packages:
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
bcrypt@5.1.1:
resolution: {integrity: sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==}
engines: {node: '>= 10.0.0'}
bignumber.js@9.1.2:
resolution: {integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==}
binary-extensions@2.3.0:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
@ -206,6 +219,9 @@ packages:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
buffer-equal-constant-time@1.0.1:
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
call-bind-apply-helpers@1.0.2:
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
engines: {node: '>= 0.4'}
@ -294,6 +310,9 @@ packages:
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
engines: {node: '>=6'}
extend@3.0.2:
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
fast-content-type-parse@1.1.0:
resolution: {integrity: sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==}
@ -385,6 +404,14 @@ packages:
engines: {node: '>=10'}
deprecated: This package is no longer supported.
gaxios@6.7.1:
resolution: {integrity: sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==}
engines: {node: '>=14'}
gcp-metadata@6.1.1:
resolution: {integrity: sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==}
engines: {node: '>=14'}
get-intrinsic@1.3.0:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'}
@ -401,10 +428,22 @@ packages:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Glob versions prior to v9 are no longer supported
google-auth-library@9.15.1:
resolution: {integrity: sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==}
engines: {node: '>=14'}
google-logging-utils@0.0.2:
resolution: {integrity: sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==}
engines: {node: '>=14'}
gopd@1.2.0:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'}
gtoken@7.1.0:
resolution: {integrity: sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==}
engines: {node: '>=14.0.0'}
has-flag@3.0.0:
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
engines: {node: '>=4'}
@ -428,6 +467,10 @@ packages:
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
engines: {node: '>= 6'}
https-proxy-agent@7.0.6:
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
engines: {node: '>= 14'}
humanize-ms@1.2.1:
resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==}
@ -465,12 +508,25 @@ packages:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
is-stream@2.0.1:
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
engines: {node: '>=8'}
json-bigint@1.0.0:
resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==}
json-schema-ref-resolver@1.0.1:
resolution: {integrity: sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==}
json-schema-traverse@1.0.0:
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
jwa@2.0.0:
resolution: {integrity: sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==}
jws@4.0.0:
resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==}
light-my-request@5.14.0:
resolution: {integrity: sha512-aORPWntbpH5esaYpGOOmri0OHDOe3wC5M2MQxZ9dvMLZm6DnaAn0kJlcbU9hwsQgLzmZyReKwFwwPkR+nHu5kA==}
@ -782,6 +838,10 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
uuid@9.0.1:
resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
hasBin: true
web-streams-polyfill@4.0.0-beta.3:
resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==}
engines: {node: '>= 14'}
@ -920,6 +980,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
agent-base@7.1.3: {}
agentkeepalive@4.6.0:
dependencies:
humanize-ms: 1.2.1
@ -971,6 +1033,8 @@ snapshots:
balanced-match@1.0.2: {}
base64-js@1.5.1: {}
bcrypt@5.1.1:
dependencies:
'@mapbox/node-pre-gyp': 1.0.11
@ -979,6 +1043,8 @@ snapshots:
- encoding
- supports-color
bignumber.js@9.1.2: {}
binary-extensions@2.3.0: {}
bn.js@4.12.1: {}
@ -992,6 +1058,8 @@ snapshots:
dependencies:
fill-range: 7.1.1
buffer-equal-constant-time@1.0.1: {}
call-bind-apply-helpers@1.0.2:
dependencies:
es-errors: 1.3.0
@ -1071,6 +1139,8 @@ snapshots:
event-target-shim@5.0.1: {}
extend@3.0.2: {}
fast-content-type-parse@1.1.0: {}
fast-decode-uri-component@1.0.1: {}
@ -1192,6 +1262,26 @@ snapshots:
strip-ansi: 6.0.1
wide-align: 1.1.5
gaxios@6.7.1:
dependencies:
extend: 3.0.2
https-proxy-agent: 7.0.6
is-stream: 2.0.1
node-fetch: 2.7.0
uuid: 9.0.1
transitivePeerDependencies:
- encoding
- supports-color
gcp-metadata@6.1.1:
dependencies:
gaxios: 6.7.1
google-logging-utils: 0.0.2
json-bigint: 1.0.0
transitivePeerDependencies:
- encoding
- supports-color
get-intrinsic@1.3.0:
dependencies:
call-bind-apply-helpers: 1.0.2
@ -1223,8 +1313,30 @@ snapshots:
once: 1.4.0
path-is-absolute: 1.0.1
google-auth-library@9.15.1:
dependencies:
base64-js: 1.5.1
ecdsa-sig-formatter: 1.0.11
gaxios: 6.7.1
gcp-metadata: 6.1.1
gtoken: 7.1.0
jws: 4.0.0
transitivePeerDependencies:
- encoding
- supports-color
google-logging-utils@0.0.2: {}
gopd@1.2.0: {}
gtoken@7.1.0:
dependencies:
gaxios: 6.7.1
jws: 4.0.0
transitivePeerDependencies:
- encoding
- supports-color
has-flag@3.0.0: {}
has-symbols@1.1.0: {}
@ -1246,6 +1358,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
https-proxy-agent@7.0.6:
dependencies:
agent-base: 7.1.3
debug: 4.4.0(supports-color@5.5.0)
transitivePeerDependencies:
- supports-color
humanize-ms@1.2.1:
dependencies:
ms: 2.1.3
@ -1275,12 +1394,29 @@ snapshots:
is-number@7.0.0: {}
is-stream@2.0.1: {}
json-bigint@1.0.0:
dependencies:
bignumber.js: 9.1.2
json-schema-ref-resolver@1.0.1:
dependencies:
fast-deep-equal: 3.1.3
json-schema-traverse@1.0.0: {}
jwa@2.0.0:
dependencies:
buffer-equal-constant-time: 1.0.1
ecdsa-sig-formatter: 1.0.11
safe-buffer: 5.2.1
jws@4.0.0:
dependencies:
jwa: 2.0.0
safe-buffer: 5.2.1
light-my-request@5.14.0:
dependencies:
cookie: 0.7.2
@ -1581,6 +1717,8 @@ snapshots:
util-deprecate@1.0.2: {}
uuid@9.0.1: {}
web-streams-polyfill@4.0.0-beta.3: {}
webidl-conversions@3.0.1: {}

View File

@ -0,0 +1,29 @@
/*
Warnings:
- Made the column `name` on table `User` required. This step will fail if there are existing NULL values in that column.
- Made the column `stripeId` on table `User` required. This step will fail if there are existing NULL values in that column.
*/
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_User" (
"id" TEXT NOT NULL PRIMARY KEY,
"email" TEXT NOT NULL,
"password" TEXT,
"name" TEXT NOT NULL,
"googleId" TEXT,
"stripeId" TEXT NOT NULL,
"subscription" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
INSERT INTO "new_User" ("createdAt", "email", "id", "name", "password", "stripeId", "subscription", "updatedAt") SELECT "createdAt", "email", "id", "name", "password", "stripeId", "subscription", "updatedAt" FROM "User";
DROP TABLE "User";
ALTER TABLE "new_User" RENAME TO "User";
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
CREATE UNIQUE INDEX "User_googleId_key" ON "User"("googleId");
CREATE UNIQUE INDEX "User_stripeId_key" ON "User"("stripeId");
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

View File

@ -10,10 +10,11 @@ datasource db {
model User {
id String @id @default(uuid())
email String @unique
password String
name String?
subscription String @default("free") // "free" ou "premium"
stripeId String?
password String? // Optionnel pour les utilisateurs Google
name String
googleId String? @unique // ID Google pour SSO
stripeId String @unique
subscription String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
recipes Recipe[]

View File

@ -0,0 +1,8 @@
const fp = require('fastify-plugin');
const { OAuth2Client } = require('google-auth-library');
module.exports = fp(async function (fastify, opts) {
const googleClient = new OAuth2Client(process.env.GOOGLE_CLIENT_ID);
fastify.decorate('googleClient', googleClient);
});

View File

@ -108,4 +108,75 @@ module.exports = async function (fastify, opts) {
return reply.code(500).send({ error: 'Erreur lors de la connexion' });
}
});
fastify.post('/google-auth', {
schema: {
body: {
type: 'object',
required: ['token'],
properties: {
token: { type: 'string' }
}
}
}
}, async (request, reply) => {
const { token } = request.body;
try {
// Utiliser le token d'accès pour obtenir les informations utilisateur
// au lieu d'essayer de le vérifier comme un ID token
const response = await fetch('https://www.googleapis.com/oauth2/v3/userinfo', {
headers: { Authorization: `Bearer ${token}` }
});
if (!response.ok) {
throw new Error('Impossible de récupérer les informations utilisateur');
}
const userData = await response.json();
const { email, name, sub: googleId } = userData;
// Vérifier si l'utilisateur existe déjà
let user = await fastify.prisma.user.findUnique({
where: { email }
});
if (!user) {
// Créer un client Stripe
const customer = await fastify.createCustomer(email, name);
// Créer l'utilisateur
user = await fastify.prisma.user.create({
data: {
email,
name,
googleId,
stripeId: customer.id
}
});
} else if (!user.googleId) {
// Mettre à jour l'utilisateur existant avec l'ID Google
user = await fastify.prisma.user.update({
where: { id: user.id },
data: { googleId }
});
}
// Générer un token JWT
const jwtToken = fastify.jwt.sign({ id: user.id }, { expiresIn: '7d' });
return {
user: {
id: user.id,
email: user.email,
name: user.name,
subscription: user.subscription
},
token: jwtToken
};
} catch (error) {
fastify.log.error(error);
return reply.code(500).send({ error: 'Erreur lors de l\'authentification Google' });
}
});
};

View File

@ -14,10 +14,10 @@ fastify.register(require('@fastify/cors'), {
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
});
fastify.register(require('./plugins/auth'));
fastify.register(require('./plugins/stripe'));
fastify.register(require('./plugins/ai'));
fastify.register(require('./plugins/google-auth'));
// Routes
fastify.register(require('./routes/auth'), { prefix: '/auth' });

View File

@ -6,6 +6,7 @@
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"build-no-error": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
@ -19,6 +20,7 @@
"@radix-ui/react-slot": "^1.1.2",
"@radix-ui/react-tabs": "^1.1.3",
"@radix-ui/react-toast": "^1.2.6",
"@react-oauth/google": "^0.12.1",
"@tailwindcss/vite": "^4.0.12",
"axios": "^1.8.2",
"class-variance-authority": "^0.7.1",

View File

@ -35,6 +35,9 @@ importers:
'@radix-ui/react-toast':
specifier: ^1.2.6
version: 1.2.6(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@react-oauth/google':
specifier: ^0.12.1
version: 0.12.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@tailwindcss/vite':
specifier: ^4.0.12
version: 4.0.12(vite@6.2.1(@types/node@22.13.9)(jiti@2.4.2)(lightningcss@1.29.2))
@ -806,6 +809,12 @@ packages:
'@radix-ui/rect@1.1.0':
resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
'@react-oauth/google@0.12.1':
resolution: {integrity: sha512-qagsy22t+7UdkYAiT5ZhfM4StXi9PPNvw0zuwNmabrWyMKddczMtBIOARflbaIj+wHiQjnMAsZmzsUYuXeyoSg==}
peerDependencies:
react: '>=16.8.0'
react-dom: '>=16.8.0'
'@rollup/rollup-android-arm-eabi@4.34.9':
resolution: {integrity: sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA==}
cpu: [arm]
@ -2533,6 +2542,11 @@ snapshots:
'@radix-ui/rect@1.1.0': {}
'@react-oauth/google@0.12.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
'@rollup/rollup-android-arm-eabi@4.34.9':
optional: true

View File

@ -78,3 +78,13 @@ export const isAuthenticated = (): boolean => {
export const getToken = (): string | null => {
return localStorage.getItem('token');
};
export const googleAuth = async (token: string) => {
const response = await base.post('/auth/google-auth', { token });
if (!response.data.token) {
throw new Error('Erreur lors de l\'authentification Google');
}
return response.data;
};

View File

@ -1,10 +1,12 @@
import { useState } from "react"
import { useNavigate } from "react-router-dom"
import { useGoogleLogin } from "@react-oauth/google"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { login } from "@/api/auth"
import { login, googleAuth } from "@/api/auth"
export function LoginForm({
className,
...props
@ -36,6 +38,33 @@ export function LoginForm({
}
}
const handleGoogleLogin = useGoogleLogin({
onSuccess: async (tokenResponse) => {
setLoading(true)
setError("")
try {
const response = await googleAuth(tokenResponse.access_token)
if (!response.user) {
throw new Error("Échec de la connexion")
}
localStorage.setItem("token", response.token)
navigate("/")
} catch (err) {
console.error("Erreur de connexion Google:", err)
setError(err instanceof Error ? err.message : "Une erreur est survenue")
} finally {
setLoading(false)
}
},
onError: () => {
setError("Échec de l'authentification Google")
}
})
return (
<form className={cn("flex flex-col gap-6", className)} {...props} onSubmit={handleSubmit}>
<div className="flex flex-col items-center gap-2 text-center">
@ -87,11 +116,29 @@ export function LoginForm({
Ou continuer avec
</span>
</div>
<Button variant="outline" className="w-full">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<Button
type="button"
variant="outline"
className="w-full"
onClick={() => handleGoogleLogin()}
disabled={loading}
>
<svg className="mr-2 h-4 w-4" viewBox="0 0 24 24">
<path
d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
fill="currentColor"
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
fill="#4285F4"
/>
<path
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
fill="#34A853"
/>
<path
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
fill="#FBBC05"
/>
<path
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
fill="#EA4335"
/>
</svg>
Se connecter avec Google

View File

@ -5,6 +5,8 @@ import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { register } from "@/api/auth"
import { useGoogleLogin } from '@react-oauth/google'
import { googleAuth } from "@/api/auth"
export function RegisterForm({
className,
@ -39,6 +41,30 @@ export function RegisterForm({
}
}
const handleGoogleLogin = useGoogleLogin({
onSuccess: async (tokenResponse) => {
setLoading(true);
setError("");
try {
const response = await googleAuth(tokenResponse.access_token);
if (response.token) {
localStorage.setItem("token", response.token);
}
navigate("/recipes");
} catch (err) {
setError(err instanceof Error ? err.message : "Une erreur est survenue");
} finally {
setLoading(false);
}
},
onError: (errorResponse) => {
setError("Échec de l'authentification Google");
}
});
return (
<form className={cn("flex flex-col gap-6", className)} {...props} onSubmit={handleSubmit}>
<div className="flex flex-col items-center gap-2 text-center">
@ -101,12 +127,31 @@ export function RegisterForm({
Ou continuez avec
</span>
</div>
<Button variant="outline" className="w-full">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<Button
type="button"
variant="outline"
className="w-full"
onClick={() => handleGoogleLogin()}
disabled={loading}
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" className="mr-2 h-4 w-4">
<path
d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
fill="currentColor"
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
fill="#4285F4"
/>
<path
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
fill="#34A853"
/>
<path
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
fill="#FBBC05"
/>
<path
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
fill="#EA4335"
/>
<path d="M1 1h22v22H1z" fill="none" />
</svg>
Créer un compte avec Google
</Button>

View File

@ -2,9 +2,12 @@ import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
import { GoogleOAuthProvider } from '@react-oauth/google'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
<GoogleOAuthProvider clientId={import.meta.env.VITE_GOOGLE_CLIENT_ID}>
<App />
</GoogleOAuthProvider>
</StrictMode>,
)