diff --git a/app/src/components/AuthenticationPart.vue b/app/src/components/AuthenticationPart.vue index 3d2da99..431a1b0 100644 --- a/app/src/components/AuthenticationPart.vue +++ b/app/src/components/AuthenticationPart.vue @@ -1,18 +1,141 @@ - + diff --git a/app/src/components/HeaderPart.vue b/app/src/components/HeaderPart.vue index 60e000b..ebf0ee5 100644 --- a/app/src/components/HeaderPart.vue +++ b/app/src/components/HeaderPart.vue @@ -1,4 +1,14 @@ - + diff --git a/app/src/library.js b/app/src/library.js new file mode 100644 index 0000000..f637869 --- /dev/null +++ b/app/src/library.js @@ -0,0 +1,37 @@ +/* import the fontawesome core */ +import { library } from '@fortawesome/fontawesome-svg-core' + +/* import specific icons */ +import { + faBars, + faArrowLeft, + faList, + faGear, + faTriangleExclamation, + faExpand, + faTerminal, + faArrowRotateLeft, + faArrowRightFromBracket, +} from '@fortawesome/free-solid-svg-icons' + +import { + faLightbulb, + faCircleQuestion, + faCircleRight, +} from '@fortawesome/free-regular-svg-icons' + +/* add icons to the library */ +library.add( + faBars, + faArrowLeft, + faTriangleExclamation, + faGear, + faExpand, + faList, + faLightbulb, + faCircleQuestion, + faArrowRotateLeft, + faCircleRight, + faTerminal, + faArrowRightFromBracket +) diff --git a/app/src/main.js b/app/src/main.js index 55e4c22..d5067e7 100644 --- a/app/src/main.js +++ b/app/src/main.js @@ -1,59 +1,11 @@ import { createApp } from 'vue' -import { createRouter, createWebHistory } from 'vue-router' import './style.css' - -/* import the fontawesome core */ -import { library } from '@fortawesome/fontawesome-svg-core' - /* import font awesome icon component */ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' - -/* import specific icons */ -import { - faBars, - faArrowLeft, - faList, - faGear, - faTriangleExclamation, - faExpand, - faTerminal, - faArrowRotateLeft, -} from '@fortawesome/free-solid-svg-icons' - -import { - faLightbulb, - faCircleQuestion, - faCircleRight, -} from '@fortawesome/free-regular-svg-icons' +import './library' import App from './App.vue' -import GamePage from './views/GamePage.vue' -import AuthPage from './views/AuthPage.vue' - -/* add icons to the library */ -library.add( - faBars, - faArrowLeft, - faTriangleExclamation, - faGear, - faExpand, - faList, - faLightbulb, - faCircleQuestion, - faArrowRotateLeft, - faCircleRight, - faTerminal -) - -const routes = [ - { path: '/', name: 'Home', component: AuthPage }, - { path: '/game', name: 'Game', component: GamePage }, -] - -const router = createRouter({ - history: createWebHistory(), - routes, -}) +import router from './router' const app = createApp(App) diff --git a/app/src/router.js b/app/src/router.js new file mode 100644 index 0000000..86a7894 --- /dev/null +++ b/app/src/router.js @@ -0,0 +1,50 @@ +import { createRouter, createWebHistory } from 'vue-router' +import GamePage from './views/GamePage.vue' +import AuthPage from './views/AuthPage.vue' +import { isLoggedIn } from './utils/auth' + +const routes = [ + { + path: '/', + redirect: '/game', + }, + { + path: '/login', + name: 'auth', + component: AuthPage, + meta: { requiresGuest: true }, + }, + { + path: '/game', + name: 'game', + component: GamePage, + meta: { requiresAuth: true }, + }, +] + +const router = createRouter({ + history: createWebHistory(), + routes, +}) + +router.beforeEach(async (to, from, next) => { + console.log(to, from) + const loggedIn = await isLoggedIn() + // Check if the route requires authentication + if (to.meta.requiresAuth && !loggedIn) { + // Check if the user isn't authenticated + // Redirect to the login page + next({ + name: 'auth', + query: { redirect: to.fullPath }, // Optionally pass a redirect parameter + }) + } else if (to.meta.requiresGuest && loggedIn) { + next({ + name: 'game', // Or any default authenticated user route + }) + } else { + next() // Always call next() at the end + } +}) + +export default router diff --git a/app/src/utils/api.js b/app/src/utils/api.js index 67ad944..c5f2353 100644 --- a/app/src/utils/api.js +++ b/app/src/utils/api.js @@ -2,6 +2,10 @@ import axios from 'axios' const api = axios.create({ baseURL: '/api/v1', + headers: { + 'Content-Type': 'application/json', + }, + withCredentials: true, }) export default api diff --git a/app/src/utils/auth.js b/app/src/utils/auth.js new file mode 100644 index 0000000..deb5e9d --- /dev/null +++ b/app/src/utils/auth.js @@ -0,0 +1,32 @@ +import api from './api' + +export async function isLoggedIn() { + const res = await api.get('/auth/check-session') + const { loggedIn } = res.data + return loggedIn +} + +export const login = async (username, password) => { + try { + const res = await api.post('/auth/login', { username, password }) + console.log(res.data) + return true + } catch (err) { + if (err.response?.status === 401) { + return false + } + throw err + } +} + +export const register = (username, email, password) => { + return api.post('/auth/register', { + username, + password, + email, + }) +} + +export const logout = () => { + return api.post('/auth/logout') +} diff --git a/packages/client/index.js b/packages/client/index.js index 99ec410..e03f9ca 100644 --- a/packages/client/index.js +++ b/packages/client/index.js @@ -5,6 +5,7 @@ import { io } from 'socket.io-client' import { program } from 'commander' let api = null +const COOKIE_NAME = 'connect.sid' function get(key, defaultValue) { const value = process.env[key] @@ -48,7 +49,7 @@ async function lastSession() { console.log( 'No session found. Please use --create to create a new session.' ) - exit(1) + return null } const session = sessions[sessions.length - 1] console.log( @@ -57,21 +58,29 @@ async function lastSession() { return session } -async function connect(sessionId) { +async function connect(sessionId, cookie) { console.log(`Session ID: ${sessionId}`) console.log('Use Ctrl + D to exit.') if (!sessionId) exit() const socket = io(program.opts().host, { + extraHeaders: { + Cookie: cookie, + }, query: { session_id: sessionId, }, }) - socket.on('open', function open() { + socket.on('connection', function open() { console.log('Connected to the server.') }) + socket.on('connect_error', (err) => { + console.error(err) + exit(1) + }) + socket.on('message', console.log) socket.on('terminal', function incoming(data) { @@ -101,28 +110,54 @@ async function connect(sessionId) { }) } +async function login() { + try { + const res = await api.post('/auth/login', { + username: program.opts().user, + password: program.opts().password, + }) + console.log('Login success.') + return res.headers['set-cookie'].find((cookie) => + cookie.startsWith(COOKIE_NAME) + ) + } catch (err) { + console.log('Login failed.') + console.error(err.message) + return exit(1) + } +} + async function main() { api = axios.create({ baseURL: `${program.opts().host}/api/v1`, headers: { 'Content-Type': 'application/json', }, + withCredentials: true, }) - console.log(program.opts().session) - // main() - const sessionId = - program.opts().session || - (program.opts().create ? (await createSession())._id : null) || - (await lastSession())._id + try { + const cookie = await login() + api.defaults.headers.Cookie = cookie + const sessionId = + program.opts().session || + (program.opts().create && (await createSession())?._id) || + (await lastSession())?._id || + (await createSession())?._id - await connect(sessionId) + await connect(sessionId, cookie) + } catch (err) { + console.error(err.message) + exit(1) + } } program .option('-s, --session ', 'Session ID') .option('-c, --create', 'Create a new session') .option('-d, --debug', 'Debug mode') + .option('-u, --user ', 'Username', get('USERNAME', 'alex')) + .option('-p, --password ', 'Password', get('PASSWORD', '123456')) .option( '-h, --host ', 'Server host', @@ -130,15 +165,4 @@ program ) .action(main) -program - .command('list') - .description('List all sessions') - .action(async () => { - const sessions = await getSessionList() - sessions.forEach(({ _id, quest, createdAt }) => { - console.log(`${_id} ${quest} ${createdAt}`) - }) - exit() - }) - program.parse() diff --git a/packages/container/cli.js b/packages/container/cli.js index fac7eba..a6f71d3 100644 --- a/packages/container/cli.js +++ b/packages/container/cli.js @@ -49,7 +49,6 @@ async function main() { output, error, exit_code: CMD_EXIT_CODE, - token: TOKEN, pwd: PWD, ...additionalData, } @@ -58,6 +57,7 @@ async function main() { method: 'POST', headers: { 'Content-Type': 'application/json', + Authorization: `Bearer ${TOKEN}`, }, body: JSON.stringify(payload), }) diff --git a/server/Dockerfile.dev b/server/Dockerfile.dev index 738d4ea..d6c2d35 100644 --- a/server/Dockerfile.dev +++ b/server/Dockerfile.dev @@ -6,6 +6,8 @@ COPY server/package.json ./server/package.json COPY packages/file-graph/package.json ./packages/file-graph/package.json RUN yarn install --frozen-lockfile +COPY server/nodemon.json ./server/nodemon.json + ENV NODE_ENV=development ENV HOST=0.0.0.0 ENV PORT=3000 diff --git a/server/nodemon.json b/server/nodemon.json new file mode 100644 index 0000000..cc2ba22 --- /dev/null +++ b/server/nodemon.json @@ -0,0 +1,4 @@ +{ + "watch": ["src", "swagger.yaml"], + "ext": "js,yaml" +} diff --git a/server/package.json b/server/package.json index 678672b..60519fb 100644 --- a/server/package.json +++ b/server/package.json @@ -11,13 +11,19 @@ }, "dependencies": { "@linux-odyssey/file-graph": "*", + "bcrypt": "^5.1.1", "commander": "^11.0.0", + "connect-mongo": "^5.0.0", "dockerode": "^3.3.5", "express": "^4.18.2", + "express-session": "^1.17.3", "jsonwebtoken": "^9.0.1", "minimist": "^1.2.8", "mongoose": "^7.4.0", "node-schedule": "^2.1.1", + "passport": "^0.6.0", + "passport-jwt": "^4.0.1", + "passport-local": "^1.0.0", "socket.io": "^4.7.1", "swagger-ui-express": "^5.0.0", "yaml": "^2.3.1" diff --git a/server/src/api/controllers/authController.js b/server/src/api/controllers/authController.js new file mode 100644 index 0000000..5fe8b2d --- /dev/null +++ b/server/src/api/controllers/authController.js @@ -0,0 +1,131 @@ +import User from '../../models/user.js' +import { + genJWT, + hashPassword, + isValidEmail, + isValidUsername, +} from '../../utils/auth.js' + +export async function issueToken(req, res) { + const { user } = req + const token = await genJWT({ + _id: user.id, + username: user.username, + email: user.email, + }) + res.json({ token }) +} + +export async function checkUsername(req, res) { + const { username, email } = req.query + const cred = username || email + if (!cred) { + res.status(400).json({ + message: 'username or email is required', + }) + return + } + + if (isValidEmail(cred)) { + if (await User.exists({ email: cred })) { + res.status(409).json({ + type: 'email', + message: `"${cred}" already exists`, + }) + return + } + res.status(200).json({ + type: 'email', + message: `"${cred}" is available`, + }) + return + } + if (isValidUsername(cred)) { + if (await User.exists({ username: cred })) { + res.status(409).json({ + type: 'username', + message: `"${cred}" already exists`, + }) + return + } + res.status(200).json({ + type: 'username', + message: `"${cred}" is available`, + }) + return + } + res.status(400).json({ + message: 'invalid username or email', + }) +} + +export async function register(req, res, next) { + const { username, password, email } = req.body + if (!(username && password && email)) { + res.status(400).json({ + message: 'username, password, and email are required', + }) + return + } + + if (await User.exists({ username })) { + res.status(409).json({ + type: 'username', + message: 'username already exists', + }) + return + } + + if (await User.exists({ email })) { + res.status(409).json({ + type: 'email', + message: 'username already exists', + }) + return + } + + const user = new User({ + username, + email, + }) + try { + user.hashedPassword = await hashPassword(password) + await user.save() + req.user = user + req.login(user, (err) => { + if (err) { + next(err) + return + } + res.status(201).json({ + message: 'user created', + }) + }) + } catch (err) { + console.error(err) + res.status(500).json({ + message: 'error registering user', + }) + } +} + +export function logout(req, res) { + req.logout((err) => { + if (err) { + console.error(err) + res.status(500).json({ + message: 'error logging out', + }) + return + } + res.status(200).json({ + message: 'logged out', + }) + }) +} + +export function checkSession(req, res) { + res.json({ + loggedIn: req.isAuthenticated(), + }) +} diff --git a/server/src/api/controllers/commandController.js b/server/src/api/controllers/commandController.js index fd583e1..5b3465c 100644 --- a/server/src/api/controllers/commandController.js +++ b/server/src/api/controllers/commandController.js @@ -1,26 +1,18 @@ /* eslint-disable import/prefer-default-export */ import Command from '../../models/command.js' import Session from '../../models/session.js' -import { verifySessionJWT } from '../../utils/auth.js' import CommandHandler from '../../game/commandHandler.js' export async function newCommand(req, res) { console.log('new command:', req.body) - const { token, command, pwd, output, error, ...additionalData } = req.body + const { command, pwd, output, error, ...additionalData } = req.body if (!command) { res.status(400).json({ message: 'command is required' }) return } - let sessionId - try { - const decoded = await verifySessionJWT(token) - sessionId = decoded.session_id - } catch (err) { - res.status(401).json({ message: 'token not valid' }) - return - } + const sessionId = req.user.session_id const session = await Session.findById(sessionId) diff --git a/server/src/api/controllers/sessionController.js b/server/src/api/controllers/sessionController.js index 3130288..444f1ce 100644 --- a/server/src/api/controllers/sessionController.js +++ b/server/src/api/controllers/sessionController.js @@ -3,35 +3,35 @@ import Quest from '../../models/quest.js' import { createContainer } from '../../containers/docker.js' import SessionHandler from '../../game/sessionHandler.js' +function sessionSummary(session) { + return { + _id: session._id, + user: session.user._id || session.user, + quest: session.quest._id || session.quest, + status: session.status, + createdAt: session.createdAt, + lastActivityAt: session.lastActivityAt, + } +} + +function sessionDetail(session) { + return { + ...sessionSummary(session), + hints: session.hints, + tasks: session.tasks, + graph: session.graph, + } +} + export async function getSessionList(req, res) { try { - const query = { user: req.user.id } + const query = { user: req.user._id } if (req.query.quest_id) { query.quest = req.query.quest_id } query.status = req.query.status || 'active' const sessions = await Session.find(query) - res.json( - sessions.map( - ({ - _id, - user, - quest, - status, - progress, - createdAt, - lastActivityAt, - }) => ({ - _id, - user, - quest, - status, - progress, - createdAt, - lastActivityAt, - }) - ) - ) + res.json(sessions.map(sessionSummary)) } catch (err) { console.error(err) res.status(500).json({ message: err.message }) @@ -59,7 +59,7 @@ export async function createSession(req, res) { session.addNewTasks() await newSession.save() - res.status(201).json(newSession) + res.status(201).json(sessionDetail(newSession)) } catch (err) { console.error(err) res.status(400).json({ message: err.message }) @@ -70,7 +70,7 @@ export async function getSessionById(req, res) { try { const session = await Session.findOne({ _id: req.params.id, - user: req.user.id, + user: req.user._id, }) if (!session) { @@ -78,7 +78,7 @@ export async function getSessionById(req, res) { return } - res.json(session) + res.json(sessionDetail(session)) } catch (err) { res.status(500).json({ message: err.message }) } @@ -88,7 +88,7 @@ export async function deleteSessionById(req, res) { try { const session = await Session.findOneAndDelete({ _id: req.params.id, - user: req.user.id, + user: req.user._id, }) if (!session) { diff --git a/server/src/api/routes/auth.js b/server/src/api/routes/auth.js new file mode 100644 index 0000000..fb52e09 --- /dev/null +++ b/server/src/api/routes/auth.js @@ -0,0 +1,29 @@ +import { Router } from 'express' +import passport from 'passport' + +import { + register, + checkUsername, + checkSession, + logout, +} from '../controllers/authController.js' + +const router = Router() + +router.post( + '/login', + passport.authenticate('local', { failureMessage: true }), + (req, res) => { + res.json({ message: 'success' }) + } +) + +router.post('/register', register) + +router.post('/logout', logout) + +router.get('/check-username', checkUsername) + +router.get('/check-session', checkSession) + +export default router diff --git a/server/src/api/routes/index.js b/server/src/api/routes/index.js index e7c6084..53a9c9f 100644 --- a/server/src/api/routes/index.js +++ b/server/src/api/routes/index.js @@ -1,8 +1,10 @@ import { Router } from 'express' +import passport from 'passport' import sessions from './sessions.js' import quests from './quests.js' import commands from './commands.js' -import { authMiddleware } from '../../middleware/auth.js' +import authRouter from './auth.js' +import authRequired from '../../middleware/authRequired.js' const router = Router() @@ -10,8 +12,14 @@ router.get('/', (req, res) => { res.send('Hello API!') }) -router.use('/sessions', authMiddleware, sessions) +router.use('/auth', authRouter) router.use('/quests', quests) -router.use('/commands', commands) + +router.use('/sessions', authRequired, sessions) +router.use( + '/commands', + passport.authenticate('jwt', { session: false }), + commands +) export default router diff --git a/server/src/api/socket.js b/server/src/api/socket.js index 67b9d77..df98867 100644 --- a/server/src/api/socket.js +++ b/server/src/api/socket.js @@ -1,7 +1,8 @@ import { Server } from 'socket.io' import { getAndStartContainer, attachContainer } from '../containers/docker.js' import Session from '../models/session.js' -import { defaultUser, genSessionJWT } from '../utils/auth.js' +import SessionMiddleware from '../middleware/session.js' +import { genJWT } from '../utils/auth.js' const sessions = new Map() @@ -27,6 +28,13 @@ export function pushToSession(sessionId, event, ...args) { async function connectClient(socket) { console.log('Connected to the client.') + + const user = socket.request.session?.passport?.user + if (!user) { + socket.send('User not found.') + socket.disconnect() + return + } const sessionId = socket.handshake.query.session_id if (!sessionId) { socket.send('Session ID not found.') @@ -36,7 +44,7 @@ async function connectClient(socket) { const session = await Session.findOne({ _id: sessionId, - user: socket.user, + user, status: 'active', }) if (!session) { @@ -45,7 +53,9 @@ async function connectClient(socket) { return } - const token = await genSessionJWT(session) + const token = await genJWT({ + session_id: session.id, + }) let container let stream @@ -87,18 +97,7 @@ async function connectClient(socket) { export default (server) => { const io = new Server(server) - io.use(async (socket, next) => { - console.log('Authenticating...') - try { - const user = await defaultUser() - // eslint-disable-next-line no-param-reassign - socket.user = user - next() - } catch (err) { - console.error(err) - next(err) - } - }) + io.engine.use(SessionMiddleware) io.on('connection', connectClient) } diff --git a/server/src/auth/jwtStrategy.js b/server/src/auth/jwtStrategy.js new file mode 100644 index 0000000..f1b2cd4 --- /dev/null +++ b/server/src/auth/jwtStrategy.js @@ -0,0 +1,13 @@ +import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt' +import config from '../config.js' + +const jwtOptions = { + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: config.jwtSecret, +} + +function verifyJwt(jwtPayload, done) { + return done(null, jwtPayload) +} + +export default new JwtStrategy(jwtOptions, verifyJwt) diff --git a/server/src/auth/passport.js b/server/src/auth/passport.js new file mode 100644 index 0000000..068050e --- /dev/null +++ b/server/src/auth/passport.js @@ -0,0 +1,22 @@ +import passport from 'passport' +import PasswordStrategy from './passwordStrategy.js' +import JwtStrategy from './jwtStrategy.js' + +passport.use(PasswordStrategy) +passport.use(JwtStrategy) + +passport.serializeUser((user, done) => { + process.nextTick(() => { + done(null, { + _id: user._id, + username: user.username, + email: user.email, + }) + }) +}) + +passport.deserializeUser((user, done) => { + process.nextTick(() => { + done(null, user) + }) +}) diff --git a/server/src/auth/passwordStrategy.js b/server/src/auth/passwordStrategy.js new file mode 100644 index 0000000..b89d18e --- /dev/null +++ b/server/src/auth/passwordStrategy.js @@ -0,0 +1,24 @@ +import bcrypt from 'bcrypt' +import LocalStrategy from 'passport-local' +import User from '../models/user.js' + +async function verifyPassword(username, password, done) { + try { + const user = await User.findOne({ + $or: [{ username }, { email: username }], + }) + if (!user) { + return done(null, false, { message: 'Incorrect username.' }) + } + + const isPasswordValid = await bcrypt.compare(password, user.hashedPassword) + if (!isPasswordValid) { + return done(null, false, { message: 'Incorrect username or password.' }) + } + return done(null, user) + } catch (err) { + return done(err) + } +} + +export default new LocalStrategy(verifyPassword) diff --git a/server/src/config.js b/server/src/config.js index 9190ade..1bda384 100644 --- a/server/src/config.js +++ b/server/src/config.js @@ -8,5 +8,6 @@ export default { dockerNetwork: get('DOCKER_NETWORK', 'linux-odyssey-players'), isProduction: process.env.NODE_ENV === 'production', hostPwd: get('HOST_PWD', ''), - expiry: get('EXPIRY', 1000 * 60 * 60), + containerExpiry: get('EXPIRY', 1000 * 60 * 60), + sessionMaxAge: get('SESSION_MAX_AGE', 1000 * 60 * 60 * 24 * 7), } diff --git a/server/src/containers/expiryChecker.js b/server/src/containers/expiryChecker.js index 2a24136..1f5fd73 100644 --- a/server/src/containers/expiryChecker.js +++ b/server/src/containers/expiryChecker.js @@ -6,21 +6,25 @@ import { deleteContainer } from './docker.js' export async function removeExpired() { // Find all sessions that are containerId is not null and lastActivityAt is // less than the current time minus the expiry time - console.log('Checking for expired sessions') - const sessions = await Session.find({ - containerId: { $ne: null }, - lastActivityAt: { $lt: new Date(Date.now() - config.expiry) }, - }) - await Promise.all( - sessions.map(async (session) => { - console.log('Expiring session: ', session._id, session.containerId) - await deleteContainer(session.containerId) - session.status = 'inactive' - session.containerId = null - await session.save() + try { + console.log('Checking for expired sessions') + const sessions = await Session.find({ + containerId: { $ne: null }, + lastActivityAt: { $lt: new Date(Date.now() - config.containerExpiry) }, }) - ) - console.log('Done checking for expired sessions') + await Promise.all( + sessions.map(async (session) => { + console.log('Expiring session: ', session._id, session.containerId) + await deleteContainer(session.containerId) + session.status = 'inactive' + session.containerId = null + await session.save() + }) + ) + console.log('Done checking for expired sessions') + } catch (err) { + console.error(err.message) + } } export default function expiryRemovalScheduler() { diff --git a/server/src/db.js b/server/src/db.js index 842ce6f..7522e35 100644 --- a/server/src/db.js +++ b/server/src/db.js @@ -3,10 +3,15 @@ import config from './config.js' export default async function connectDB() { try { - await mongoose.connect(config.db) + const db = await mongoose.connect(config.db, { + useNewUrlParser: true, + useUnifiedTopology: true, + }) console.log('Connected to MongoDB') + return db.connection.getClient() } catch (err) { console.error(err) process.exit(1) + return null } } diff --git a/server/src/index.js b/server/src/index.js index d7570b3..e6cf522 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -3,26 +3,43 @@ import express from 'express' import http from 'http' import YAML from 'yaml' import swaggerUI from 'swagger-ui-express' +import passport from 'passport' +import './auth/passport.js' import socketServer from './api/socket.js' import connectDB from './db.js' import apiRouter from './api/routes/index.js' import loadAndUpdateQuests from './utils/quest.js' import config from './config.js' import errorHandler from './middleware/error.js' +import sessionMiddleware from './middleware/session.js' import expiryRemovalScheduler from './containers/expiryChecker.js' +import { createTestUser } from './utils/auth.js' async function main() { - const app = express() - const server = http.createServer(app) - socketServer(server) await connectDB() + + await createTestUser() + await loadAndUpdateQuests() expiryRemovalScheduler() const file = await fs.readFile('./swagger.yaml', 'utf8') const swaggerDocument = YAML.parse(file) - app.use('/api-docs', swaggerUI.serve, swaggerUI.setup(swaggerDocument)) + + const app = express() + const server = http.createServer(app) + socketServer(server) + app.use(errorHandler) + app.use(sessionMiddleware) + app.use(passport.session()) + app.use( + '/api-docs', + swaggerUI.serve, + swaggerUI.setup(swaggerDocument, { + withCredentials: true, + }) + ) app.use(express.json()) app.get('/', (req, res) => { @@ -31,8 +48,6 @@ async function main() { app.use('/api/v1', apiRouter) - app.use(errorHandler) - server.listen(config.port, config.host, () => { console.log(`Server listening at http://${config.host}:${config.port}`) }) diff --git a/server/src/middleware/auth.js b/server/src/middleware/auth.js deleted file mode 100644 index 62bb190..0000000 --- a/server/src/middleware/auth.js +++ /dev/null @@ -1,11 +0,0 @@ -/* eslint-disable import/prefer-default-export */ -import { defaultUser } from '../utils/auth.js' - -export async function authMiddleware(req, res, next) { - try { - req.user = await defaultUser() - next() - } catch (err) { - res.status(500).json({ message: err.message }) - } -} diff --git a/server/src/middleware/authRequired.js b/server/src/middleware/authRequired.js new file mode 100644 index 0000000..8a5bd4f --- /dev/null +++ b/server/src/middleware/authRequired.js @@ -0,0 +1,9 @@ +export default function authRequired(req, res, next) { + if (req.isAuthenticated()) { + next() + return + } + res.status(401).json({ + message: 'not authenticated', + }) +} diff --git a/server/src/middleware/session.js b/server/src/middleware/session.js new file mode 100644 index 0000000..d118bb1 --- /dev/null +++ b/server/src/middleware/session.js @@ -0,0 +1,21 @@ +import session from 'express-session' +import MongoStore from 'connect-mongo' + +import config from '../config.js' + +export default session({ + secret: config.jwtSecret, + resave: false, + saveUninitialized: false, + rolling: true, + cookie: { + httpOnly: true, + secure: config.isProduction, + sameSite: 'strict', + maxAge: config.sessionMaxAge, + }, + store: MongoStore.create({ + mongoUrl: config.db, + collectionName: 'loginSessions', + }), +}) diff --git a/server/src/models/user.js b/server/src/models/user.js index cc0a1b4..5899e90 100644 --- a/server/src/models/user.js +++ b/server/src/models/user.js @@ -1,9 +1,17 @@ import { model } from 'mongoose' const User = model('User', { - username: String, - password: String, - email: String, + username: { + type: String, + unique: true, + required: true, + }, + email: { + type: String, + unique: true, + required: true, + }, + hashedPassword: String, }) export default User diff --git a/server/src/utils/auth.js b/server/src/utils/auth.js index 49d3ac6..fc8478d 100644 --- a/server/src/utils/auth.js +++ b/server/src/utils/auth.js @@ -1,25 +1,30 @@ import jwt from 'jsonwebtoken' +import bcrypt from 'bcrypt' import config from '../config.js' import User from '../models/user.js' -export async function defaultUser() { - let user = await User.findOne({ username: 'defaultUser' }) - if (!user) { - user = new User({ - username: 'defaultUser', - email: 'jackson@example.com', - }) - await user.save() - } - return user +export function hashPassword(password) { + return bcrypt.hash(password, 10) } -export function genSessionJWT(session) { +export async function createTestUser() { + let user = await User.findOne({ username: 'alex' }) + if (!user) { + user = new User({ + username: 'alex', + email: 'alex@example.com', + }) + user.hashedPassword = await hashPassword('123456') + await user.save() + } +} + +export function genJWT(payload) { return new Promise((resolve, reject) => { jwt.sign( - { session_id: session.id }, + payload, config.jwtSecret, - { expiresIn: config.expiry }, + { expiresIn: config.containerExpiry }, (err, token) => { if (err) { reject(err) @@ -31,7 +36,7 @@ export function genSessionJWT(session) { }) } -export function verifySessionJWT(token) { +export function verifyJWT(token) { return new Promise((resolve, reject) => { jwt.verify(token, config.jwtSecret, (err, decoded) => { if (err) { @@ -42,3 +47,42 @@ export function verifySessionJWT(token) { }) }) } + +export function isValidUsername(username) { + // Define a regular expression pattern for a valid Linux username. + const usernamePattern = /^[a-z][a-z0-9_-]*$/ + + // Test the username against the pattern. + if (!usernamePattern.test(username)) return false + + // check if the username is between 1 and 32 characters + if (username.length < 1 || username.length > 32) { + return false + } + + const reservedWords = [ + 'root', + 'admin', + 'administrator', + 'sysadmin', + 'system', + 'daemon', + 'bin', + 'nobody', + 'nogroup', + 'guest', + 'user', + ] + if (reservedWords.includes(username)) { + return false + } + return true +} + +export function isValidEmail(email) { + // Define a regular expression pattern for a valid email address. + const emailPattern = /^[\w-.+]+@([\w][\w-]*\.)+[\w-]{2,}$/ + + // Test the email against the pattern. + return emailPattern.test(email) +} diff --git a/server/swagger.yaml b/server/swagger.yaml index 69e65a8..c3c6bb7 100644 --- a/server/swagger.yaml +++ b/server/swagger.yaml @@ -24,7 +24,7 @@ paths: - signed-in summary: list existing sessions of current user security: - - BearerAuth: [] + - cookieAuth: [] parameters: - in: query name: quest_id @@ -52,7 +52,7 @@ paths: tags: - signed-in security: - - BearerAuth: [] + - cookieAuth: [] requestBody: description: session data content: @@ -62,6 +62,7 @@ paths: properties: quest_id: type: string + example: helloworld responses: '201': description: session created @@ -85,7 +86,7 @@ paths: - signed-in summary: get a specific session of current user by ID security: - - BearerAuth: [] + - cookieAuth: [] parameters: - in: path name: session_id @@ -116,7 +117,7 @@ paths: - signed-in summary: delete a specific session by ID security: - - BearerAuth: [] + - cookieAuth: [] parameters: - in: path name: session_id @@ -150,7 +151,7 @@ paths: type: string example: 'Unauthorized.' - /register: + /auth/register: post: summary: Register a new user requestBody: @@ -162,21 +163,33 @@ paths: properties: username: type: string + format: username + example: alex email: type: string + format: email + example: alex@example.com password: type: string + format: password + example: 123456 responses: '201': description: User created + headers: + Set-Cookie: + schema: + type: string + example: connect.sid=s%3Aq1Z; Path=/; HttpOnly; SameSite=Strict; Secure content: application/json: schema: type: object properties: - message: + token: type: string - example: User created successfully + description: Access token + example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... '400': description: Bad Request content: @@ -198,7 +211,7 @@ paths: type: string example: User already exists - /login: + /auth/login: post: summary: Log in a user requestBody: @@ -210,11 +223,20 @@ paths: properties: username: type: string + format: username + example: alex password: type: string + format: password + example: 123456 responses: '200': description: User logged in + headers: + Set-Cookie: + schema: + type: string + example: connect.sid=s%3Aq1Z; Path=/; HttpOnly; SameSite=Strict; Secure content: application/json: schema: @@ -222,10 +244,7 @@ paths: properties: message: type: string - example: Login successful - token: - type: string - description: Access token + example: Logged in successfully '400': description: Bad Request content: @@ -247,18 +266,73 @@ paths: type: string example: Incorrect username or password - /profile: - get: - summary: Get the profile of current logged-in user + /auth/logout: + post: + summary: Log out a user + tags: + - signed-in security: - - BearerAuth: [] + - cookieAuth: [] responses: '200': - description: Get user's profile + description: User logged out content: application/json: schema: - $ref: '#/components/schemas/User' + type: object + properties: + message: + type: string + example: Logged out successfully + + /auth/check-username: + get: + summary: Check if the username is already taken + parameters: + - in: query + name: username + description: username to check + required: true + schema: + type: string + format: username + example: alex + responses: + '200': + description: Username is available + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Username is available + '409': + description: Username is already taken + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Username is already taken + + /auth/check-session: + get: + summary: Check if the session is valid + responses: + '200': + description: Result + content: + application/json: + schema: + type: object + properties: + loggedIn: + type: boolean + example: true /quests: get: @@ -303,6 +377,10 @@ paths: /commands: post: summary: Record a command that was executed in a session + tags: + - signed-in + security: + - cookieAuth: [] requestBody: required: true content: @@ -433,13 +511,9 @@ components: Command: type: object required: - - token - pwd - command properties: - token: - type: string - description: The JWT token of the session pwd: type: string example: '/root' @@ -493,7 +567,7 @@ components: example: 'Unix Spirit' securitySchemes: - BearerAuth: - type: http - scheme: bearer - bearerFormat: JWT + cookieAuth: + type: apiKey + in: cookie + name: connect.sid diff --git a/server/tests/utils/auth.test.js b/server/tests/utils/auth.test.js new file mode 100644 index 0000000..3df34ed --- /dev/null +++ b/server/tests/utils/auth.test.js @@ -0,0 +1,44 @@ +import { isValidEmail, isValidUsername } from '../../src/utils/auth' + +test('check valid username', () => { + const valid = ['alex', 'alex123', 'john_doe', 'john-doe', 'rudeus'] + + const invalid = ['root', 'admin', '12michael', 'Justin', 'testUser'] + + valid.forEach((username) => { + expect(isValidUsername(username)).toBe(true) + }) + + invalid.forEach((username) => { + expect(isValidUsername(username)).toBe(false) + }) +}) + +test('check valid email', () => { + const validEmails = [ + 'user@example.com', + 'user.name@example.co.uk', + 'user1234@email.domain', + 'user+tag@example.net', + 'user@subdomain.example.com', + 'user@123.45.67.89', + ] + + const invalidEmails = [ + 'user@.com', + '@example.com', + 'user@-invalid-domain.com', + 'user@.123', + 'user@.123.45.67', + ] + + validEmails.forEach((email) => { + console.log(email) + expect(isValidEmail(email)).toBe(true) + }) + + invalidEmails.forEach((email) => { + console.log(email) + expect(isValidEmail(email)).toBe(false) + }) +}) diff --git a/yarn.lock b/yarn.lock index c3c6bed..d30aaa9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -755,6 +755,21 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" +"@mapbox/node-pre-gyp@^1.0.11": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa" + integrity sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ== + dependencies: + detect-libc "^2.0.0" + https-proxy-agent "^5.0.0" + make-dir "^3.1.0" + node-fetch "^2.6.7" + nopt "^5.0.0" + npmlog "^5.0.1" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.11" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -1059,6 +1074,13 @@ acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + ajv@^6.10.0, ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -1113,6 +1135,19 @@ anymatch@^3.0.3, anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" +"aproba@^1.0.3 || ^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + arg@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" @@ -1174,6 +1209,16 @@ array.prototype.flatmap@^1.3.1: es-abstract "^1.20.4" es-shim-unscopables "^1.0.0" +asn1.js@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" + integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + safer-buffer "^2.1.0" + asn1@^0.2.6: version "0.2.6" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" @@ -1303,6 +1348,14 @@ bcrypt-pbkdf@^1.0.2: dependencies: tweetnacl "^0.14.3" +bcrypt@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-5.1.1.tgz#0f732c6dcb4e12e5b70a25e326a72965879ba6e2" + integrity sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww== + dependencies: + "@mapbox/node-pre-gyp" "^1.0.11" + node-addon-api "^5.0.0" + big-integer@^1.6.44: version "1.6.51" resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" @@ -1322,6 +1375,11 @@ bl@^4.0.3: inherits "^2.0.4" readable-stream "^3.4.0" +bn.js@^4.0.0: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + body-parser@1.20.1: version "1.20.1" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" @@ -1499,6 +1557,11 @@ chownr@^1.1.1: resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + ci-info@^3.2.0: version "3.8.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" @@ -1552,6 +1615,11 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +color-support@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -1579,6 +1647,19 @@ confusing-browser-globals@^1.0.10: resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81" integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA== +connect-mongo@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/connect-mongo/-/connect-mongo-5.0.0.tgz#2a902d293d369a4baff2e988634cda6f8c0bf721" + integrity sha512-s93jiP6GkRApn5duComx6RLwtP23YrulPxShz+8peX7svd6Q+MS8nKLhKCCazbP92C13eTVaIOxgeLt0ezIiCg== + dependencies: + debug "^4.3.1" + kruptein "^3.0.0" + +console-control-strings@^1.0.0, console-control-strings@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== + content-disposition@0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" @@ -1606,16 +1687,16 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== +cookie@0.4.2, cookie@~0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== + cookie@0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== -cookie@~0.4.1: - version "0.4.2" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" - integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== - cors@~2.8.5: version "2.8.5" resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" @@ -1665,7 +1746,7 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4.x, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: +debug@4, debug@4.x, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -1730,7 +1811,12 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== -depd@2.0.0: +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== + +depd@2.0.0, depd@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== @@ -1740,6 +1826,11 @@ destroy@1.2.0: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== +detect-libc@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.2.tgz#8ccf2ba9315350e1241b88d0ac3b0e1fbd99605d" + integrity sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw== + detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -2228,6 +2319,20 @@ expect@^29.6.1: jest-message-util "^29.6.1" jest-util "^29.6.1" +express-session@^1.17.3: + version "1.17.3" + resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.17.3.tgz#14b997a15ed43e5949cb1d073725675dd2777f36" + integrity sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw== + dependencies: + cookie "0.4.2" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~2.0.0" + on-headers "~1.0.2" + parseurl "~1.3.3" + safe-buffer "5.2.1" + uid-safe "~2.1.5" + express@^4.18.2: version "4.18.2" resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" @@ -2407,6 +2512,13 @@ fs-constants@^1.0.0: resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -2437,6 +2549,21 @@ functions-have-names@^1.2.2, functions-have-names@^1.2.3: resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== +gauge@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395" + integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + object-assign "^4.1.1" + signal-exit "^3.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.2" + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -2588,6 +2715,11 @@ has-tostringtag@^1.0.0: dependencies: has-symbols "^1.0.2" +has-unicode@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== + has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -2611,6 +2743,14 @@ http-errors@2.0.0: statuses "2.0.1" toidentifier "1.0.1" +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + human-signals@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" @@ -2672,7 +2812,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -3339,6 +3479,22 @@ json5@^2.2.2: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +jsonwebtoken@^9.0.0: + version "9.0.2" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" + integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^7.5.4" + jsonwebtoken@^9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz#81d8c901c112c24e497a55daf6b2be1225b40145" @@ -3376,6 +3532,13 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== +kruptein@^3.0.0: + version "3.0.6" + resolved "https://registry.yarnpkg.com/kruptein/-/kruptein-3.0.6.tgz#9cf976225af4bc1fbeba069dbb24f17eebad974d" + integrity sha512-EQJjTwAJfQkC4NfdQdo3HXM2a9pmBm8oidzH270cYu1MbgXPNPMJuldN7OPX+qdhPO5rw4X3/iKz0BFBfkXGKA== + dependencies: + asn1.js "^5.4.1" + leven@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" @@ -3413,11 +3576,46 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -3454,7 +3652,7 @@ magic-string@^0.30.0: dependencies: "@jridgewell/sourcemap-codec" "^1.4.15" -make-dir@^3.0.0: +make-dir@^3.0.0, make-dir@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== @@ -3533,6 +3731,11 @@ mimic-fn@^4.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== +minimalistic-assert@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -3545,11 +3748,36 @@ minimist@^1.2.0, minimist@^1.2.6, minimist@^1.2.8: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== +minipass@^3.0.0: + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + mkdirp-classic@^0.5.2: version "0.5.3" resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== +mkdirp@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + mongodb-connection-string-url@^2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz#57901bf352372abdde812c81be47b75c6b2ec5cf" @@ -3638,6 +3866,18 @@ negotiator@0.6.3: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== +node-addon-api@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762" + integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== + +node-fetch@^2.6.7: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -3673,6 +3913,13 @@ nodemon@^3.0.1: touch "^3.1.0" undefsafe "^2.0.5" +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + nopt@~1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" @@ -3704,6 +3951,16 @@ npm-run-path@^5.1.0: dependencies: path-key "^4.0.0" +npmlog@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" + integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== + dependencies: + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^3.0.0" + set-blocking "^2.0.0" + nth-check@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" @@ -3711,7 +3968,7 @@ nth-check@^2.0.1: dependencies: boolbase "^1.0.0" -object-assign@^4, object-assign@^4.0.1: +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" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== @@ -3766,6 +4023,11 @@ on-finished@2.4.1: dependencies: ee-first "1.1.1" +on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -3864,6 +4126,35 @@ parseurl@~1.3.3: resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== +passport-jwt@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/passport-jwt/-/passport-jwt-4.0.1.tgz#c443795eff322c38d173faa0a3c481479646ec3d" + integrity sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ== + dependencies: + jsonwebtoken "^9.0.0" + passport-strategy "^1.0.0" + +passport-local@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/passport-local/-/passport-local-1.0.0.tgz#1fe63268c92e75606626437e3b906662c15ba6ee" + integrity sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow== + dependencies: + 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" + integrity sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA== + +passport@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/passport/-/passport-0.6.0.tgz#e869579fab465b5c0b291e841e6cc95c005fac9d" + integrity sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug== + dependencies: + passport-strategy "1.x.x" + pause "0.0.1" + utils-merge "^1.0.1" + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -3894,6 +4185,11 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== +pause@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d" + integrity sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg== + picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" @@ -4056,6 +4352,11 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +random-bytes@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b" + integrity sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ== + range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" @@ -4083,7 +4384,7 @@ read-cache@^1.0.0: dependencies: pify "^2.3.0" -readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0: +readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -4191,7 +4492,7 @@ safe-regex-test@^1.0.0: get-intrinsic "^1.1.3" is-regex "^1.1.4" -"safer-buffer@>= 2.1.2 < 3", safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -4208,7 +4509,7 @@ semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.5, semver@^7.3.6, semver@^7.3.8, semver@^7.5.3: +semver@^7.3.5, semver@^7.3.6, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -4244,6 +4545,11 @@ serve-static@1.15.0: parseurl "~1.3.3" send "0.18.0" +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + setprototypeof@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" @@ -4275,7 +4581,7 @@ sift@16.0.1: resolved "https://registry.yarnpkg.com/sift/-/sift-16.0.1.tgz#e9c2ccc72191585008cf3e36fc447b2d2633a053" integrity sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ== -signal-exit@^3.0.3, signal-exit@^3.0.7: +signal-exit@^3.0.0, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== @@ -4419,7 +4725,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -4602,6 +4908,18 @@ tar-stream@^2.0.0: inherits "^2.0.3" readable-stream "^3.1.1" +tar@^6.1.11: + version "6.2.0" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.0.tgz#b14ce49a79cb1cd23bc9b016302dea5474493f73" + integrity sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^5.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + test-exclude@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" @@ -4671,6 +4989,11 @@ tr46@^3.0.0: dependencies: punycode "^2.1.1" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + ts-interface-checker@^0.1.9: version "0.1.13" resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" @@ -4746,6 +5069,13 @@ typed-array-length@^1.0.4: for-each "^0.3.3" is-typed-array "^1.1.9" +uid-safe@~2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a" + integrity sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA== + dependencies: + random-bytes "~1.0.0" + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" @@ -4791,7 +5121,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: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== @@ -4859,6 +5189,11 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + webidl-conversions@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" @@ -4872,6 +5207,14 @@ whatwg-url@^11.0.0: tr46 "^3.0.0" webidl-conversions "^7.0.0" +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" @@ -4902,6 +5245,13 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +wide-align@^1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"