mirror of
https://github.com/linux-odyssey/linux-odyssey.git
synced 2026-11-05 19:20:12 +08:00
feat: update frontend to use cookie session
* use SessionMiddleware in socket.io server * add back jwtStrategy for commands
This commit is contained in:
1 parent
be5cd152a9
commit
34c277c886
13 files changed
+90
-116
No files matched your search
@@ -1,6 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { isAuthenticated, logout } from '../utils/auth'
|
import { logout } from '../utils/auth'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ const handleLogout = () => {
|
|||||||
class="text-text-primary h-5 w-5"
|
class="text-text-primary h-5 w-5"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<button v-if="isAuthenticated" @click="handleLogout">
|
<button @click="handleLogout">
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
:icon="['fas', 'arrow-right-from-bracket']"
|
:icon="['fas', 'arrow-right-from-bracket']"
|
||||||
class="text-text-primary h-5 w-5"
|
class="text-text-primary h-5 w-5"
|
||||||
|
|||||||
+23
-20
@@ -1,11 +1,14 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import GamePage from './views/GamePage.vue'
|
import GamePage from './views/GamePage.vue'
|
||||||
import AuthPage from './views/AuthPage.vue'
|
import AuthPage from './views/AuthPage.vue'
|
||||||
import { isAuthenticated } from './utils/auth'
|
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
|
redirect: '/game',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
name: 'auth',
|
name: 'auth',
|
||||||
component: AuthPage,
|
component: AuthPage,
|
||||||
meta: { requiresGuest: true },
|
meta: { requiresGuest: true },
|
||||||
@@ -23,24 +26,24 @@ const router = createRouter({
|
|||||||
routes,
|
routes,
|
||||||
})
|
})
|
||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
// router.beforeEach((to, from, next) => {
|
||||||
console.log(to, from)
|
// console.log(to, from)
|
||||||
const isAuthed = isAuthenticated.value
|
// const isAuthed = isAuthenticated.value
|
||||||
// Check if the route requires authentication
|
// // Check if the route requires authentication
|
||||||
if (to.meta.requiresAuth && !isAuthed) {
|
// if (to.meta.requiresAuth && !isAuthed) {
|
||||||
// Check if the user isn't authenticated
|
// // Check if the user isn't authenticated
|
||||||
// Redirect to the login page
|
// // Redirect to the login page
|
||||||
next({
|
// next({
|
||||||
name: 'auth',
|
// name: 'auth',
|
||||||
query: { redirect: to.fullPath }, // Optionally pass a redirect parameter
|
// query: { redirect: to.fullPath }, // Optionally pass a redirect parameter
|
||||||
})
|
// })
|
||||||
} else if (to.meta.requiresGuest && isAuthed) {
|
// } else if (to.meta.requiresGuest && isAuthed) {
|
||||||
next({
|
// next({
|
||||||
name: 'game', // Or any default authenticated user route
|
// name: 'game', // Or any default authenticated user route
|
||||||
})
|
// })
|
||||||
} else {
|
// } else {
|
||||||
next() // Always call next() at the end
|
// next() // Always call next() at the end
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
@@ -5,14 +5,7 @@ const api = axios.create({
|
|||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
|
withCredentials: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const setToken = (token) => {
|
|
||||||
if (token) {
|
|
||||||
api.defaults.headers.common.Authorization = `Bearer ${token}`
|
|
||||||
} else {
|
|
||||||
delete api.defaults.headers.common.Authorization
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default api
|
export default api
|
||||||
+5
-37
@@ -1,36 +1,9 @@
|
|||||||
import { computed, ref, watch } from 'vue'
|
import api from './api'
|
||||||
import api, { setToken } from './api'
|
|
||||||
|
|
||||||
const TOKEN_KEY = 'JWT_TOKEN'
|
|
||||||
|
|
||||||
const myStorage = window.localStorage
|
|
||||||
const myToken = ref(myStorage.getItem(TOKEN_KEY))
|
|
||||||
|
|
||||||
if (myToken.value) {
|
|
||||||
setToken(myToken.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// watch the token and store it in localStorage
|
|
||||||
watch(myToken, (newToken) => {
|
|
||||||
if (newToken) {
|
|
||||||
console.log('update token:', newToken)
|
|
||||||
myStorage.setItem(TOKEN_KEY, newToken)
|
|
||||||
setToken(newToken)
|
|
||||||
} else {
|
|
||||||
myStorage.removeItem(TOKEN_KEY)
|
|
||||||
setToken(null)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export const getToken = () => {
|
|
||||||
return myToken.value
|
|
||||||
}
|
|
||||||
|
|
||||||
export const login = async (username, password) => {
|
export const login = async (username, password) => {
|
||||||
try {
|
try {
|
||||||
const res = await api.post('/auth/login', { username, password })
|
const res = await api.post('/auth/login', { username, password })
|
||||||
const { token } = res.data
|
console.log(res.data)
|
||||||
myToken.value = token
|
|
||||||
return true
|
return true
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.response?.status === 401) {
|
if (err.response?.status === 401) {
|
||||||
@@ -40,19 +13,14 @@ export const login = async (username, password) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const register = async (username, email, password) => {
|
export const register = (username, email, password) => {
|
||||||
const res = await api.post('/auth/register', {
|
return api.post('/auth/register', {
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
email,
|
email,
|
||||||
})
|
})
|
||||||
const { token } = res.data
|
|
||||||
myToken.value = token
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const logout = () => {
|
export const logout = () => {
|
||||||
myToken.value = null
|
return api.post('/auth/logout')
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isAuthenticated = computed(() => !!myToken.value)
|
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
import io from 'socket.io-client'
|
import io from 'socket.io-client'
|
||||||
|
|
||||||
import { getToken } from './auth'
|
|
||||||
|
|
||||||
class SocketWrapper {
|
class SocketWrapper {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.socket = null
|
this.socket = null
|
||||||
@@ -18,9 +16,6 @@ class SocketWrapper {
|
|||||||
query: {
|
query: {
|
||||||
session_id: session._id,
|
session_id: session._id,
|
||||||
},
|
},
|
||||||
auth: {
|
|
||||||
token: getToken(),
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
this.socket.on('connect', () => {
|
this.socket.on('connect', () => {
|
||||||
|
|||||||
@@ -118,6 +118,6 @@ export function logout(req, res) {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
res.json({ message: 'success' })
|
res.redirect('/login')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -4,9 +4,9 @@ import sessions from './sessions.js'
|
|||||||
import quests from './quests.js'
|
import quests from './quests.js'
|
||||||
import commands from './commands.js'
|
import commands from './commands.js'
|
||||||
import authRouter from './auth.js'
|
import authRouter from './auth.js'
|
||||||
|
import authRequired from '../../middleware/authRequired.js'
|
||||||
|
|
||||||
const router = Router()
|
const router = Router()
|
||||||
router.use(passport.authenticate('session'))
|
|
||||||
|
|
||||||
router.get('/', (req, res) => {
|
router.get('/', (req, res) => {
|
||||||
res.send('Hello API!')
|
res.send('Hello API!')
|
||||||
@@ -15,10 +15,11 @@ router.get('/', (req, res) => {
|
|||||||
router.use('/auth', authRouter)
|
router.use('/auth', authRouter)
|
||||||
router.use('/quests', quests)
|
router.use('/quests', quests)
|
||||||
|
|
||||||
const authZone = Router()
|
router.use('/sessions', authRequired, sessions)
|
||||||
authZone.use('/sessions', sessions)
|
router.use(
|
||||||
authZone.use('/commands', commands)
|
'/commands',
|
||||||
|
passport.authenticate('jwt', { session: false }),
|
||||||
router.use(authZone)
|
commands
|
||||||
|
)
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
+11
-20
@@ -1,7 +1,8 @@
|
|||||||
import { Server } from 'socket.io'
|
import { Server } from 'socket.io'
|
||||||
import { getAndStartContainer, attachContainer } from '../containers/docker.js'
|
import { getAndStartContainer, attachContainer } from '../containers/docker.js'
|
||||||
import Session from '../models/session.js'
|
import Session from '../models/session.js'
|
||||||
import { genJWT, verifyJWT } from '../utils/auth.js'
|
import SessionMiddleware from '../middleware/session.js'
|
||||||
|
import { genJWT } from '../utils/auth.js'
|
||||||
|
|
||||||
const sessions = new Map()
|
const sessions = new Map()
|
||||||
|
|
||||||
@@ -27,6 +28,13 @@ export function pushToSession(sessionId, event, ...args) {
|
|||||||
|
|
||||||
async function connectClient(socket) {
|
async function connectClient(socket) {
|
||||||
console.log('Connected to the client.')
|
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
|
const sessionId = socket.handshake.query.session_id
|
||||||
if (!sessionId) {
|
if (!sessionId) {
|
||||||
socket.send('Session ID not found.')
|
socket.send('Session ID not found.')
|
||||||
@@ -36,7 +44,7 @@ async function connectClient(socket) {
|
|||||||
|
|
||||||
const session = await Session.findOne({
|
const session = await Session.findOne({
|
||||||
_id: sessionId,
|
_id: sessionId,
|
||||||
user: socket.user,
|
user,
|
||||||
status: 'active',
|
status: 'active',
|
||||||
})
|
})
|
||||||
if (!session) {
|
if (!session) {
|
||||||
@@ -89,24 +97,7 @@ async function connectClient(socket) {
|
|||||||
export default (server) => {
|
export default (server) => {
|
||||||
const io = new Server(server)
|
const io = new Server(server)
|
||||||
|
|
||||||
io.use(async (socket, next) => {
|
io.engine.use(SessionMiddleware)
|
||||||
console.log('Authenticating...')
|
|
||||||
try {
|
|
||||||
const { token } = socket.handshake.auth
|
|
||||||
if (!token) {
|
|
||||||
next(new Error('Token not found.'))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const user = await verifyJWT(token)
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
console.log(user)
|
|
||||||
socket.user = user
|
|
||||||
next()
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
next(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
io.on('connection', connectClient)
|
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)
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import passport from 'passport'
|
import passport from 'passport'
|
||||||
import PasswordStrategy from './passwordStrategy.js'
|
import PasswordStrategy from './passwordStrategy.js'
|
||||||
|
import JwtStrategy from './jwtStrategy.js'
|
||||||
|
|
||||||
passport.use(PasswordStrategy)
|
passport.use(PasswordStrategy)
|
||||||
|
passport.use(JwtStrategy)
|
||||||
|
|
||||||
passport.serializeUser((user, done) => {
|
passport.serializeUser((user, done) => {
|
||||||
process.nextTick(() => {
|
process.nextTick(() => {
|
||||||
|
|||||||
+4
-2
@@ -3,6 +3,7 @@ import express from 'express'
|
|||||||
import http from 'http'
|
import http from 'http'
|
||||||
import YAML from 'yaml'
|
import YAML from 'yaml'
|
||||||
import swaggerUI from 'swagger-ui-express'
|
import swaggerUI from 'swagger-ui-express'
|
||||||
|
import passport from 'passport'
|
||||||
|
|
||||||
import './auth/passport.js'
|
import './auth/passport.js'
|
||||||
import socketServer from './api/socket.js'
|
import socketServer from './api/socket.js'
|
||||||
@@ -16,7 +17,7 @@ import expiryRemovalScheduler from './containers/expiryChecker.js'
|
|||||||
import { createTestUser } from './utils/auth.js'
|
import { createTestUser } from './utils/auth.js'
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const db = await connectDB()
|
await connectDB()
|
||||||
|
|
||||||
await createTestUser()
|
await createTestUser()
|
||||||
|
|
||||||
@@ -30,7 +31,8 @@ async function main() {
|
|||||||
const server = http.createServer(app)
|
const server = http.createServer(app)
|
||||||
socketServer(server)
|
socketServer(server)
|
||||||
app.use(errorHandler)
|
app.use(errorHandler)
|
||||||
app.use(sessionMiddleware(db))
|
app.use(sessionMiddleware)
|
||||||
|
app.use(passport.session())
|
||||||
app.use(
|
app.use(
|
||||||
'/api-docs',
|
'/api-docs',
|
||||||
swaggerUI.serve,
|
swaggerUI.serve,
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export default function authRequired(req, res, next) {
|
||||||
|
if (req.isAuthenticated()) {
|
||||||
|
next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res.status(401).json({
|
||||||
|
message: 'not authenticated',
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -3,18 +3,15 @@ import MongoStore from 'connect-mongo'
|
|||||||
|
|
||||||
import config from '../config.js'
|
import config from '../config.js'
|
||||||
|
|
||||||
export default (db) => {
|
export default session({
|
||||||
console.log(db.constructor.name)
|
secret: config.jwtSecret,
|
||||||
return session({
|
resave: false,
|
||||||
secret: config.jwtSecret,
|
saveUninitialized: false,
|
||||||
resave: false,
|
cookie: {
|
||||||
saveUninitialized: false,
|
httpOnly: true,
|
||||||
cookie: {
|
},
|
||||||
httpOnly: true,
|
store: MongoStore.create({
|
||||||
},
|
mongoUrl: config.db,
|
||||||
store: MongoStore.create({
|
collectionName: 'loginSessions',
|
||||||
mongoUrl: config.db,
|
}),
|
||||||
collectionName: 'loginSessions',
|
})
|
||||||
}),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user