From 04d288b84473c8d1810841a5a5b763a8eeddbc72 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 21 Jun 2023 19:10:18 +0900 Subject: [PATCH] Add google signin/signup --- back/package-lock.json | 147 ++++++++++++++++++ back/package.json | 2 + .../20230621090510_google/migration.sql | 12 ++ back/prisma/schema.prisma | 3 +- back/src/auth/auth.controller.ts | 17 ++ back/src/auth/auth.module.ts | 3 +- back/src/auth/auth.service.ts | 2 +- back/src/auth/google.strategy.ts | 34 ++++ back/src/users/users.service.ts | 3 +- front/i18n/Translations.ts | 4 +- front/views/AuthenticationView.tsx | 7 + 11 files changed, 229 insertions(+), 5 deletions(-) create mode 100644 back/prisma/migrations/20230621090510_google/migration.sql create mode 100644 back/src/auth/google.strategy.ts diff --git a/back/package-lock.json b/back/package-lock.json index a41cbe8..017de09 100644 --- a/back/package-lock.json +++ b/back/package-lock.json @@ -24,6 +24,7 @@ "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.13.2", + "passport-google-oauth20": "^2.0.0", "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", "reflect-metadata": "^0.1.13", @@ -38,6 +39,7 @@ "@types/express": "^4.17.13", "@types/jest": "27.4.1", "@types/node": "^16.0.0", + "@types/passport-google-oauth20": "^2.0.11", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", @@ -1962,6 +1964,15 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.33.tgz", "integrity": "sha512-0PJ0vg+JyU0MIan58IOIFRtSvsb7Ri+7Wltx2qAg94eMOrpg4+uuP3aUHCpxXc1i0jCXiC+zIamSZh3l9AbcQA==" }, + "node_modules/@types/oauth": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@types/oauth/-/oauth-0.9.1.tgz", + "integrity": "sha512-a1iY62/a3yhZ7qH7cNUsxoI3U/0Fe9+RnuFrpTKr+0WVOzbKlSLojShCKe20aOD1Sppv+i8Zlq0pLDuTJnwS4A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -1976,6 +1987,28 @@ "@types/express": "*" } }, + "node_modules/@types/passport-google-oauth20": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/passport-google-oauth20/-/passport-google-oauth20-2.0.11.tgz", + "integrity": "sha512-9XMT1GfwhZL7UQEiCepLef55RNPHkbrCtsU7rsWPTEOsmu5qVIW8nSemtB4p+P24CuOhA+IKkv8LsPThYghGww==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/passport": "*", + "@types/passport-oauth2": "*" + } + }, + "node_modules/@types/passport-oauth2": { + "version": "1.4.12", + "resolved": "https://registry.npmjs.org/@types/passport-oauth2/-/passport-oauth2-1.4.12.tgz", + "integrity": "sha512-RZg6cYTyEGinrZn/7REYQds6zrTxoBorX1/fdaz5UHzkG8xdFE7QQxkJagCr2ETzGII58FAFDmnmbTUVMrltNA==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/oauth": "*", + "@types/passport": "*" + } + }, "node_modules/@types/prettier": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.6.0.tgz", @@ -2763,6 +2796,14 @@ } ] }, + "node_modules/base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/bcryptjs": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", @@ -6870,6 +6911,11 @@ "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", "dev": true }, + "node_modules/oauth": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -7113,6 +7159,17 @@ "url": "https://github.com/sponsors/jaredhanson" } }, + "node_modules/passport-google-oauth20": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz", + "integrity": "sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ==", + "dependencies": { + "passport-oauth2": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/passport-jwt": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.0.tgz", @@ -7133,6 +7190,25 @@ "node": ">= 0.4.0" } }, + "node_modules/passport-oauth2": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.7.0.tgz", + "integrity": "sha512-j2gf34szdTF2Onw3+76alNnaAExlUmHvkc7cL+cmaS5NzHzDP/BvFHJruueQ9XAeNOdpI+CH+PWid8RA7KCwAQ==", + "dependencies": { + "base64url": "3.x.x", + "oauth": "0.9.x", + "passport-strategy": "1.x.x", + "uid2": "0.0.x", + "utils-merge": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, "node_modules/passport-strategy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", @@ -8775,6 +8851,11 @@ "node": ">=4.2.0" } }, + "node_modules/uid2": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", + "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==" + }, "node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -10691,6 +10772,15 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.33.tgz", "integrity": "sha512-0PJ0vg+JyU0MIan58IOIFRtSvsb7Ri+7Wltx2qAg94eMOrpg4+uuP3aUHCpxXc1i0jCXiC+zIamSZh3l9AbcQA==" }, + "@types/oauth": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@types/oauth/-/oauth-0.9.1.tgz", + "integrity": "sha512-a1iY62/a3yhZ7qH7cNUsxoI3U/0Fe9+RnuFrpTKr+0WVOzbKlSLojShCKe20aOD1Sppv+i8Zlq0pLDuTJnwS4A==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -10705,6 +10795,28 @@ "@types/express": "*" } }, + "@types/passport-google-oauth20": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/passport-google-oauth20/-/passport-google-oauth20-2.0.11.tgz", + "integrity": "sha512-9XMT1GfwhZL7UQEiCepLef55RNPHkbrCtsU7rsWPTEOsmu5qVIW8nSemtB4p+P24CuOhA+IKkv8LsPThYghGww==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/passport": "*", + "@types/passport-oauth2": "*" + } + }, + "@types/passport-oauth2": { + "version": "1.4.12", + "resolved": "https://registry.npmjs.org/@types/passport-oauth2/-/passport-oauth2-1.4.12.tgz", + "integrity": "sha512-RZg6cYTyEGinrZn/7REYQds6zrTxoBorX1/fdaz5UHzkG8xdFE7QQxkJagCr2ETzGII58FAFDmnmbTUVMrltNA==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/oauth": "*", + "@types/passport": "*" + } + }, "@types/prettier": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.6.0.tgz", @@ -11301,6 +11413,11 @@ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true }, + "base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==" + }, "bcryptjs": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", @@ -14452,6 +14569,11 @@ "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", "dev": true }, + "oauth": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==" + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -14624,6 +14746,14 @@ "utils-merge": "^1.0.1" } }, + "passport-google-oauth20": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz", + "integrity": "sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ==", + "requires": { + "passport-oauth2": "1.x.x" + } + }, "passport-jwt": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.0.tgz", @@ -14641,6 +14771,18 @@ "passport-strategy": "1.x.x" } }, + "passport-oauth2": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.7.0.tgz", + "integrity": "sha512-j2gf34szdTF2Onw3+76alNnaAExlUmHvkc7cL+cmaS5NzHzDP/BvFHJruueQ9XAeNOdpI+CH+PWid8RA7KCwAQ==", + "requires": { + "base64url": "3.x.x", + "oauth": "0.9.x", + "passport-strategy": "1.x.x", + "uid2": "0.0.x", + "utils-merge": "1.x.x" + } + }, "passport-strategy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", @@ -15831,6 +15973,11 @@ "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==", "dev": true }, + "uid2": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", + "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==" + }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", diff --git a/back/package.json b/back/package.json index 2be88c8..217d059 100644 --- a/back/package.json +++ b/back/package.json @@ -36,6 +36,7 @@ "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.13.2", + "passport-google-oauth20": "^2.0.0", "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", "reflect-metadata": "^0.1.13", @@ -50,6 +51,7 @@ "@types/express": "^4.17.13", "@types/jest": "27.4.1", "@types/node": "^16.0.0", + "@types/passport-google-oauth20": "^2.0.11", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", diff --git a/back/prisma/migrations/20230621090510_google/migration.sql b/back/prisma/migrations/20230621090510_google/migration.sql new file mode 100644 index 0000000..b4bb7c2 --- /dev/null +++ b/back/prisma/migrations/20230621090510_google/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - A unique constraint covering the columns `[googleID]` on the table `User` will be added. If there are existing duplicate values, this will fail. + +*/ +-- AlterTable +ALTER TABLE "User" ADD COLUMN "googleID" TEXT, +ALTER COLUMN "password" DROP NOT NULL; + +-- CreateIndex +CREATE UNIQUE INDEX "User_googleID_key" ON "User"("googleID"); diff --git a/back/prisma/schema.prisma b/back/prisma/schema.prisma index bebd22c..8eeed4a 100644 --- a/back/prisma/schema.prisma +++ b/back/prisma/schema.prisma @@ -12,8 +12,9 @@ datasource db { model User { id Int @id @default(autoincrement()) username String @unique - password String + password String? email String + googleID String? @unique isGuest Boolean @default(false) partyPlayed Int @default(0) LessonHistory LessonHistory[] diff --git a/back/src/auth/auth.controller.ts b/back/src/auth/auth.controller.ts index db904d8..bd69b01 100644 --- a/back/src/auth/auth.controller.ts +++ b/back/src/auth/auth.controller.ts @@ -12,6 +12,7 @@ import { InternalServerErrorException, Patch, NotFoundException, + Req, } from '@nestjs/common'; import { AuthService } from './auth.service'; import { JwtAuthGuard } from './jwt-auth.guard'; @@ -32,6 +33,7 @@ import { Profile } from './dto/profile.dto'; import { Setting } from 'src/models/setting'; import { UpdateSettingDto } from 'src/settings/dto/update-setting.dto'; import { SettingsService } from 'src/settings/settings.service'; +import { AuthGuard } from '@nestjs/passport'; @ApiTags('auth') @Controller('auth') @@ -42,6 +44,21 @@ export class AuthController { private settingsService: SettingsService, ) {} + @Get("login/google") + @UseGuards(AuthGuard('google')) + googleLogin() { } + + @Get("logged/google") + @UseGuards(AuthGuard('google')) + async googleLoginCallbakc(@Req() req: any) { + let user = await this.usersService.user({googleID: req.id}); + if (!user) { + user = await this.usersService.createUser(req) + await this.settingsService.createUserSetting(user.id); + } + return this.authService.login(user); + } + @Post('register') async register(@Body() registerDto: RegisterDto): Promise { try { diff --git a/back/src/auth/auth.module.ts b/back/src/auth/auth.module.ts index 7ac9c82..c5fee1f 100644 --- a/back/src/auth/auth.module.ts +++ b/back/src/auth/auth.module.ts @@ -9,6 +9,7 @@ import { ConfigModule } from '@nestjs/config'; import { ConfigService } from '@nestjs/config'; import { JwtStrategy } from './jwt.strategy'; import { SettingsModule } from 'src/settings/settings.module'; +import { GoogleStrategy } from './google.strategy'; @Module({ imports: [ @@ -25,7 +26,7 @@ import { SettingsModule } from 'src/settings/settings.module'; inject: [ConfigService], }), ], - providers: [AuthService, LocalStrategy, JwtStrategy], + providers: [AuthService, LocalStrategy, JwtStrategy, GoogleStrategy], controllers: [AuthController], }) export class AuthModule {} diff --git a/back/src/auth/auth.service.ts b/back/src/auth/auth.service.ts index 1568cd8..c733207 100644 --- a/back/src/auth/auth.service.ts +++ b/back/src/auth/auth.service.ts @@ -15,7 +15,7 @@ export class AuthService { password: string, ): Promise { const user = await this.userService.user({ username }); - if (user && bcrypt.compareSync(password, user.password)) { + if (user && user.password && bcrypt.compareSync(password, user.password)) { return { username: user.username, id: user.id, diff --git a/back/src/auth/google.strategy.ts b/back/src/auth/google.strategy.ts new file mode 100644 index 0000000..9fb2406 --- /dev/null +++ b/back/src/auth/google.strategy.ts @@ -0,0 +1,34 @@ +import { PassportStrategy } from '@nestjs/passport'; +import { Strategy, VerifyCallback } from 'passport-google-oauth20'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class GoogleStrategy extends PassportStrategy(Strategy) { + constructor() { + super({ + clientID: process.env.GOOGLE_CLIENT_ID, + clientSecret: process.env.GOOGLE_SECRET, + callbackURL: 'http://localhost:3000/google/redirect', + scope: ['email', 'profile'], + }); + } + + async validate( + _accessToken: string, + _refreshToken: string, + profile: any, + done: VerifyCallback, + ): Promise { + console.log(profile); + const { name, emails, photos, username } = profile; + const user = { + email: emails[0].value, + username, + password: null, + // firstName: name.givenName, + // lastName: name.familyName, + // picture: photos[0].value, + }; + done(null, user); + } +} diff --git a/back/src/users/users.service.ts b/back/src/users/users.service.ts index 0bcfab8..1cbfc20 100644 --- a/back/src/users/users.service.ts +++ b/back/src/users/users.service.ts @@ -34,7 +34,8 @@ export class UsersService { } async createUser(data: Prisma.UserCreateInput): Promise { - data.password = await bcrypt.hash(data.password, 8); + if (data.password) + data.password = await bcrypt.hash(data.password, 8); return this.prisma.user.create({ data, }); diff --git a/front/i18n/Translations.ts b/front/i18n/Translations.ts index 014ed86..96f33cb 100644 --- a/front/i18n/Translations.ts +++ b/front/i18n/Translations.ts @@ -7,6 +7,7 @@ export const en = { signOutBtn: 'Sign out', signInBtn: 'Sign in', signUpBtn: 'Sign up', + continuewithgoogle: 'Continue with Google', changeLanguageBtn: 'Change language', search: 'Search', login: 'Login', @@ -189,6 +190,7 @@ export const fr: typeof en = { welcomeMessage: 'Re-Bonjour ', signOutBtn: 'Se déconnecter', signInBtn: 'Se connecter', + continuewithgoogle: 'Continuer avec Google', changeLanguageBtn: 'Changer la langue', searchBtn: 'Rechercher', playBtn: 'Jouer', @@ -362,7 +364,7 @@ export const fr: typeof en = { noRecentSearches: 'Aucune recherche récente', }; -export const sp: typeof en = { +export const sp: Partial = { error: 'Error', anErrorOccured: 'ocurrió un error', goBackHome: 'regresar a casa', diff --git a/front/views/AuthenticationView.tsx b/front/views/AuthenticationView.tsx index a0a463c..df8eee4 100644 --- a/front/views/AuthenticationView.tsx +++ b/front/views/AuthenticationView.tsx @@ -78,6 +78,13 @@ const AuthenticationView = ({ isSignup }: RouteProps) = {translate('forgottenPassword')} )} + window.location.href = "/api/login/google"} + />