Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions components/Queue/Queue.vue
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,16 @@ const changeButton = () => {
//if button is teal and it's pressed, have user join queue and change color to read.
if(buttonColor.value == '#5FE0B7')
{
console.log("🟢 Emitting join-queue event")
emit("join-queue")
buttonColor.value = '#FF0000';
buttonStatus.value = 'Leave Queue';
}
//if button is red (in queue) and its pressed, have user leave queue and change color to teal.
else
{

console.log("🔴 Emitting leave-queue event")
emit("leave-queue")
buttonColor.value = '#5FE0B7'
buttonStatus.value = 'Join Queue';
Expand Down
8 changes: 6 additions & 2 deletions components/Queue/QueueContainer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<template>
<div class="w-[340px] h-full">

<Upcoming :queue="queueUsers" />
<LeaderBoardHomepage :theme="theme"/>
<div v-if="isLoggedIn">
Expand All @@ -13,13 +14,16 @@
<script setup lang="ts">
//need to pass in the queue of all users here.
import Upcoming from '~/components/Gameplay/UpComing.vue'

import Queue from '~/components/Queue/Queue.vue'
const props = defineProps({
queueUsers: { type: Array<string>, default: [] },
queueUsers: { type: Array<string>, default: () => [] },
theme: {type: String, default: "light"}
})


const sruser = useCookie('sruser');
console.log("sruser cookie value: ", sruser.value);
const isLoggedIn = computed(() => !!sruser.value)
console.log("Is user logged in? ", isLoggedIn.value);

</script>
7 changes: 2 additions & 5 deletions components/userauth/Profile.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,10 @@ import { navigateTo } from '#app'
let username = getUser.value?.username as string
//clear cookis and send user to homepage
const logout = async () => {
console.log("Logging out user from Profile.vue");
try {

await $fetch('/api/user.logout', { method: 'POST' })

document.cookie = 'sruser=; Max-Age=0; path=/;'


await $fetch('/api/user-logout', { method: 'POST' })
await navigateTo('/')

} catch (error) {
Expand Down
8 changes: 4 additions & 4 deletions pages/player.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!--The main file for the page, that has embedded in it all the UI components from the components folder.-->
<template>
<button class="fixed bottom-4 right-4 p-2 bg-gray-200 dark:bg-gray-700 rounded text-gray-800 dark:text-gray-200 shadow-lg" @click="toggleTheme" > 🌓</button>
<button class="fixed bottom-4 right-4 p-2 bg-gray-200 dark:bg-gray-700 rounded text-gray-800 dark:text-gray-200 shadow-lg" @click="toggleTheme" > 🌓</button>

<div class="min-h-screen w-screen overflow-x-hidden dark:bg-[#333333]">
<div>
Expand All @@ -11,8 +11,8 @@
<Scoreboard :timer="Number(timer ?? 0)" :user1="player1?.username ?? ''" :user2="player2?.username ?? ''" :user1score="player1?.score ?? 0 " :user2score="player2?.score ?? 0"></Scoreboard>
<span class="py-4">

<VideoStream streamType="twitch"></VideoStream>
</span>
<VideoStream streamType="twitch"></VideoStream>
</span>
</div>
<!--when user tries to join or leave queue, run the according functions in this file.-->
<QueueContainer :queueUsers="queue" :theme="theme" @join-queue="joinQueue" @leave-queue="leaveQueue"></QueueContainer>
Expand Down Expand Up @@ -44,7 +44,7 @@ onMounted(() => {
applyTheme()
})

// 3️⃣ toggle & persist
// toggle & persist
function toggleTheme() {
theme.value = theme.value === 'light' ? 'dark' : 'light'
localStorage.setItem('theme', theme.value)
Expand Down
1 change: 1 addition & 0 deletions prisma/ERD.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added prisma/dev.db
Binary file not shown.
12 changes: 12 additions & 0 deletions prisma/migrations/20251126024020_init/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
Warnings:

- A unique constraint covering the columns `[authToken]` on the table `Player` will be added. If there are existing duplicate values, this will fail.

*/
-- AlterTable
ALTER TABLE "Player" ADD COLUMN "authExpiry" DATETIME;
ALTER TABLE "Player" ADD COLUMN "authToken" TEXT;

-- CreateIndex
CREATE UNIQUE INDEX "Player_authToken_key" ON "Player"("authToken");
33 changes: 33 additions & 0 deletions prisma/migrations/20251126024914_add_session_token/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
Warnings:

- You are about to drop the column `authExpiry` on the `Player` table. All the data in the column will be lost.
- You are about to drop the column `authToken` on the `Player` table. All the data in the column will be lost.

*/
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Player" (
"user_id" TEXT NOT NULL PRIMARY KEY,
"username" TEXT NOT NULL,
"email" TEXT NOT NULL,
"magicToken" TEXT,
"tokenExpiry" DATETIME,
"sessionToken" TEXT,
"wins" INTEGER NOT NULL DEFAULT 0,
"goals" INTEGER NOT NULL DEFAULT 0,
"games" INTEGER NOT NULL DEFAULT 0,
"losses" INTEGER NOT NULL DEFAULT 0,
"ratio" REAL,
"role" TEXT NOT NULL DEFAULT 'player'
);
INSERT INTO "new_Player" ("email", "games", "goals", "losses", "magicToken", "ratio", "role", "tokenExpiry", "user_id", "username", "wins") SELECT "email", "games", "goals", "losses", "magicToken", "ratio", "role", "tokenExpiry", "user_id", "username", "wins" FROM "Player";
DROP TABLE "Player";
ALTER TABLE "new_Player" RENAME TO "Player";
CREATE UNIQUE INDEX "Player_username_key" ON "Player"("username");
CREATE UNIQUE INDEX "Player_email_key" ON "Player"("email");
CREATE UNIQUE INDEX "Player_magicToken_key" ON "Player"("magicToken");
CREATE UNIQUE INDEX "Player_sessionToken_key" ON "Player"("sessionToken");
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;
4 changes: 4 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ model Player {
user_id String @id
username String @unique
email String @unique

magicToken String? @unique
tokenExpiry DateTime?

sessionToken String? @unique

wins Int @default(0)
goals Int @default(0)
games Int @default(0)
Expand Down
34 changes: 10 additions & 24 deletions server/Game_Manager/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ import cors from "cors"
import { WebSocket, WebSocketServer } from "ws"
import { createServer, IncomingMessage } from "http"
// import { createServer } from "https"
import jwt from "jsonwebtoken"
import fs from "fs"
import { PrismaClient } from "@prisma/client"
import { nanoid } from "nanoid"
import type {Player as PlayerType} from "@prisma/client"
import type {Player as PlayerType} from "@prisma/client"

const prisma = new PrismaClient()

Expand Down Expand Up @@ -110,7 +108,7 @@ const gameCycle = setInterval( async () => {
}

//checks if the amount of accepted players is equal to total number of players
if(numAccepted = numPlayers*2)
if(numAccepted == numPlayers*2)
{

game_state = GAME_STATE.PLAYING
Expand Down Expand Up @@ -307,7 +305,6 @@ const gameCycle = setInterval( async () => {
games: {increment: 1},
ratio: ((player1 as PlayerType).wins) / ((player1 as PlayerType).losses + 1),
goals: {increment: score1}

}

}),
Expand Down Expand Up @@ -461,38 +458,27 @@ server_wss_CLIENT_GM.on("upgrade", async (request, socket, head) => {
return
}

// Authenticate using jwt from cookie srtoken
const srtoken = cookieObj["srtoken"]
const claims: any = jwt.verify(srtoken, fs.readFileSync(process.cwd()+"/cert-dev.pem"), (error, decoded) => {
//if error, close connection, otherwise return the decoded token
if(error){
socket.destroy()
return
}
return decoded
})

if(!(claims instanceof Object && claims["sub"])){ // if jwt is invalid, close connection
if(!srtoken){ // if srtoken cookie is empty, close connection
socket.destroy()
return
}

//else, get user id from the token
const user_id: string = claims["sub"]
//make sure user is actually in the database already
const find_user = await prisma.player.findUnique({
const find_user = await prisma.player.findFirst({
where: {
user_id: user_id
sessionToken: srtoken
}
})
if(!find_user){ // if user is not in database, close connection
});

if(!find_user){ // if token is not in database, close connection
socket.destroy()
return
}

// valid logged in user, upgrade connection to websocket
wss_client_gm.handleUpgrade(request, socket, head, (ws) => {
wss_client_gm.emit("connection", ws, request, find_user.username, user_id)
wss_client_gm.emit("connection", ws, request, find_user.username, find_user.user_id)
})
})

Expand Down Expand Up @@ -586,7 +572,7 @@ ws_raspberry.onopen = (event) => {
}

ws_raspberry.onerror = (error) => {
console.log("WS_RASPBERRY error: " + error)
console.log("WS_RASPBERRY error: " , error)
}
ws_raspberry.onclose = (event) => {
console.log("WS_RASPBERRY closed")
Expand Down
20 changes: 14 additions & 6 deletions server/api/login-request.post.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { PrismaClient } from "@prisma/client"
import { nanoid } from "nanoid"
import { setCookie } from "h3"

const prisma = new PrismaClient()

Expand All @@ -13,6 +14,9 @@ export default defineEventHandler(async (event) => {

let player = await prisma.player.findUnique({ where: { email } })

const token = nanoid(32)
const expiry = new Date(Date.now() + 10 * 60 * 1000)

if (!player) {
player = await prisma.player.create({
data: {
Expand All @@ -23,19 +27,23 @@ export default defineEventHandler(async (event) => {
})
}

const token = nanoid(32)
const expiry = new Date(Date.now() + 10 * 60 * 1000)


await prisma.player.update({
player = await prisma.player.update({
where: { email },
data: {
magicToken: token,
tokenExpiry: expiry
}
})

console.log("updated player:", player);
setCookie(event, 'magic_token', token, {
httpOnly: true,
path: '/',
sameSite: 'lax',
secure: false,
})

console.log("player:", player);


const loginLink = `http://localhost:3000/login?token=${token}`

Expand Down
63 changes: 40 additions & 23 deletions server/api/login.get.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
// /api/auth/login.get.ts
import { PrismaClient } from "@prisma/client"
import { nanoid } from "nanoid"
import { getQuery, setCookie, defineEventHandler, sendRedirect } from "h3"
import jwt from "jsonwebtoken"
console.log('JWT_SECRET:', process.env.JWT_SECRET)


const prisma = new PrismaClient()

export default defineEventHandler(async (event) => {
const token = getQuery(event).token
const { token } = getQuery(event)

if (!token || typeof token !== "string") {
throw createError({ statusCode: 400, statusMessage: "Missing or invalid token" })
throw createError({
statusCode: 400,
statusMessage: "Missing or invalid token"
})
}

// Validate magic login token
const player = await prisma.player.findFirst({
where: {
magicToken: token,
Expand All @@ -20,35 +25,47 @@ export default defineEventHandler(async (event) => {
})

if (!player) {
throw createError({ statusCode: 401, statusMessage: "Invalid or expired token" })
throw createError({
statusCode: 401,
statusMessage: "Invalid or expired magic link"
})
}

// Clear token so it can't be reuse
// Generate permanent session token
const payload = { id: player.user_id }
const sessionToken = jwt.sign(payload, process.env.JWT_SECRET!, { expiresIn: '30d' })
// Update user → remove magic link + set permanent token
await prisma.player.update({
where: { user_id: player.user_id },
data: {
magicToken: null,
tokenExpiry: null
tokenExpiry: null,
sessionToken,
}
})

const jwtToken = jwt.sign(
{
id: player.user_id,
email: player.email,
username: player.username,
role: player.role
},
process.env.JWT_SECRET!,
{ expiresIn: "1h" }
)

// Set cookie for frontend auth session
setCookie(event, 'srtoken', jwtToken, {
console.log(`Player ${player.username} logged in with session token.`)
// Set auth cookie
setCookie(event, "srtoken", sessionToken, {
httpOnly: true,
path: '/',
maxAge: 60 * 60
path: "/",
maxAge: 60 * 60 * 24 * 30, // 30 days
sameSite: "lax",
secure: false
})

return { message: "Login successful", username: player.username }
console.log(`Set srtoken cookie for player ${player.username}.`)
// Set username cookie
setCookie(event, "sruser", player.username, {
path: "/",
maxAge: 60 * 60 * 24 * 30,
sameSite: "lax",
secure: false
})

console.log(`Set sruser cookie for player ${player.username}.`)


// Redirect to player page
return sendRedirect(event, "/player")
})
Loading