mirror of
https://github.com/linux-odyssey/linux-odyssey.git
synced 2026-11-05 19:20:12 +08:00
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:
33 files changed
+1271
-228
No files matched your search
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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()
|
||||
@@ -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),
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"watch": ["src", "swagger.yaml"],
|
||||
"ext": "js,yaml"
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
@@ -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)
|
||||
@@ -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),
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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}`)
|
||||
})
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export default function authRequired(req, res, next) {
|
||||
if (req.isAuthenticated()) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
res.status(401).json({
|
||||
message: 'not authenticated',
|
||||
})
|
||||
}
|
||||
@@ -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',
|
||||
}),
|
||||
})
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
@@ -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"
|
||||
|
||||
Reference in new issue
Block a user