Merge pull request #26 from linux-odyssey/feat-auth

backend:
* add `username` and `email` 2 unique and required field to User
* use PassportJS for authentication framework
* use express-session, connect-mongo as backend
* use passport-local for password authentication
* use passport-jwt for container authentication

frontend:
* combined login / sign up flow
* use http-only cookie
* move login page from `/` to `/login`, automatically redirect

client:
* remove `list` command (not useful)
* add authentication functions
This commit is contained in:
Lin, Hung-Shin authored and GitHub committed 2023-09-18 22:54:28 +08:00
commit 248355a2f5
33 files changed
+1271 -228

No files matched your search

+143 -9
View File
@@ -1,18 +1,141 @@
<script setup></script>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { login, register } from '../utils/auth'
import api from '../utils/api'
const router = useRouter()
const isRegister = ref(false)
const username = ref('')
const usernameInput = ref(null)
const email = ref('')
const emailInput = ref(null)
const password = ref('')
const errorMessage = ref('')
const success = () => {
router.push({ name: 'game' })
}
const handleLogin = async () => {
try {
const isSuccess = await login(username.value, password.value)
if (isSuccess) success()
else errorMessage.value = 'Invalid username or password.'
} catch (err) {
console.error(err)
errorMessage.value = 'Something went wrong.'
}
}
async function check() {
const user = username.value.trim()
try {
const res = await api.get('/auth/check-username', {
params: { username: user },
})
// user not exists, try register
const { type } = res.data
if (type === 'email') {
email.value = user
username.value = ''
}
isRegister.value = true
usernameInput.value.focus()
} catch (err) {
if (err.response?.status === 409) {
// user already exists, try login
await handleLogin()
return
}
if (err.response?.status === 400) {
errorMessage.value = 'Invalid username or email.'
return
}
errorMessage.value = 'Something went wrong.'
console.error(err)
}
}
function handleRegister() {
console.log('register', username.value, email.value, password.value)
register(username.value, email.value, password.value)
.then((isSuccess) => {
if (isSuccess) {
success()
} else {
errorMessage.value = 'Something went wrong.'
}
})
.catch((err) => {
if (err.response?.status === 409) {
// Handle username or email already exists error here
if (err.response.data.type === 'username') {
errorMessage.value = 'Username already exists.'
} else if (err.response.data.type === 'email') {
errorMessage.value = 'Email already exists.'
} else {
errorMessage.value = 'Something went wrong.'
}
} else if (err.response?.status === 400) {
errorMessage.value = 'Invalid username or email.'
} else {
errorMessage.value = 'Something went wrong.'
console.error(err)
}
})
}
async function handleSubmit() {
if (!isRegister.value) {
check()
return
}
// register
handleRegister()
}
const clearError = () => {
errorMessage.value = ''
}
const goBack = () => {
isRegister.value = false
}
</script>
<template>
<!--login page-->
<div
class="bg-background border-8 border-background-primary flex flex-1 flex-col items-center justify-center rounded-3xl p-10"
>
<h1 class="text-text-primary text-3xl font-black mb-8">Linux Odyssey</h1>
<form action="" class="w-full">
<form @submit.prevent="handleSubmit" class="w-full">
<div class="mb-6">
<label for="username" class="text-sm font-semibold text-text">{{
isRegister ? 'Username' : 'Email / Username'
}}</label
><input
type="text"
id="username"
ref="usernameInput"
class="my-4 bg-background-primary text-text-primary bg- rounded-md block w-full px-3 h-10 shadow-sm focus:outline-none placeholder:text-text-line focus:ring-2 focus:ring-text-primary ring-1 ring-background-secondary"
placeholder="Enter your email or username"
v-model="username"
@input="clearError"
autocomplete="username"
/>
</div>
<div class="mb-6" v-if="isRegister">
<label for="email" class="text-sm font-semibold text-text">Email</label
><input
type="email"
id="email"
ref="emailInput"
class="my-4 bg-background-primary text-text-primary bg- rounded-md block w-full px-3 h-10 shadow-sm focus:outline-none placeholder:text-text-line focus:ring-2 focus:ring-text-primary ring-1 ring-background-secondary"
placeholder="Enter your email address"
placeholder="Enter your email or username"
v-model="email"
@input="clearError"
autocomplete="email"
/>
</div>
<div class="mb-6">
@@ -23,16 +146,27 @@
id="password"
class="my-4 bg-background-primary text-text-primary bg- rounded-md block w-full px-3 h-10 shadow-sm focus:outline-none placeholder:text-text-line focus:ring-2 focus:ring-text-primary ring-1 ring-background-secondary"
placeholder="Enter your password"
v-model="password"
@input="clearError"
/>
</div>
<router-link
type="submit"
<p class="text-red-500 flex justify-center" v-if="errorMessage">
{{ errorMessage }}
</p>
<button
class="inline-flex justify-center rounded-lg text-sm font-black py-2 mt-3 bg-text-primary text-background w-full"
to="/game"
@click="handleSubmit"
>
<span>Log in / Sign up</span>
</router-link>
<input type="hidden" name="remember" value="true" />
<span v-if="isRegister">Sign up</span>
<span v-else>Log in / Sign up</span>
</button>
<button
class="inline-flex justify-center rounded-lg text-sm font-black py-2 mt-3 bg-text-primary text-background w-full"
v-if="isRegister"
@click="goBack"
>
<span>Back to Login</span>
</button>
</form>
</div>
</template>
+17 -1
View File
@@ -1,4 +1,14 @@
<script setup></script>
<script setup>
import { useRouter } from 'vue-router'
import { logout } from '../utils/auth'
const router = useRouter()
const handleLogout = async () => {
await logout()
router.push({ name: 'auth' })
}
</script>
<template>
<div class="h-full flex items-center justify-center mx-3">
@@ -46,6 +56,12 @@
class="text-text-primary h-5 w-5"
/>
</button>
<button @click="handleLogout">
<font-awesome-icon
:icon="['fas', 'arrow-right-from-bracket']"
class="text-text-primary h-5 w-5"
/>
</button>
</div>
</div>
</template>
+37
View File
@@ -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
)
+2 -50
View File
@@ -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)
+50
View File
@@ -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
+4
View File
@@ -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
+32
View File
@@ -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')
}
+45 -21
View File
@@ -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 <string>', 'Session ID')
.option('-c, --create', 'Create a new session')
.option('-d, --debug', 'Debug mode')
.option('-u, --user <string>', 'Username', get('USERNAME', 'alex'))
.option('-p, --password <string>', 'Password', get('PASSWORD', '123456'))
.option(
'-h, --host <string>',
'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()
+1 -1
View File
@@ -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),
})
+2
View File
@@ -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
+4
View File
@@ -0,0 +1,4 @@
{
"watch": ["src", "swagger.yaml"],
"ext": "js,yaml"
}
+6
View File
@@ -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"
@@ -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(),
})
}
@@ -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)
+26 -26
View File
@@ -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) {
+29
View File
@@ -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
+11 -3
View File
@@ -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
+14 -15
View File
@@ -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)
}
+13
View File
@@ -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)
+22
View File
@@ -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)
})
})
+24
View File
@@ -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)
+2 -1
View File
@@ -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),
}
+18 -14
View File
@@ -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() {
+6 -1
View File
@@ -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
}
}
+21 -6
View File
@@ -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}`)
})
-11
View File
@@ -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 })
}
}
+9
View File
@@ -0,0 +1,9 @@
export default function authRequired(req, res, next) {
if (req.isAuthenticated()) {
next()
return
}
res.status(401).json({
message: 'not authenticated',
})
}
+21
View File
@@ -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',
}),
})
+11 -3
View File
@@ -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
+58 -14
View File
@@ -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)
}
+100 -26
View File
@@ -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
+44
View File
@@ -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)
})
})
+366 -16
View File
@@ -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"