mirror of
https://github.com/linux-odyssey/linux-odyssey.git
synced 2026-11-05 19:20:12 +08:00
Update CI/CD workflows (#66)
**BREAKING CHANGES:** * Schema change: put user social logins to a `socialLogins` map * Original users would migrate to new schema without problem (because sharing the same email) * Docker images are moved to a new account [linuxodyssey](https://hub.docker.com/u/linuxodyssey) * Images are separate to different repo: frontend, backend, analytics, quest-base * Tags are only for versioning. Major improvements: * Deploy to production on releasing tags * Deploy to staging on main branch pushes * build docker images with versioning tags: `latest`, `main`, `v0.1.0`, `pr-63`, etc. * Add basic auth for admin dashboard. Specify username and password by env. Minor improvements: * Upgrade code review bot to use GPT 4 * Centralize all user creation in `models/userManager.js` * Update docker get image method, based on QUEST_PREFIX to generate the image name
This commit is contained in:
28 files changed
+364
-178
No files matched your search
@@ -0,0 +1,57 @@
|
||||
name: Build Docker Image
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
imageName:
|
||||
type: string
|
||||
required: true
|
||||
description: 'Name of the image to build'
|
||||
dockerfilePath:
|
||||
type: string
|
||||
required: true
|
||||
description: 'Path to the Dockerfile'
|
||||
target:
|
||||
type: string
|
||||
required: false
|
||||
description: 'Target to build'
|
||||
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME:
|
||||
required: true
|
||||
DOCKERHUB_TOKEN:
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ inputs.imageName }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build Image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ${{ inputs.dockerfilePath }}
|
||||
target: ${{ inputs.target }}
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
@@ -0,0 +1,36 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
build-backend:
|
||||
uses: ./.github/workflows/build-docker-image.yml
|
||||
with:
|
||||
imageName: 'linuxodyssey/backend'
|
||||
dockerfilePath: './server/Dockerfile'
|
||||
target: prod
|
||||
secrets: inherit
|
||||
|
||||
build-frontend:
|
||||
uses: ./.github/workflows/build-docker-image.yml
|
||||
with:
|
||||
imageName: 'linuxodyssey/frontend'
|
||||
dockerfilePath: './app/Dockerfile'
|
||||
target: prod
|
||||
secrets: inherit
|
||||
|
||||
build-analytics:
|
||||
uses: ./.github/workflows/build-docker-image.yml
|
||||
with:
|
||||
imageName: 'linuxodyssey/analytics'
|
||||
dockerfilePath: './packages/analytics/Dockerfile'
|
||||
target: prod
|
||||
secrets: inherit
|
||||
|
||||
build-base:
|
||||
uses: ./.github/workflows/build-docker-image.yml
|
||||
with:
|
||||
imageName: 'linuxodyssey/quest-base'
|
||||
dockerfilePath: './quests/Dockerfile'
|
||||
secrets: inherit
|
||||
@@ -0,0 +1,45 @@
|
||||
name: CI Checks
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
|
||||
- name: Lint
|
||||
run: yarn lint
|
||||
|
||||
- name: Test
|
||||
run: yarn test
|
||||
|
||||
e2e:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Build Test Environment, Stage 1
|
||||
run: docker-compose -f docker-compose.testing.yml build base backend app-test cypress
|
||||
|
||||
- name: Build Test Quests
|
||||
run: docker-compose -f docker-compose.testing.yml run --rm backend yarn build:quests
|
||||
|
||||
- name: Run Cypress Tests
|
||||
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
|
||||
@@ -1,110 +0,0 @@
|
||||
name: CI/CD
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
|
||||
- name: Lint
|
||||
run: yarn lint
|
||||
|
||||
- name: Test
|
||||
run: yarn test
|
||||
|
||||
e2e:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Build Test Environment, Stage 1
|
||||
run: docker-compose -f docker-compose.testing.yml build base backend app-test
|
||||
|
||||
- name: Build Test Quests
|
||||
run: docker-compose -f docker-compose.testing.yml run --rm backend yarn build:quests
|
||||
|
||||
- name: Run Cypress Tests
|
||||
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
|
||||
|
||||
build-and-push:
|
||||
needs:
|
||||
- ci
|
||||
- e2e
|
||||
if: github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build backend
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ./server/Dockerfile
|
||||
target: prod
|
||||
push: true
|
||||
tags: docker.io/${{ secrets.DOCKERHUB_USERNAME }}/linux-odyssey:backend
|
||||
|
||||
- name: Build app
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ./app/Dockerfile
|
||||
target: prod
|
||||
push: true
|
||||
tags: docker.io/${{ secrets.DOCKERHUB_USERNAME }}/linux-odyssey:app
|
||||
|
||||
- name: Build analytics
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ./packages/analytics/Dockerfile
|
||||
target: prod
|
||||
push: true
|
||||
tags: docker.io/${{ secrets.DOCKERHUB_USERNAME }}/linux-odyssey:analytics
|
||||
|
||||
- name: Build base image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ./quests/Dockerfile
|
||||
push: true
|
||||
tags: docker.io/${{ secrets.DOCKERHUB_USERNAME }}/linux-odyssey:base
|
||||
|
||||
deploy:
|
||||
needs: build-and-push
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: SSH into server and update service
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.SERVER_HOST }}
|
||||
port: ${{ secrets.SERVER_PORT }}
|
||||
username: ${{ secrets.SERVER_USER }}
|
||||
key: ${{ secrets.SERVER_SSH_KEY }}
|
||||
@@ -0,0 +1,32 @@
|
||||
name: Production Deployment
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
concurrency:
|
||||
group: production
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
ci-checks:
|
||||
uses: ./.github/workflows/ci-checks.yml
|
||||
|
||||
build:
|
||||
needs: ci-checks
|
||||
uses: ./.github/workflows/build.yml
|
||||
secrets: inherit
|
||||
|
||||
deploy:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: SSH into server and update service
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.GCP_SSH_HOST }}
|
||||
port: ${{ secrets.GCP_SSH_PORT }}
|
||||
username: ${{ secrets.GCP_SSH_USER }}
|
||||
key: ${{ secrets.GCP_SSH_PRIVATE_KEY }}
|
||||
@@ -1,11 +1,17 @@
|
||||
name: Code Review GPT
|
||||
name: Pull Request Workflow
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
run_code_review:
|
||||
ci-checks:
|
||||
uses: ./.github/workflows/ci-checks.yml
|
||||
|
||||
code_review:
|
||||
permissions:
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
@@ -18,5 +24,10 @@ jobs:
|
||||
uses: mattzcarey/code-review-gpt@v0.1.4-alpha
|
||||
with:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
MODEL: 'gpt-3.5-turbo'
|
||||
MODEL: 'gpt-4'
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
|
||||
build:
|
||||
needs: ci-checks
|
||||
uses: ./.github/workflows/build.yml
|
||||
secrets: inherit
|
||||
@@ -0,0 +1,32 @@
|
||||
name: Staging Deployment
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency:
|
||||
group: staging
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
ci-checks:
|
||||
uses: ./.github/workflows/ci-checks.yml
|
||||
|
||||
build:
|
||||
needs: ci-checks
|
||||
uses: ./.github/workflows/build.yml
|
||||
secrets: inherit
|
||||
|
||||
deploy:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: SSH into server and update service
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.SERVER_HOST }}
|
||||
port: ${{ secrets.SERVER_PORT }}
|
||||
username: ${{ secrets.SERVER_USER }}
|
||||
key: ${{ secrets.SERVER_SSH_KEY }}
|
||||
@@ -1,7 +1,7 @@
|
||||
version: '3'
|
||||
services:
|
||||
app:
|
||||
image: lancatlin/linux-odyssey:app
|
||||
image: linuxodyssey/frontend
|
||||
depends_on:
|
||||
- backend
|
||||
restart: always
|
||||
@@ -13,8 +13,9 @@ services:
|
||||
internal:
|
||||
ports:
|
||||
- 8080:80
|
||||
|
||||
backend:
|
||||
image: lancatlin/linux-odyssey:backend
|
||||
image: linuxodyssey/backend
|
||||
depends_on:
|
||||
- db
|
||||
restart: always
|
||||
@@ -36,7 +37,7 @@ services:
|
||||
- TRUSTED_PROXIES=${TRUSTED_PROXIES}
|
||||
|
||||
analytics:
|
||||
image: lancatlin/linux-odyssey:analytics
|
||||
image: linuxodyssey/analytics
|
||||
build:
|
||||
context: .
|
||||
dockerfile: packages/analytics/Dockerfile
|
||||
@@ -50,6 +51,8 @@ services:
|
||||
- '3001:3001'
|
||||
environment:
|
||||
- MONGO_URL=mongodb://${MONGO_USER}:${MONGO_PASSWORD}@db:27017/${MONGO_DB}
|
||||
- ADMIN_USERNAME=${ADMIN_USERNAME}
|
||||
- ADMIN_PASSWORD=${ADMIN_PASSWORD}
|
||||
|
||||
db:
|
||||
image: mongo
|
||||
@@ -76,7 +79,7 @@ services:
|
||||
- BUG_REPORT_URL=${BUG_REPORT_URL}
|
||||
|
||||
base:
|
||||
image: lancatlin/linux-odyssey:base
|
||||
image: linuxodyssey/quest-base
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./quests/Dockerfile
|
||||
|
||||
@@ -16,7 +16,7 @@ services:
|
||||
- app-test
|
||||
|
||||
app-test:
|
||||
image: lancatlin/linux-odyssey:app
|
||||
image: linuxodyssey/app
|
||||
build:
|
||||
context: .
|
||||
dockerfile: app/Dockerfile
|
||||
@@ -29,7 +29,7 @@ services:
|
||||
- backend
|
||||
|
||||
backend:
|
||||
image: lancatlin/linux-odyssey:backend
|
||||
image: linuxodyssey/backend
|
||||
build:
|
||||
context: .
|
||||
dockerfile: server/Dockerfile
|
||||
@@ -69,9 +69,11 @@ services:
|
||||
- MONGO_USER=user
|
||||
- MONGO_PASS=user-password
|
||||
- MONGO_DB=odyssey-test
|
||||
ports:
|
||||
- 27017:27017
|
||||
|
||||
base:
|
||||
image: lancatlin/linux-odyssey:base
|
||||
image: linuxodyssey/quest-base
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./quests/Dockerfile
|
||||
|
||||
+4
-1
@@ -14,6 +14,7 @@ services:
|
||||
volumes:
|
||||
- ./app:/app/app
|
||||
- ./packages:/app/packages
|
||||
|
||||
backend:
|
||||
depends_on:
|
||||
- db
|
||||
@@ -62,6 +63,8 @@ services:
|
||||
- '3001:3001'
|
||||
environment:
|
||||
- MONGO_URL=mongodb://db:27017/odyssey-test
|
||||
- USERNAME=user
|
||||
- PASSWORD=user
|
||||
volumes:
|
||||
- ./packages/analytics:/app/packages/analytics
|
||||
- ./packages/models:/app/packages/models
|
||||
@@ -85,7 +88,7 @@ services:
|
||||
- SWAGGER_FILE=/tmp/swagger.yaml
|
||||
|
||||
base:
|
||||
image: lancatlin/linux-odyssey:base
|
||||
image: linuxodyssey/quest-base
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./quests/Dockerfile
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"dependencies": {
|
||||
"@linux-odyssey/models": "*",
|
||||
"express": "^4.18.2",
|
||||
"express-basic-auth": "^1.2.1",
|
||||
"express-handlebars": "^7.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
function getOrFailed(key) {
|
||||
if (!process.env[key]) {
|
||||
throw new Error(`Missing required environment variable: ${key}`)
|
||||
}
|
||||
return process.env[key]
|
||||
}
|
||||
|
||||
const config = {
|
||||
host: process.env.HOST || 'localhost',
|
||||
port: process.env.PORT || 3001,
|
||||
db: process.env.MONGO_URL || 'mongodb://localhost:27017/odyssey-test',
|
||||
username: getOrFailed('ADMIN_USERNAME'),
|
||||
password: getOrFailed('ADMIN_PASSWORD'),
|
||||
}
|
||||
|
||||
config.baseUrl = process.env.BASE_URL || `http://${config.host}:${config.port}`
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import connectDB from '@linux-odyssey/models'
|
||||
import express from 'express'
|
||||
import { engine } from 'express-handlebars'
|
||||
import basicAuth from 'express-basic-auth'
|
||||
import config from './config.js'
|
||||
import router from './router.js'
|
||||
|
||||
@@ -13,6 +14,13 @@ async function main() {
|
||||
app.set('view engine', 'handlebars')
|
||||
app.set('views', './views')
|
||||
|
||||
app.use(
|
||||
basicAuth({
|
||||
users: { [config.username]: config.password },
|
||||
challenge: true,
|
||||
realm: 'Linux Odyssey Admin Dashboard',
|
||||
})
|
||||
)
|
||||
app.use(router)
|
||||
app.use(express.static('./public'))
|
||||
|
||||
|
||||
@@ -3,8 +3,7 @@ import { Session, User } from '@linux-odyssey/models'
|
||||
function loginMethods(user) {
|
||||
const methods = []
|
||||
if (user.hashedPassword) methods.push('password')
|
||||
if (user.google?.id) methods.push('google')
|
||||
if (user.github?.id) methods.push('github')
|
||||
if (user.socialLogins) methods.push(...Object.keys(user.socialLogins))
|
||||
return methods
|
||||
}
|
||||
|
||||
|
||||
+20
-7
@@ -1,5 +1,20 @@
|
||||
import { model, Schema } from 'mongoose'
|
||||
|
||||
const socialLoginSchema = new Schema({
|
||||
provider: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
displayName: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
|
||||
const User = model(
|
||||
'User',
|
||||
new Schema(
|
||||
@@ -15,13 +30,11 @@ const User = model(
|
||||
required: true,
|
||||
},
|
||||
hashedPassword: String,
|
||||
google: {
|
||||
id: String,
|
||||
displayName: String,
|
||||
},
|
||||
github: {
|
||||
id: String,
|
||||
displayName: String,
|
||||
socialLogins: {
|
||||
type: Map,
|
||||
of: socialLoginSchema,
|
||||
default: {},
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
FROM lancatlin/linux-odyssey:base
|
||||
FROM linuxodyssey/quest-base
|
||||
|
||||
COPY home ${HOME}
|
||||
@@ -1,3 +1,3 @@
|
||||
FROM lancatlin/linux-odyssey:base
|
||||
FROM linuxodyssey/quest-base
|
||||
|
||||
COPY home ${HOME}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { matchedData } from 'express-validator'
|
||||
import { User } from '@linux-odyssey/models'
|
||||
import { genJWT } from '../../utils/auth.js'
|
||||
import { createUser } from '../../models/userManager.js'
|
||||
import { asyncHandler } from '../../middleware/error.js'
|
||||
@@ -17,7 +16,7 @@ export const issueToken = asyncHandler(async (req, res) => {
|
||||
export const register = asyncHandler(async (req, res, next) => {
|
||||
const { username, password, email } = matchedData(req)
|
||||
|
||||
const user = await createUser(username, email, password)
|
||||
const user = await createUser(username, email, { password })
|
||||
req.login(user, (err) => {
|
||||
if (err) {
|
||||
next(err)
|
||||
@@ -50,7 +49,7 @@ export function checkSession(req, res) {
|
||||
})
|
||||
}
|
||||
|
||||
export function socialLogin(req, res) {
|
||||
export function socialLoginHandler(req, res) {
|
||||
const { newUser } = req.user
|
||||
if (newUser) {
|
||||
req.session.newUser = newUser
|
||||
@@ -71,10 +70,13 @@ export const registerFromSession = asyncHandler(async (req, res, next) => {
|
||||
return
|
||||
}
|
||||
|
||||
newUser.username = username
|
||||
const user = new User(newUser)
|
||||
const { email, socialLogin } = newUser
|
||||
|
||||
await user.save()
|
||||
if (!socialLogin) {
|
||||
throw new Error('Socail Login not found')
|
||||
}
|
||||
|
||||
const user = await createUser(username, email, { socialLogin })
|
||||
|
||||
req.login(user, (err) => {
|
||||
if (err) {
|
||||
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
register,
|
||||
checkSession,
|
||||
logout,
|
||||
socialLogin,
|
||||
registerFromSession,
|
||||
socialLoginHandler,
|
||||
} from '../controllers/authController.js'
|
||||
import {
|
||||
checkUsernameValidators,
|
||||
@@ -49,7 +49,7 @@ if (enabledMethods.google) {
|
||||
'/google/callback',
|
||||
authenticateRateLimit,
|
||||
passport.authenticate('google'),
|
||||
socialLogin
|
||||
socialLoginHandler
|
||||
)
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ if (enabledMethods.github) {
|
||||
'/github/callback',
|
||||
authenticateRateLimit,
|
||||
passport.authenticate('github'),
|
||||
socialLogin
|
||||
socialLoginHandler
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import config from '../config.js'
|
||||
import oauthVerify from './oauthVerify.js'
|
||||
|
||||
function verify(accessToken, refreshToken, profile, cb) {
|
||||
oauthVerify('github', profile, { 'github.id': profile.id }, cb)
|
||||
oauthVerify('github', profile, { 'socialLogins.github.id': profile.id }, cb)
|
||||
}
|
||||
|
||||
const enabled = config.github.clientID && config.github.clientSecret
|
||||
|
||||
@@ -4,7 +4,7 @@ import config from '../config.js'
|
||||
import oauthVerify from './oauthVerify.js'
|
||||
|
||||
function verify(issuer, profile, cb) {
|
||||
oauthVerify('google', profile, { 'google.id': profile.id }, cb)
|
||||
oauthVerify('google', profile, { 'socialLogins.google.id': profile.id }, cb)
|
||||
}
|
||||
|
||||
const enabled = config.google.clientID && config.google.clientSecret
|
||||
|
||||
@@ -2,26 +2,31 @@ import { User } from '@linux-odyssey/models'
|
||||
|
||||
export default async function oauthVerify(provider, profile, query, cb) {
|
||||
process.nextTick(async () => {
|
||||
const email = profile.emails[0]?.value
|
||||
if (!email) {
|
||||
return cb(new Error('No email found'))
|
||||
}
|
||||
|
||||
let user = await User.findOne(query)
|
||||
if (!user) {
|
||||
user = await User.findOne({ email })
|
||||
if (!user) {
|
||||
const newUser = { email }
|
||||
newUser[provider] = profile
|
||||
return cb(null, { newUser })
|
||||
try {
|
||||
const email = profile.emails[0]?.value
|
||||
if (!email) {
|
||||
return cb(new Error('No email found'))
|
||||
}
|
||||
user[provider] = {
|
||||
|
||||
const socialLogin = {
|
||||
provider,
|
||||
id: profile.id,
|
||||
displayName: profile.displayName,
|
||||
}
|
||||
await user.save()
|
||||
}
|
||||
let user = await User.findOne(query)
|
||||
if (!user) {
|
||||
user = await User.findOne({ email })
|
||||
if (!user) {
|
||||
const newUser = { email, socialLogin }
|
||||
return cb(null, { newUser })
|
||||
}
|
||||
user.socialLogins.set(provider, socialLogin)
|
||||
await user.save()
|
||||
}
|
||||
|
||||
return cb(null, user)
|
||||
return cb(null, user)
|
||||
} catch (err) {
|
||||
return cb(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -26,14 +26,18 @@ if (enabledMethods.github) {
|
||||
|
||||
passport.serializeUser((user, done) => {
|
||||
process.nextTick(async () => {
|
||||
if (!(await UserProfile.exists({ user: user._id }))) {
|
||||
UserProfile.create({ user: user._id })
|
||||
try {
|
||||
if (user._id && !(await UserProfile.exists({ user: user._id }))) {
|
||||
await UserProfile.create({ user: user._id })
|
||||
}
|
||||
done(null, {
|
||||
_id: user._id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
})
|
||||
} catch (err) {
|
||||
done(err)
|
||||
}
|
||||
done(null, {
|
||||
_id: user._id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
+10
-3
@@ -24,7 +24,6 @@ const config = {
|
||||
port: get('PORT', 3000),
|
||||
db: get('MONGO_URL', 'mongodb://localhost:27017/odyssey-test'),
|
||||
secret: get('SECRET_KEY', ''),
|
||||
dockerNetwork: get('DOCKER_NETWORK', 'linux-odyssey-players'),
|
||||
isProduction: process.env.NODE_ENV === 'production',
|
||||
hostPwd: get('HOST_PWD', ''),
|
||||
containerExpiry: get('EXPIRY', 1000 * 60 * 60),
|
||||
@@ -40,9 +39,13 @@ const config = {
|
||||
trustedProxies: getTrustProxies('TRUSTED_PROXIES'),
|
||||
surveyUrl: getUrl('SURVEY_URL', ''),
|
||||
bugReportUrl: getUrl('BUG_REPORT_URL', ''),
|
||||
dockerImage: get('DOCKER_IMAGE', 'lancatlin/linux-odyssey'),
|
||||
|
||||
mountQuest: get('MOUNT_QUEST', ''),
|
||||
docker: {
|
||||
network: get('DOCKER_NETWORK', 'linux-odyssey-players'),
|
||||
defaultImage: get('QUEST_IMAGE', 'linuxodyssey/quest-base'),
|
||||
imagePrefix: get('DOCKER_PREFIX', 'linuxodyssey/quest-'),
|
||||
mountQuest: get('MOUNT_QUEST', ''),
|
||||
},
|
||||
}
|
||||
|
||||
config.baseUrl = get('BASE_URL', `http://${config.host}:${config.port}`)
|
||||
@@ -52,4 +55,8 @@ config.testing = {
|
||||
password: get('TESTING_PASSWORD', ''),
|
||||
}
|
||||
|
||||
export function getQuestImage(id) {
|
||||
return `${config.docker.imagePrefix}${id}`
|
||||
}
|
||||
|
||||
export default config
|
||||
@@ -1,5 +1,5 @@
|
||||
import Docker from 'dockerode'
|
||||
import config from '../config.js'
|
||||
import config, { getQuestImage } from '../config.js'
|
||||
|
||||
const engine = new Docker()
|
||||
|
||||
@@ -11,17 +11,17 @@ const containerOptions = {
|
||||
OpenStdin: true,
|
||||
StdinOnce: false,
|
||||
HostConfig: {
|
||||
NetworkMode: config.dockerNetwork,
|
||||
NetworkMode: config.docker.network,
|
||||
},
|
||||
}
|
||||
|
||||
// const network = engine.getNetwork(config.dockerNetwork)
|
||||
|
||||
export function createContainer(name, tag) {
|
||||
export function createContainer(name, questId) {
|
||||
const option = {
|
||||
...containerOptions,
|
||||
name,
|
||||
Image: `${config.dockerImage}:${tag}`,
|
||||
Image: getQuestImage(questId),
|
||||
}
|
||||
if (!config.isProduction && config.hostPwd && config.mountQuest) {
|
||||
option.HostConfig.Bind = [
|
||||
@@ -87,7 +87,7 @@ export function buildQuestImage(questPath, questId) {
|
||||
src: ['Dockerfile', 'home'],
|
||||
},
|
||||
{
|
||||
t: `${config.dockerImage}:${questId}`,
|
||||
t: getQuestImage(questId),
|
||||
networkmode: 'none',
|
||||
memory: 10 * 1e6,
|
||||
},
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
import { User, UserProfile } from '@linux-odyssey/models'
|
||||
import { hashPassword } from '../utils/auth.js'
|
||||
|
||||
export async function createUser(username, email, password) {
|
||||
export async function createUser(username, email, { password, socialLogin }) {
|
||||
const user = new User({
|
||||
username,
|
||||
email,
|
||||
})
|
||||
user.hashedPassword = await hashPassword(password)
|
||||
if (password) {
|
||||
user.hashedPassword = await hashPassword(password)
|
||||
} else if (socialLogin) {
|
||||
user.socialLogins.set(socialLogin.provider, socialLogin)
|
||||
} else {
|
||||
throw new Error('No password or social login provided')
|
||||
}
|
||||
await user.save()
|
||||
|
||||
const userProfile = new UserProfile({
|
||||
user,
|
||||
})
|
||||
await userProfile.save()
|
||||
|
||||
return user
|
||||
}
|
||||
@@ -17,6 +17,6 @@ export default async function setupTest() {
|
||||
return
|
||||
}
|
||||
|
||||
await createUser(username, email, password)
|
||||
await createUser(username, email, { password })
|
||||
console.log('Created testing account')
|
||||
}
|
||||
@@ -1593,6 +1593,13 @@ base64url@3.x.x:
|
||||
resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d"
|
||||
integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==
|
||||
|
||||
basic-auth@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a"
|
||||
integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==
|
||||
dependencies:
|
||||
safe-buffer "5.1.2"
|
||||
|
||||
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"
|
||||
@@ -2850,6 +2857,13 @@ expect@^29.6.1:
|
||||
jest-message-util "^29.6.1"
|
||||
jest-util "^29.6.1"
|
||||
|
||||
express-basic-auth@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/express-basic-auth/-/express-basic-auth-1.2.1.tgz#d31241c03a915dd55db7e5285573049cfcc36381"
|
||||
integrity sha512-L6YQ1wQ/mNjVLAmK3AG1RK6VkokA1BIY6wmiH304Xtt/cLTps40EusZsU1Uop+v9lTDPxdtzbFmdXfFO3KEnwA==
|
||||
dependencies:
|
||||
basic-auth "^2.0.1"
|
||||
|
||||
express-handlebars@^7.1.2:
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/express-handlebars/-/express-handlebars-7.1.2.tgz#2471673d11af46f496cba4098a705f0217232fda"
|
||||
@@ -5581,6 +5595,11 @@ rxjs@^7.5.1:
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
safe-buffer@5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||
|
||||
safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
|
||||
Reference in new issue
Block a user