From ad5344c98b0b62b14b50ec51199174a03a6476fa Mon Sep 17 00:00:00 2001 From: Annie Huang Date: Thu, 12 Oct 2023 16:12:07 +0800 Subject: [PATCH] feat: github auth --- .github/workflows/ci_cd.yml | 2 + app/src/components/AuthenticationPart.vue | 7 +++ app/tailwind.config.js | 1 + docker-compose.prod.yml | 2 + docker-compose.testing.yml | 2 + docker-compose.yml | 2 + example.env | 4 +- server/package.json | 1 + server/src/api/routes/auth.js | 11 +++++ server/src/auth/githubStrategy.js | 53 +++++++++++++++++++++++ server/src/auth/passport.js | 6 +++ server/src/config.js | 4 ++ server/src/models/user.js | 4 ++ yarn.lock | 30 ++++++++++++- 14 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 server/src/auth/githubStrategy.js diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index ba73a5d..87ab406 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -40,6 +40,8 @@ jobs: env: GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }} + GITHUB_CLIENT_ID: ${{ secrets.GITHUB_CLIENT_ID }} + GITHUB_CLIENT_SECRET: ${{ secrets.GITHUB_CLIENT_SECRET }} run: docker-compose -f docker-compose.testing.yml run cypress - name: Tear Down Test Environment diff --git a/app/src/components/AuthenticationPart.vue b/app/src/components/AuthenticationPart.vue index 8ac1a14..cb2728a 100644 --- a/app/src/components/AuthenticationPart.vue +++ b/app/src/components/AuthenticationPart.vue @@ -191,6 +191,13 @@ const goBack = () => { > Google + + Github + diff --git a/app/tailwind.config.js b/app/tailwind.config.js index 6864fdc..14cfd55 100644 --- a/app/tailwind.config.js +++ b/app/tailwind.config.js @@ -24,6 +24,7 @@ export default { }, icon: { google: '#DB4437', + github: '#171515', }, }, }, diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 006532b..1883512 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -44,6 +44,8 @@ services: - MONGO_DB=odyssey-test - GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID} - GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET} + - GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID} + - GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET} base: image: lancatlin/linux-odyssey:base diff --git a/docker-compose.testing.yml b/docker-compose.testing.yml index 46ea3b0..31b78cf 100644 --- a/docker-compose.testing.yml +++ b/docker-compose.testing.yml @@ -42,6 +42,8 @@ services: - DOCKER_NETWORK=linux-odyssey-players - GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID} - GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET} + - GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID} + - GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET} db-test: image: mongo diff --git a/docker-compose.yml b/docker-compose.yml index e787faf..195f55f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -33,6 +33,8 @@ services: - HOST_PWD=${PWD} - GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID} - GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET} + - GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID} + - GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET} db: image: mongo networks: diff --git a/example.env b/example.env index 17418ff..95c204b 100644 --- a/example.env +++ b/example.env @@ -1,2 +1,4 @@ GOOGLE_CLIENT_ID= -GOOGLE_CLIENT_SECRET= \ No newline at end of file +GOOGLE_CLIENT_SECRET= +GITHUB_CLIENT_ID= +GITNUB_CLIENT_SECRET= \ No newline at end of file diff --git a/server/package.json b/server/package.json index b88362a..317b6bb 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-github2": "^0.1.12", "passport-google-oidc": "^0.1.0", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", diff --git a/server/src/api/routes/auth.js b/server/src/api/routes/auth.js index d94c3c0..4687610 100644 --- a/server/src/api/routes/auth.js +++ b/server/src/api/routes/auth.js @@ -43,4 +43,15 @@ if (enabledMethods.google) { ) } +if (enabledMethods.github) { + router.get('/github', passport.authenticate('github')) + router.get( + '/github/callback', + passport.authenticate('github', { + successRedirect: '/', + failureRedirect: '/login', + }) + ) +} + export default router diff --git a/server/src/auth/githubStrategy.js b/server/src/auth/githubStrategy.js new file mode 100644 index 0000000..7b2120a --- /dev/null +++ b/server/src/auth/githubStrategy.js @@ -0,0 +1,53 @@ +import GitHubStrategy from 'passport-github2' + +import config from '../config.js' +import User from '../models/user.js' + +function verify(accessToken, refreshToken, profile, cb) { + process.nextTick(async () => { + console.log(accessToken, refreshToken, profile) + // cb('todo') + const email = profile.emails[0]?.value + if (!email) { + cb(new Error('No email found')) + return + } + + let user = await User.findOne({ 'github.id': profile.id }) + if (!user) { + user = await User.findOne({ email }) + const github = { + id: profile.id, + displayName: profile.displayName, + } + if (!user) { + user = new User({ + email, + github, + }) + } else if (user.github?.id) { + cb(new Error('Email already used and binded to another account')) + return + } else { + user.github = github + } + await user.save() + } + + cb(null, user) + }) +} + +const enabled = config.github.clientID && config.github.clientSecret + +export default enabled + ? new GitHubStrategy( + { + clientID: config.github.clientID, + clientSecret: config.github.clientSecret, + callbackURL: `${config.baseUrl}/api/v1/auth/github/callback`, + scope: ['user:email'], + }, + verify + ) + : null diff --git a/server/src/auth/passport.js b/server/src/auth/passport.js index e2f4525..9cd9011 100644 --- a/server/src/auth/passport.js +++ b/server/src/auth/passport.js @@ -2,6 +2,7 @@ import passport from 'passport' import passwordStrategy from './passwordStrategy.js' import jwtStrategy from './jwtStrategy.js' import googleStrategy from './googleStrategy.js' +import githubStrategy from './githubStrategy.js' passport.use(passwordStrategy) passport.use(jwtStrategy) @@ -9,6 +10,7 @@ passport.use(jwtStrategy) const enabledMethods = { local: true, google: googleStrategy !== null, + github: githubStrategy !== null, } console.log('Enabled login methods', enabledMethods) @@ -17,6 +19,10 @@ if (enabledMethods.google) { passport.use(googleStrategy) } +if (enabledMethods.github) { + passport.use(githubStrategy) +} + passport.serializeUser((user, done) => { process.nextTick(() => { done(null, { diff --git a/server/src/config.js b/server/src/config.js index 9dd4f47..e987cf4 100644 --- a/server/src/config.js +++ b/server/src/config.js @@ -14,6 +14,10 @@ const config = { clientID: get('GOOGLE_CLIENT_ID', ''), clientSecret: get('GOOGLE_CLIENT_SECRET', ''), }, + github: { + clientID: get('GITHUB_CLIENT_ID', ''), + clientSecret: get('GITHUB_CLIENT_SECRET', ''), + }, } config.baseUrl = get('BASE_URL', `http://${config.host}:${config.port}`) diff --git a/server/src/models/user.js b/server/src/models/user.js index 1c3b891..5d910c7 100644 --- a/server/src/models/user.js +++ b/server/src/models/user.js @@ -17,6 +17,10 @@ const User = model('User', { id: String, displayName: String, }, + github: { + id: String, + displayName: String, + }, }) export default User diff --git a/yarn.lock b/yarn.lock index 40e7f1d..8465be9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1448,6 +1448,11 @@ base64id@2.0.0, base64id@~2.0.0: resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== +base64url@3.x.x: + version "3.0.1" + resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" + integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A== + bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" @@ -4664,6 +4669,13 @@ parseurl@~1.3.3: resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== +passport-github2@^0.1.12: + version "0.1.12" + resolved "https://registry.yarnpkg.com/passport-github2/-/passport-github2-0.1.12.tgz#a72ebff4fa52a35bc2c71122dcf470d1116f772c" + integrity sha512-3nPUCc7ttF/3HSP/k9sAXjz3SkGv5Nki84I05kSQPo01Jqq1NzJACgMblCK0fGcv9pKCG/KXU3AJRDGLqHLoIw== + dependencies: + passport-oauth2 "1.x.x" + 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" @@ -4686,6 +4698,17 @@ passport-local@^1.0.0: dependencies: passport-strategy "1.x.x" +passport-oauth2@1.x.x: + version "1.7.0" + resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.7.0.tgz#5c4766c8531ac45ffe9ec2c09de9809e2c841fc4" + 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" + passport-openidconnect@0.1.x: version "0.1.1" resolved "https://registry.yarnpkg.com/passport-openidconnect/-/passport-openidconnect-0.1.1.tgz#83921ff5f87f634079f65262dada834af1972244" @@ -5775,6 +5798,11 @@ uid-safe@~2.1.5: dependencies: random-bytes "~1.0.0" +uid2@0.0.x: + version "0.0.4" + resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.4.tgz#033f3b1d5d32505f5ce5f888b9f3b667123c0a44" + integrity sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA== + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" @@ -5838,7 +5866,7 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -utils-merge@1.0.1, utils-merge@^1.0.1: +utils-merge@1.0.1, utils-merge@1.x.x, utils-merge@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==