clerk auth fixed
This commit is contained in:
122
party/index.ts
122
party/index.ts
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import type * as Party from 'partykit/server';
|
||||
import { createClerkClient, verifyToken } from '@clerk/backend';
|
||||
|
||||
interface GameState {
|
||||
totalClicks: number;
|
||||
@@ -12,22 +13,23 @@ interface GameState {
|
||||
currentClickImage: string;
|
||||
}
|
||||
|
||||
interface ClickMessage {
|
||||
type: 'click';
|
||||
userId: string;
|
||||
userName: string;
|
||||
interface AuthenticatedMessage {
|
||||
token: string;
|
||||
userId: string; // Expected userId from client
|
||||
userName: string; // Expected userName from client
|
||||
}
|
||||
|
||||
interface PurchaseUpgradeMessage {
|
||||
interface ClickMessage extends AuthenticatedMessage {
|
||||
type: 'click';
|
||||
}
|
||||
|
||||
interface PurchaseUpgradeMessage extends AuthenticatedMessage {
|
||||
type: 'purchase-upgrade';
|
||||
userId: string;
|
||||
upgradeId: string;
|
||||
}
|
||||
|
||||
interface UserJoinMessage {
|
||||
interface UserJoinMessage extends AuthenticatedMessage {
|
||||
type: 'user-join';
|
||||
userId: string;
|
||||
userName: string;
|
||||
}
|
||||
|
||||
type Message = ClickMessage | PurchaseUpgradeMessage | UserJoinMessage;
|
||||
@@ -50,7 +52,13 @@ const MILESTONES = [
|
||||
];
|
||||
|
||||
export default class GameServer implements Party.Server {
|
||||
constructor(readonly party: Party.Party) { }
|
||||
clerkClient: ReturnType<typeof createClerkClient>;
|
||||
|
||||
constructor(readonly party: Party.Party) {
|
||||
this.clerkClient = createClerkClient({
|
||||
secretKey: party.env.CLERK_SECRET_KEY as string,
|
||||
});
|
||||
}
|
||||
|
||||
gameState: GameState = {
|
||||
totalClicks: 0,
|
||||
@@ -68,68 +76,104 @@ export default class GameServer implements Party.Server {
|
||||
|
||||
autoClickInterval?: NodeJS.Timeout;
|
||||
|
||||
onConnect(conn: Party.Connection, _ctx: Party.ConnectionContext) {
|
||||
async onConnect(conn: Party.Connection, _ctx: Party.ConnectionContext) {
|
||||
conn.send(JSON.stringify({ type: 'game-state', state: this.gameState }));
|
||||
}
|
||||
|
||||
onMessage(message: string, _sender: Party.Connection) {
|
||||
async onMessage(message: string, sender: Party.Connection) {
|
||||
const data = JSON.parse(message) as Message;
|
||||
|
||||
switch (data.type) {
|
||||
case 'user-join':
|
||||
this.handleUserJoin(data);
|
||||
break;
|
||||
case 'click':
|
||||
this.handleClick(data);
|
||||
break;
|
||||
case 'purchase-upgrade':
|
||||
this.handlePurchaseUpgrade(data);
|
||||
break;
|
||||
// Verify the Clerk token for authenticated messages
|
||||
if ('token' in data && data.token) {
|
||||
try {
|
||||
const sessionClaims = await verifyToken(data.token, {
|
||||
jwtKey: this.party.env.CLERK_JWT_KEY as string,
|
||||
});
|
||||
const authenticatedUserId = sessionClaims.sub;
|
||||
|
||||
// Ensure the userId from the client matches the authenticated userId
|
||||
if (authenticatedUserId !== data.userId) {
|
||||
console.warn(`User ID mismatch: Client sent ${data.userId}, but token is for ${authenticatedUserId}`);
|
||||
sender.close(); // Close connection for potential tampering
|
||||
return;
|
||||
}
|
||||
|
||||
// Update user info on the server with authenticated data
|
||||
if (!this.gameState.users[authenticatedUserId]) {
|
||||
this.gameState.users[authenticatedUserId] = {
|
||||
name: data.userName, // Use the name provided by the client, which comes from Clerk
|
||||
clicks: 0,
|
||||
lastSeen: Date.now()
|
||||
};
|
||||
}
|
||||
this.gameState.users[authenticatedUserId].lastSeen = Date.now();
|
||||
this.gameState.users[authenticatedUserId].name = data.userName; // Update name in case it changed
|
||||
|
||||
switch (data.type) {
|
||||
case 'user-join':
|
||||
// User join is handled by the token verification and user state update above
|
||||
break;
|
||||
case 'click':
|
||||
this.handleClick(data, authenticatedUserId);
|
||||
break;
|
||||
case 'purchase-upgrade':
|
||||
this.handlePurchaseUpgrade(data, authenticatedUserId);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Clerk token verification failed:', error);
|
||||
sender.close(); // Close connection if token is invalid
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// For messages without a token (e.g., initial connection before token is sent, or unauthenticated actions)
|
||||
// For this game, we only allow authenticated actions.
|
||||
console.warn('Received unauthenticated message, closing connection.');
|
||||
sender.close();
|
||||
return;
|
||||
}
|
||||
|
||||
this.broadcast();
|
||||
}
|
||||
|
||||
// handleUserJoin is now largely integrated into onMessage's token verification
|
||||
// Keeping it for now, but it might become redundant or simplified.
|
||||
handleUserJoin(data: UserJoinMessage) {
|
||||
if (!this.gameState.users[data.userId]) {
|
||||
this.gameState.users[data.userId] = {
|
||||
name: data.userName,
|
||||
clicks: 0,
|
||||
lastSeen: Date.now()
|
||||
};
|
||||
}
|
||||
this.gameState.users[data.userId].lastSeen = Date.now();
|
||||
// This function is now mostly handled by the onMessage token verification logic
|
||||
// It ensures the user exists in gameState.users and updates lastSeen/name.
|
||||
// No explicit action needed here beyond what onMessage does.
|
||||
}
|
||||
|
||||
handleClick(data: ClickMessage) {
|
||||
handleClick(data: ClickMessage, authenticatedUserId: string) {
|
||||
const clickValue = this.gameState.clickMultiplier;
|
||||
|
||||
this.gameState.totalClicks += clickValue;
|
||||
|
||||
if (!this.gameState.users[data.userId]) {
|
||||
this.gameState.users[data.userId] = {
|
||||
name: data.userName,
|
||||
// Ensure user exists (should be handled by onMessage's token verification)
|
||||
if (!this.gameState.users[authenticatedUserId]) {
|
||||
this.gameState.users[authenticatedUserId] = {
|
||||
name: data.userName, // Use the name from the client, which comes from Clerk
|
||||
clicks: 0,
|
||||
lastSeen: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
this.gameState.users[data.userId].clicks += clickValue;
|
||||
this.gameState.users[data.userId].lastSeen = Date.now();
|
||||
this.gameState.users[authenticatedUserId].clicks += clickValue;
|
||||
this.gameState.users[authenticatedUserId].lastSeen = Date.now();
|
||||
|
||||
this.checkMilestones();
|
||||
}
|
||||
|
||||
handlePurchaseUpgrade(data: PurchaseUpgradeMessage) {
|
||||
handlePurchaseUpgrade(data: PurchaseUpgradeMessage, authenticatedUserId: string) {
|
||||
const upgrade = UPGRADES[data.upgradeId as keyof typeof UPGRADES];
|
||||
const currentUpgrade = this.gameState.upgrades[data.upgradeId];
|
||||
|
||||
if (!upgrade || !currentUpgrade) return;
|
||||
|
||||
const userClicks = this.gameState.users[data.userId]?.clicks || 0;
|
||||
const userClicks = this.gameState.users[authenticatedUserId]?.clicks || 0;
|
||||
|
||||
if (userClicks >= currentUpgrade.cost) {
|
||||
this.gameState.users[data.userId].clicks -= currentUpgrade.cost;
|
||||
this.gameState.users[authenticatedUserId].clicks -= currentUpgrade.cost;
|
||||
currentUpgrade.owned += 1;
|
||||
currentUpgrade.cost = Math.floor(upgrade.baseCost * Math.pow(upgrade.multiplier, currentUpgrade.owned));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user