Skip to content

Commit

Permalink
Merge pull request code100x#115 from N4r35h/main
Browse files Browse the repository at this point in the history
Add basic Guest play support
  • Loading branch information
siinghd authored Jul 10, 2024
2 parents 394065b + 20bf030 commit e718e13
Show file tree
Hide file tree
Showing 16 changed files with 182 additions and 40 deletions.
5 changes: 4 additions & 1 deletion apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"dependencies": {
"@repo/db": "*",
"@types/express-session": "^1.18.0",
"cookie-parser": "^1.4.6",
"cookie-session": "^2.1.0",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
Expand All @@ -24,9 +25,11 @@
"jsonwebtoken": "^9.0.2",
"passport": "^0.7.0",
"passport-github2": "^0.1.12",
"passport-google-oauth20": "^2.0.0"
"passport-google-oauth20": "^2.0.0",
"uuid": "^9.0.1"
},
"devDependencies": {
"@types/cookie-parser": "^1.4.7",
"@types/cookie-session": "^2.0.49",
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/consts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const COOKIE_MAX_AGE = 24 * 60 * 60 * 1000;
6 changes: 5 additions & 1 deletion apps/backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@ import authRoute from './router/auth';
import dotenv from 'dotenv';
import session from 'express-session';
import passport from 'passport';
import cookieParser from 'cookie-parser';
import { COOKIE_MAX_AGE } from './consts';

const app = express();

dotenv.config();
app.use(express.json());
app.use(cookieParser());
app.use(
session({
secret: process.env.COOKIE_SECRET || 'keyboard cat',
resave: false,
saveUninitialized: false,
cookie: { secure: false, maxAge: 24 * 60 * 60 * 1000 },
cookie: { secure: false, maxAge: COOKIE_MAX_AGE },
}),
);

Expand Down
60 changes: 57 additions & 3 deletions apps/backend/src/router/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,58 @@ import { Request, Response, Router } from 'express';
import passport from 'passport';
import jwt from 'jsonwebtoken';
import { db } from '../db';
import { v4 as uuidv4 } from 'uuid';
import { COOKIE_MAX_AGE } from '../consts';
const router = Router();

const CLIENT_URL =
process.env.AUTH_REDIRECT_URL ?? 'http://localhost:5173/game/random';
const JWT_SECRET = process.env.JWT_SECRET || 'your_secret_key';

interface User {
interface userJwtClaims {
userId: string;
name: string;
isGuest?: boolean;
}

interface UserDetails {
id: string;
token?: string;
name: string;
isGuest?: boolean;
}

// this route is to be hit when the user wants to login as a guest
router.post('/guest', async (req: Request, res: Response) => {
const bodyData = req.body;
let guestUUID = 'guest-' + uuidv4();

const user = await db.user.create({
data: {
username: guestUUID,
email: guestUUID + '@chess100x.com',
name: bodyData.name || guestUUID,
provider: 'GUEST',
},
});

const token = jwt.sign(
{ userId: user.id, name: user.name, isGuest: true },
JWT_SECRET,
);
const UserDetails: UserDetails = {
id: user.id,
name: user.name!,
token: token,
isGuest: true,
};
res.cookie('guest', token, { maxAge: COOKIE_MAX_AGE });
res.json(UserDetails);
});

router.get('/refresh', async (req: Request, res: Response) => {
if (req.user) {
const user = req.user as User;
const user = req.user as UserDetails;

// Token is issued so it can be shared b/w HTTP and ws server
// Todo: Make this temporary and add refresh logic here
Expand All @@ -25,12 +64,26 @@ router.get('/refresh', async (req: Request, res: Response) => {
},
});

const token = jwt.sign({ userId: user.id }, JWT_SECRET);
const token = jwt.sign({ userId: user.id, name: userDb?.name }, JWT_SECRET);
res.json({
token,
id: user.id,
name: userDb?.name,
});
} else if (req.cookies && req.cookies.guest) {
const decoded = jwt.verify(req.cookies.guest, JWT_SECRET) as userJwtClaims;
const token = jwt.sign(
{ userId: decoded.userId, name: decoded.name, isGuest: true },
JWT_SECRET,
);
let User: UserDetails = {
id: decoded.userId,
name: decoded.name,
token: token,
isGuest: true,
};
res.cookie('guest', token, { maxAge: COOKIE_MAX_AGE });
res.json(User);
} else {
res.status(401).json({ success: false, message: 'Unauthorized' });
}
Expand All @@ -41,6 +94,7 @@ router.get('/login/failed', (req: Request, res: Response) => {
});

router.get('/logout', (req: Request, res: Response) => {
res.clearCookie('guest');
req.logout((err) => {
if (err) {
console.error('Error logging out:', err);
Expand Down
22 changes: 22 additions & 0 deletions apps/frontend/src/components/PlayerTitle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Player } from "../screens/Game"

interface PlayerTitleProps {
player: Player | undefined
isSelf?: boolean
}

export const PlayerTitle = ({player, isSelf}: PlayerTitleProps) => {
return (
<div className="flex gap-1">
{player && player.id.startsWith("guest") &&
<p className="text-gray-500">
[Guest]
</p>
}
<p>{player && player.name}</p>
{isSelf &&
<p className="text-gray-500">(You)</p>
}
</div>
)
}
25 changes: 23 additions & 2 deletions apps/frontend/src/components/UserAvatar.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
export const UserAvatar = ({ name }: { name: string }) => {
return <div className="text-white">{name}</div>;
import { useUser } from '@repo/store/useUser';
import { Metadata, Player } from '../screens/Game';

interface UserAvatarProps {
gameMetadata: Metadata | null;
self?: boolean;
}

export const UserAvatar = ({ gameMetadata, self }: UserAvatarProps) => {
const user = useUser();
let player: Player;
if (gameMetadata?.blackPlayer.id === user.id) {
player = self ? gameMetadata.blackPlayer : gameMetadata.whitePlayer;
} else {
player = self ? gameMetadata?.whitePlayer! : gameMetadata?.blackPlayer!;
}

return (
<div className="text-white flex gap-2 ">
<p>{player?.name}</p>
{player?.isGuest && <p className="text-gray-500">[Guest]</p>}
</div>
);
};
27 changes: 10 additions & 17 deletions apps/frontend/src/screens/Game.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ export interface GameResult {

const GAME_TIME_MS = 10 * 60 * 1000;

export interface Player {
id: string;
name: string;
isGuest: boolean;
}
import { useRecoilValue, useSetRecoilState } from 'recoil';

import { movesAtom, userSelectedMoveIndexAtom } from '@repo/store/chessBoard';
Expand All @@ -46,9 +51,9 @@ import ExitGameModel from '@/components/ExitGameModel';

const moveAudio = new Audio(MoveSound);

interface Metadata {
blackPlayer: { id: string; name: string };
whitePlayer: { id: string; name: string };
export interface Metadata {
blackPlayer: Player;
whitePlayer: Player;
}

export const Game = () => {
Expand Down Expand Up @@ -267,13 +272,7 @@ export const Game = () => {
{started && (
<div className="mb-4">
<div className="flex justify-between">
<UserAvatar
name={
user.id === gameMetadata?.whitePlayer?.id
? gameMetadata?.blackPlayer?.name
: gameMetadata?.whitePlayer?.name ?? ''
}
/>
<UserAvatar gameMetadata={gameMetadata} />
{getTimer(
user.id === gameMetadata?.whitePlayer?.id
? player2TimeConsumed
Expand All @@ -299,13 +298,7 @@ export const Game = () => {
</div>
{started && (
<div className="mt-4 flex justify-between">
<UserAvatar
name={
user.id === gameMetadata?.blackPlayer?.id
? gameMetadata?.blackPlayer?.name
: gameMetadata?.whitePlayer?.name ?? ''
}
/>
<UserAvatar gameMetadata={gameMetadata} self />
{getTimer(
user.id === gameMetadata?.blackPlayer?.id
? player2TimeConsumed
Expand Down
24 changes: 23 additions & 1 deletion apps/frontend/src/screens/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { useNavigate } from 'react-router-dom';
import { useRef } from 'react';
import { useRecoilState } from 'recoil';
import { userAtom } from '@repo/store/userAtom';

const BACKEND_URL =
import.meta.env.VITE_APP_BACKEND_URL ?? 'http://localhost:3000';

const Login = () => {
const navigate = useNavigate();
const guestName = useRef<HTMLInputElement>(null);
const [_, setUser] = useRecoilState(userAtom);

const google = () => {
window.open(`${BACKEND_URL}/auth/google`, '_self');
Expand All @@ -14,6 +19,22 @@ const Login = () => {
window.open(`${BACKEND_URL}/auth/github`, '_self');
};

const loginAsGuest = async () => {
const response = await fetch(`${BACKEND_URL}/auth/guest`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify({
name: (guestName.current && guestName.current.value) || '',
}),
});
const user = await response.json();
setUser(user);
navigate('/game/random');
};

return (
<div className="flex flex-col items-center justify-center h-screen text-textMain">
<h1 className="text-4xl font-bold mb-8 text-center text-green-500 drop-shadow-lg">
Expand Down Expand Up @@ -44,12 +65,13 @@ const Login = () => {
</div>
<input
type="text"
ref={guestName}
placeholder="Username"
className="border px-4 py-2 rounded-md mb-4 w-full md:w-64"
/>
<button
className="bg-green-500 text-white px-4 py-2 rounded-md hover:bg-green-600 transition-colors duration-300"
onClick={() => navigate('/game/random')}
onClick={() => loginAsGuest()}
>
Enter as guest
</button>
Expand Down
9 changes: 7 additions & 2 deletions apps/ws/src/Game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,19 +125,24 @@ export class Game {
return;
}

const WhitePlayer = users.find((user) => user.id === this.player1UserId);
const BlackPlayer = users.find((user) => user.id === this.player2UserId);

socketManager.broadcast(
this.gameId,
JSON.stringify({
type: INIT_GAME,
payload: {
gameId: this.gameId,
whitePlayer: {
name: users.find((user) => user.id === this.player1UserId)?.name,
name: WhitePlayer?.name,
id: this.player1UserId,
isGuest: WhitePlayer?.provider === AuthProvider.GUEST,
},
blackPlayer: {
name: users.find((user) => user.id === this.player2UserId)?.name,
name: BlackPlayer?.name,
id: this.player2UserId,
isGuest: BlackPlayer?.provider === AuthProvider.GUEST,
},
fen: this.board.fen(),
moves: [],
Expand Down
6 changes: 3 additions & 3 deletions apps/ws/src/GameManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export class GameManager {
const game = this.games.find((game) => game.gameId === gameId);
if (game) {
game.makeMove(user, message.payload.move);
if (game.result) {
if (game.result) {
this.removeGame(game.gameId);
}
}
Expand Down Expand Up @@ -172,9 +172,9 @@ export class GameManager {
gameFromDb?.whitePlayerId!,
gameFromDb?.blackPlayerId!,
gameFromDb.id,
gameFromDb.startAt
gameFromDb.startAt,
);
game.seedMoves(gameFromDb?.moves || [])
game.seedMoves(gameFromDb?.moves || []);
this.games.push(game);
availableGame = game;
}
Expand Down
9 changes: 7 additions & 2 deletions apps/ws/src/SocketManager.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import { randomUUID } from 'crypto';
import { WebSocket } from 'ws';
import { userJwtClaims } from './auth';

export class User {
public socket: WebSocket;
public id: string;
public userId: string;
public name: string;
public isGuest?: boolean;

constructor(socket: WebSocket, userId: string) {
constructor(socket: WebSocket, userJwtClaims: userJwtClaims) {
this.socket = socket;
this.userId = userId;
this.userId = userJwtClaims.userId;
this.id = randomUUID();
this.name = userJwtClaims.name;
this.isGuest = userJwtClaims.isGuest;
}
}

Expand Down
15 changes: 12 additions & 3 deletions apps/ws/src/auth/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import jwt from 'jsonwebtoken';
import { User } from '../SocketManager';
import { Player } from '../Game';
import { WebSocket } from 'ws';

const JWT_SECRET = process.env.JWT_SECRET || 'your_secret_key';

export const extractUserId = (token: string) => {
const decoded = jwt.verify(token, JWT_SECRET) as { userId: string };
return decoded.userId;
export interface userJwtClaims {
userId: string;
name: string;
isGuest?: boolean;
}

export const extractAuthUser = (token: string, ws: WebSocket): User => {
const decoded = jwt.verify(token, JWT_SECRET) as userJwtClaims;
return new User(ws, decoded);
};
Loading

0 comments on commit e718e13

Please sign in to comment.