Update server/src/index.js
This commit is contained in:
@@ -24,17 +24,15 @@ const WORLD = {
|
|||||||
|
|
||||||
// ===== Auth helpers =====
|
// ===== Auth helpers =====
|
||||||
|
|
||||||
const COOKIE = 'auth';
|
function signToken(cid) {
|
||||||
|
return jwt.sign({ cid }, process.env.JWT_SECRET, { expiresIn: '30d' });
|
||||||
function signToken(uid) {
|
|
||||||
return jwt.sign({ uid }, process.env.JWT_SECRET, { expiresIn: '30d' });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function authFromReq(req) {
|
function authFromReq(req) {
|
||||||
try {
|
try {
|
||||||
const token = req.cookies?.[COOKIE];
|
const token = req.cookies?.[COOKIE];
|
||||||
if (!token) return null;
|
if (!token) return null;
|
||||||
return jwt.verify(token, process.env.JWT_SECRET);
|
return jwt.verify(token, process.env.JWT_SECRET); // { cid, iat, exp }
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -42,13 +40,16 @@ function authFromReq(req) {
|
|||||||
|
|
||||||
// ===== HTTP API =====
|
// ===== HTTP API =====
|
||||||
|
|
||||||
|
// Register a new character with username + password
|
||||||
app.post('/api/register', async (req, res) => {
|
app.post('/api/register', async (req, res) => {
|
||||||
const { username, password } = req.body || {};
|
const { username, password } = req.body || {};
|
||||||
if (!username || !password)
|
if (!username || !password) {
|
||||||
return res.status(400).json({ error: 'Missing fields' });
|
return res.status(400).json({ error: 'Missing username or password' });
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const user = await Users.create(username, password);
|
const ch = await Characters.create(username, password);
|
||||||
const token = signToken(user.id);
|
const token = signToken(ch.id);
|
||||||
res
|
res
|
||||||
.cookie(COOKIE, token, { httpOnly: true, sameSite: 'lax' })
|
.cookie(COOKIE, token, { httpOnly: true, sameSite: 'lax' })
|
||||||
.json({ ok: true });
|
.json({ ok: true });
|
||||||
@@ -58,29 +59,40 @@ app.post('/api/register', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Login as a specific character
|
||||||
app.post('/api/login', async (req, res) => {
|
app.post('/api/login', async (req, res) => {
|
||||||
const { username, password } = req.body || {};
|
const { username, password } = req.body || {};
|
||||||
const user = await Users.verify(username, password);
|
if (!username || !password) {
|
||||||
if (!user) return res.status(401).json({ error: 'Invalid login' });
|
return res.status(400).json({ error: 'Missing username or password' });
|
||||||
const token = signToken(user.id);
|
}
|
||||||
|
|
||||||
|
const ch = await Characters.verify(username, password);
|
||||||
|
if (!ch) return res.status(401).json({ error: 'Invalid login' });
|
||||||
|
|
||||||
|
const token = signToken(ch.id);
|
||||||
res
|
res
|
||||||
.cookie(COOKIE, token, { httpOnly: true, sameSite: 'lax' })
|
.cookie(COOKIE, token, { httpOnly: true, sameSite: 'lax' })
|
||||||
.json({ ok: true });
|
.json({ ok: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Logout: clear cookie
|
||||||
app.post('/api/logout', (req, res) => {
|
app.post('/api/logout', (req, res) => {
|
||||||
res.clearCookie(COOKIE).json({ ok: true });
|
res.clearCookie(COOKIE).json({ ok: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Current character + inventory
|
||||||
app.get('/api/me', async (req, res) => {
|
app.get('/api/me', async (req, res) => {
|
||||||
const auth = authFromReq(req);
|
const auth = authFromReq(req);
|
||||||
if (!auth) return res.status(401).json({ error: 'Not logged in' });
|
if (!auth) return res.status(401).json({ error: 'Not logged in' });
|
||||||
const ch = await Characters.getByUserId(auth.uid);
|
|
||||||
if (!ch) return res.status(404).json({ error: 'No character' });
|
const ch = await Characters.getById(auth.cid);
|
||||||
|
if (!ch) return res.status(404).json({ error: 'Character not found' });
|
||||||
|
|
||||||
const inv = await Inventory.all(ch.id);
|
const inv = await Inventory.all(ch.id);
|
||||||
res.json({ character: ch, inventory: inv, world: WORLD });
|
res.json({ character: ch, inventory: inv, world: WORLD });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// static client
|
// static client
|
||||||
app.use(express.static(new URL('../static', import.meta.url).pathname));
|
app.use(express.static(new URL('../static', import.meta.url).pathname));
|
||||||
|
|
||||||
@@ -113,7 +125,6 @@ spawnNodes();
|
|||||||
const socketsToPlayers = new Map(); // socket.id -> { x,y,name,uid,charId }
|
const socketsToPlayers = new Map(); // socket.id -> { x,y,name,uid,charId }
|
||||||
|
|
||||||
io.on('connection', socket => {
|
io.on('connection', socket => {
|
||||||
// auth handshake
|
|
||||||
socket.on('auth:join', async ack => {
|
socket.on('auth:join', async ack => {
|
||||||
try {
|
try {
|
||||||
const cookieHeader = socket.handshake.headers.cookie || '';
|
const cookieHeader = socket.handshake.headers.cookie || '';
|
||||||
@@ -121,20 +132,23 @@ io.on('connection', socket => {
|
|||||||
if (!match) return ack?.({ error: 'no auth cookie' });
|
if (!match) return ack?.({ error: 'no auth cookie' });
|
||||||
|
|
||||||
const token = match[1];
|
const token = match[1];
|
||||||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
const decoded = jwt.verify(token, process.env.JWT_SECRET); // { cid }
|
||||||
const ch = await Characters.getByUserId(decoded.uid);
|
|
||||||
if (!ch) return ack?.({ error: 'no character' });
|
const ch = await Characters.getById(decoded.cid);
|
||||||
|
if (!ch) return ack?.({ error: 'character missing' });
|
||||||
|
|
||||||
const player = {
|
const player = {
|
||||||
x: ch.x,
|
x: ch.x,
|
||||||
y: ch.y,
|
y: ch.y,
|
||||||
name: ch.name,
|
name: ch.username,
|
||||||
uid: decoded.uid,
|
cid: decoded.cid,
|
||||||
charId: ch.id
|
charId: ch.id
|
||||||
};
|
};
|
||||||
socketsToPlayers.set(socket.id, player);
|
socketsToPlayers.set(socket.id, player);
|
||||||
|
|
||||||
socket.join('world');
|
socket.join('world');
|
||||||
|
|
||||||
|
// Send initial nodes in same handler or from separate listener
|
||||||
socket.emit(
|
socket.emit(
|
||||||
'nodes:init',
|
'nodes:init',
|
||||||
nodes.map(n => ({
|
nodes.map(n => ({
|
||||||
@@ -148,7 +162,7 @@ io.on('connection', socket => {
|
|||||||
|
|
||||||
ack?.({
|
ack?.({
|
||||||
ok: true,
|
ok: true,
|
||||||
you: { name: ch.name, x: ch.x, y: ch.y },
|
you: { name: ch.username, x: ch.x, y: ch.y },
|
||||||
world: WORLD
|
world: WORLD
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
Reference in New Issue
Block a user