From 44b3304046d4458e93356aa6587021818420ef5e Mon Sep 17 00:00:00 2001 From: "Lin, Hung-Shin" Date: Wed, 4 Oct 2023 13:23:16 +0800 Subject: [PATCH 1/9] feat: login to google success --- docker-compose.yml | 2 ++ docker-scripts.sh | 2 +- server/package.json | 1 + server/src/api/routes/auth.js | 10 ++++++++++ server/src/auth/googleStrategy.js | 18 ++++++++++++++++++ server/src/auth/passport.js | 10 ++++++---- server/src/config.js | 4 ++++ yarn.lock | 20 ++++++++++++++++++++ 8 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 server/src/auth/googleStrategy.js diff --git a/docker-compose.yml b/docker-compose.yml index 1a637f5..2c964a0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -26,6 +26,8 @@ services: - ./server/swagger.yaml:/app/server/swagger.yaml - ./packages:/app/packages - ./quests:/app/quests + env_file: + - .env environment: - MONGO_URL=mongodb://db:27017/odyssey-test - DOCKER_NETWORK=linux-odyssey-players diff --git a/docker-scripts.sh b/docker-scripts.sh index 7fcb8c3..88604d1 100755 --- a/docker-scripts.sh +++ b/docker-scripts.sh @@ -2,7 +2,7 @@ inspect() { # default to app, allow overriding by $1 - SERVICE=${1:-app} + SERVICE=${1:-backend} echo "Running docker-compose logs for service [$SERVICE]..." docker compose logs $SERVICE -f --no-log-prefix } diff --git a/server/package.json b/server/package.json index 60519fb..b88362a 100644 --- a/server/package.json +++ b/server/package.json @@ -22,6 +22,7 @@ "mongoose": "^7.4.0", "node-schedule": "^2.1.1", "passport": "^0.6.0", + "passport-google-oidc": "^0.1.0", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", "socket.io": "^4.7.1", diff --git a/server/src/api/routes/auth.js b/server/src/api/routes/auth.js index fb52e09..73140ff 100644 --- a/server/src/api/routes/auth.js +++ b/server/src/api/routes/auth.js @@ -26,4 +26,14 @@ router.get('/check-username', checkUsername) router.get('/check-session', checkSession) +router.get('/google', passport.authenticate('google')) + +router.get( + '/google/callback', + passport.authenticate('google', { + successRedirect: '/', + failureRedirect: '/login', + }) +) + export default router diff --git a/server/src/auth/googleStrategy.js b/server/src/auth/googleStrategy.js new file mode 100644 index 0000000..c126893 --- /dev/null +++ b/server/src/auth/googleStrategy.js @@ -0,0 +1,18 @@ +import GoogleStrategy from 'passport-google-oidc' + +import config from '../config.js' + +console.log('Google config:', config.google) + +export default new GoogleStrategy( + { + clientID: config.google.clientID, + clientSecret: config.google.clientSecret, + callbackURL: '/api/v1/auth/google/callback', + scope: ['profile', 'email'], + }, + function verify(issuer, profile, cb) { + console.log('Get a google login:', profile) + return cb(null, profile) + } +) diff --git a/server/src/auth/passport.js b/server/src/auth/passport.js index 068050e..682fd86 100644 --- a/server/src/auth/passport.js +++ b/server/src/auth/passport.js @@ -1,9 +1,11 @@ import passport from 'passport' -import PasswordStrategy from './passwordStrategy.js' -import JwtStrategy from './jwtStrategy.js' +import passwordStrategy from './passwordStrategy.js' +import jwtStrategy from './jwtStrategy.js' +import googleStrategy from './googleStrategy.js' -passport.use(PasswordStrategy) -passport.use(JwtStrategy) +passport.use(passwordStrategy) +passport.use(jwtStrategy) +passport.use(googleStrategy) passport.serializeUser((user, done) => { process.nextTick(() => { diff --git a/server/src/config.js b/server/src/config.js index 1bda384..78377cd 100644 --- a/server/src/config.js +++ b/server/src/config.js @@ -10,4 +10,8 @@ export default { hostPwd: get('HOST_PWD', ''), containerExpiry: get('EXPIRY', 1000 * 60 * 60), sessionMaxAge: get('SESSION_MAX_AGE', 1000 * 60 * 60 * 24 * 7), + google: { + clientID: get('GOOGLE_CLIENT_ID', ''), + clientSecret: get('GOOGLE_CLIENT_SECRET', ''), + }, } diff --git a/yarn.lock b/yarn.lock index a85225b..40e7f1d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4489,6 +4489,11 @@ nth-check@^2.0.1: dependencies: boolbase "^1.0.0" +oauth@0.9.x: + version "0.9.15" + resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1" + integrity sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA== + object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -4659,6 +4664,13 @@ parseurl@~1.3.3: resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== +passport-google-oidc@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/passport-google-oidc/-/passport-google-oidc-0.1.0.tgz#585790ba03864c0238cb89912d7e2a958086b5b0" + integrity sha512-/TtFXaWvmubm5kXNoJMyzBfxhnZ0lnBPA6w6rmQMP9klmHZf0ArE8IrIEt3yAHoiDzGx4eTO7YasKQFbPsNtVA== + dependencies: + passport-openidconnect "0.1.x" + passport-jwt@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/passport-jwt/-/passport-jwt-4.0.1.tgz#c443795eff322c38d173faa0a3c481479646ec3d" @@ -4674,6 +4686,14 @@ passport-local@^1.0.0: dependencies: passport-strategy "1.x.x" +passport-openidconnect@0.1.x: + version "0.1.1" + resolved "https://registry.yarnpkg.com/passport-openidconnect/-/passport-openidconnect-0.1.1.tgz#83921ff5f87f634079f65262dada834af1972244" + integrity sha512-r0QJiWEzwCg2MeCIXVP5G6YxVRqnEsZ2HpgKRthZ9AiQHJrgGUytXpsdcGF9BRwd3yMrEesb/uG/Yxb86rrY0g== + dependencies: + oauth "0.9.x" + passport-strategy "1.x.x" + passport-strategy@1.x.x, passport-strategy@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" From 1880b300291c8574998952cad59ccd579e5fe3ea Mon Sep 17 00:00:00 2001 From: "Lin, Hung-Shin" Date: Thu, 5 Oct 2023 11:50:00 +0800 Subject: [PATCH 2/9] feat: use google to login --- server/src/auth/googleStrategy.js | 34 ++++++++++++++++++++++++++++++- server/src/models/user.js | 7 ++++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/server/src/auth/googleStrategy.js b/server/src/auth/googleStrategy.js index c126893..f901c43 100644 --- a/server/src/auth/googleStrategy.js +++ b/server/src/auth/googleStrategy.js @@ -1,6 +1,7 @@ import GoogleStrategy from 'passport-google-oidc' import config from '../config.js' +import User from '../models/user.js' console.log('Google config:', config.google) @@ -13,6 +14,37 @@ export default new GoogleStrategy( }, function verify(issuer, profile, cb) { console.log('Get a google login:', profile) - return cb(null, profile) + + process.nextTick(async () => { + const email = profile.emails[0]?.value + if (!email) { + cb(new Error('No email found')) + return + } + + let user = await User.findOne({ 'google.id': profile.id }) + if (!user) { + user = await User.findOne({ email }) + const google = { + id: profile.id, + displayName: profile.displayName, + } + console.log('Existing user:', user) + if (!user) { + user = new User({ + email, + google, + }) + } else if (user.google?.id) { + cb(new Error('Email already used and binded to another account')) + return + } else { + user.google = google + } + await user.save() + } + + cb(null, user) + }) } ) diff --git a/server/src/models/user.js b/server/src/models/user.js index 5899e90..1c3b891 100644 --- a/server/src/models/user.js +++ b/server/src/models/user.js @@ -4,7 +4,8 @@ const User = model('User', { username: { type: String, unique: true, - required: true, + required: false, // should be required, + // but need to let social login users to pick one after login }, email: { type: String, @@ -12,6 +13,10 @@ const User = model('User', { required: true, }, hashedPassword: String, + google: { + id: String, + displayName: String, + }, }) export default User From aa29098954b264803b816beeaac43983f1c54f17 Mon Sep 17 00:00:00 2001 From: "Lin, Hung-Shin" Date: Thu, 5 Oct 2023 11:50:21 +0800 Subject: [PATCH 3/9] fix: register error message "email already exists" --- server/src/api/controllers/authController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/api/controllers/authController.js b/server/src/api/controllers/authController.js index 5fe8b2d..2d8cf15 100644 --- a/server/src/api/controllers/authController.js +++ b/server/src/api/controllers/authController.js @@ -79,7 +79,7 @@ export async function register(req, res, next) { if (await User.exists({ email })) { res.status(409).json({ type: 'email', - message: 'username already exists', + message: 'email already exists', }) return } From 9ac7fc97528414dc2a0f216bc7e4162a10d06bf5 Mon Sep 17 00:00:00 2001 From: "Lin, Hung-Shin" Date: Thu, 5 Oct 2023 12:02:23 +0800 Subject: [PATCH 4/9] feat: add login with google in frontend fix vite proxy setting --- app/src/components/AuthenticationPart.vue | 10 ++++++++++ app/tailwind.config.js | 3 +++ app/vite.config.js | 4 ++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/src/components/AuthenticationPart.vue b/app/src/components/AuthenticationPart.vue index a9f16d1..6c22147 100644 --- a/app/src/components/AuthenticationPart.vue +++ b/app/src/components/AuthenticationPart.vue @@ -167,6 +167,16 @@ const goBack = () => { > Back to Login +
+

+ Log in with social account +

+ + Google + diff --git a/app/tailwind.config.js b/app/tailwind.config.js index 403273c..6864fdc 100644 --- a/app/tailwind.config.js +++ b/app/tailwind.config.js @@ -22,6 +22,9 @@ export default { border: { DEFAULT: '#526B4E', }, + icon: { + google: '#DB4437', + }, }, }, }, diff --git a/app/vite.config.js b/app/vite.config.js index 133a5f9..9f8466e 100644 --- a/app/vite.config.js +++ b/app/vite.config.js @@ -8,11 +8,11 @@ export default defineConfig({ proxy: { '/api': { target: process.env.API_TARGET || 'http://localhost:3000', - changeOrigin: true, + changeOrigin: false, }, '/socket.io': { target: process.env.API_TARGET || 'ws://localhost:3000', - changeOrigin: true, + changeOrigin: false, ws: true, }, }, From aac15929573da6dad1f1669836d96b0045bfa45c Mon Sep 17 00:00:00 2001 From: "Lin, Hung-Shin" Date: Thu, 5 Oct 2023 12:25:42 +0800 Subject: [PATCH 5/9] fix: add env for google client id & secret --- .github/workflows/ci_cd.yml | 3 +++ docker-compose.prod.yml | 2 ++ docker-compose.testing.yml | 2 ++ 3 files changed, 7 insertions(+) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index d6420d6..ba73a5d 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -37,6 +37,9 @@ jobs: run: docker-compose -f docker-compose.testing.yml build helloworld - name: Run Cypress Tests + env: + GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} + GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }} run: docker-compose -f docker-compose.testing.yml run cypress - name: Tear Down Test Environment diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 6611c98..ebc62c1 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -20,6 +20,8 @@ services: - players volumes: - /var/run/docker.sock:/var/run/docker.sock + env_file: + - .env environment: - MONGO_URL=mongodb://user:user-password@db:27017/odyssey-test - DOCKER_NETWORK=linux-odyssey-players diff --git a/docker-compose.testing.yml b/docker-compose.testing.yml index 9232f84..658878c 100644 --- a/docker-compose.testing.yml +++ b/docker-compose.testing.yml @@ -36,6 +36,8 @@ services: - players volumes: - /var/run/docker.sock:/var/run/docker.sock + env_file: + - .env environment: - MONGO_URL=mongodb://user:user-password@db-test:27017/odyssey-test - DOCKER_NETWORK=linux-odyssey-players From a79b66ed8dae4b9833bb85e9893efe93deda649d Mon Sep 17 00:00:00 2001 From: "Lin, Hung-Shin" Date: Thu, 5 Oct 2023 13:31:15 +0800 Subject: [PATCH 6/9] fix: remove env_file field but subsistute from shell --- README.md | 15 +++++++++++++++ docker-compose.prod.yml | 2 ++ docker-compose.testing.yml | 4 ++-- docker-compose.yml | 4 ++-- example.env | 2 ++ 5 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 example.env diff --git a/README.md b/README.md index 4c91d0a..529fb1d 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,10 @@ Run everything (frontend, backend, db, swagger, quests): docker compose build docker compose up -d +If you want to enable social login, you should have OAuth client token in a `.env` file: + + docker compose --env-file .env up -d + ## Testing Run Cypress locally, using the current development containers: @@ -42,6 +46,17 @@ Run Cypress in container, create brand-new containers along with it: docker compose -f docker-compose.prod.yml pull docker compose -f docker-compose.prod.yml up -d +To enable social login, you should have OAuth client token in a `.env` file: +You can copy from `example.env` + + # .env + GOOGLE_CLIENT_ID=... + GOOGLE_CLIENT_SECRET=... + +To enable it: + + docker compose --env-file .env -f docker-compose.prod.yml up -d + ## Standalone App (without Docker) Source: `app/` diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index ebc62c1..5a23b35 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -41,6 +41,8 @@ services: - MONGO_USER=user - MONGO_PASS=user-password - MONGO_DB=odyssey-test + - GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID} + - GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET} base: image: lancatlin/linux-odyssey:base diff --git a/docker-compose.testing.yml b/docker-compose.testing.yml index 658878c..a1ad2ea 100644 --- a/docker-compose.testing.yml +++ b/docker-compose.testing.yml @@ -36,11 +36,11 @@ services: - players volumes: - /var/run/docker.sock:/var/run/docker.sock - env_file: - - .env environment: - MONGO_URL=mongodb://user:user-password@db-test:27017/odyssey-test - DOCKER_NETWORK=linux-odyssey-players + - GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID} + - GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET} db-test: image: mongo diff --git a/docker-compose.yml b/docker-compose.yml index 2c964a0..c0afae7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -26,12 +26,12 @@ services: - ./server/swagger.yaml:/app/server/swagger.yaml - ./packages:/app/packages - ./quests:/app/quests - env_file: - - .env environment: - MONGO_URL=mongodb://db:27017/odyssey-test - DOCKER_NETWORK=linux-odyssey-players - HOST_PWD=${PWD} + - GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID} + - GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET} db: image: mongo networks: diff --git a/example.env b/example.env new file mode 100644 index 0000000..17418ff --- /dev/null +++ b/example.env @@ -0,0 +1,2 @@ +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= \ No newline at end of file From 72b27113677d551f356fd77564c0ac16efb95f6a Mon Sep 17 00:00:00 2001 From: "Lin, Hung-Shin" Date: Thu, 5 Oct 2023 13:32:02 +0800 Subject: [PATCH 7/9] feat: make social login optional --- app/src/components/AuthenticationPart.vue | 9 ++- server/src/api/routes/auth.js | 23 ++++--- server/src/auth/googleStrategy.js | 82 ++++++++++++----------- server/src/auth/passport.js | 14 +++- server/swagger.yaml | 66 ++++++++++++++++++ 5 files changed, 145 insertions(+), 49 deletions(-) diff --git a/app/src/components/AuthenticationPart.vue b/app/src/components/AuthenticationPart.vue index 6c22147..5163eca 100644 --- a/app/src/components/AuthenticationPart.vue +++ b/app/src/components/AuthenticationPart.vue @@ -1,5 +1,5 @@