Commit bfbe448b authored by Zak Stephens's avatar Zak Stephens

Verify that the authenticated player is the action is being performed on, start player movement.

parent 0e21bd83
module.exports = req => req.protocol + '://' + req.get('Host') + require('url').parse(req.originalUrl).pathname;
......@@ -19,16 +19,13 @@ const app = require('express')();
// TODO: Load from file, db, etc.
// TODO: Proxy.
const world = {
players = []
};
new Proxy(world, {
const gameState = {
players: [],
set:
});
messages: {}
};
......@@ -39,8 +36,9 @@ const app = require('express')();
/* Don't start serving until async dependency setup is complete.
* It makes the code easier to reason about. */
app.use('/api/v1/players', require('./routes/players')(world, playerRepository));
app.use('/api/v1/snapshots', require('./routes/snapshots')(world));
app.use('/api/v1/messages', require('./routes/messages')(gameState));
app.use('/api/v1/players', require('./routes/players')(gameState, playerRepository));
app.use('/api/v1/snapshots', require('./routes/snapshots')(gameState));
const options = {
cert: fs.readFileSync(config.get('tls.certFilePath')),
......
......@@ -104,6 +104,14 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"basic-auth": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
"requires": {
"safe-buffer": "5.1.2"
}
},
"bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
......
'use strict';
/* Create tables in database if they're missing,
* resolve promise with player repository API to act on database. */
const argon2 = require('argon2');
const assert = require('assert');
const util = require('util');
const uuid = require('uuid/v4');
module.exports = db => new Promise((resolve, reject) => {
const dbRunAsync = util.promisify(db.run.bind(db));
const dbGetAsync = util.promisify(db.get.bind(db));
const repository = {
createUser: async (username, password) => {
createPlayer: async (username, password) => {
const passwordHash = await argon2.hash(password);
return dbRunAsync(`INSERT INTO Users VALUES (?, ?, ?, datetime('now'));`,
return dbRunAsync(`INSERT INTO Players VALUES (?, ?, ?, datetime('now'));`,
uuid(),
username,
passwordHash);
},
authPlayer: async (username, password) => {
assert(username != null && username.length > 0 && password != null);
const passwordHash = (await dbGetAsync('SELECT password_hash FROM Players WHERE username = ?;', username)).password_hash;
if (passwordHash == null) {
return false; // The user doesn't exist.
}
return argon2.verify(passwordHash, password);
},
getPlayerId: username => dbGetAsync('SELECT id FROM Players WHERE username = ?;', username),
playerExists: async username => (await dbGetAsync('SELECT EXISTS (SELECT 1 FROM Players WHERE username = ?) AS player_exists;', username)).player_exists === 1,
};
/* Create database tables, if necessary. */
const createUserTableSql = `
CREATE TABLE IF NOT EXISTS Users (
const createPlayerTableSql = `
CREATE TABLE IF NOT EXISTS Players (
id TEXT PRIMARY KEY NOT NULL,
username TEXT NOT NULL UNIQUE,
password_hash BLOB NOT NULL,
......@@ -35,7 +53,7 @@ module.exports = db => new Promise((resolve, reject) => {
);
`;
dbRunAsync(createUserTableSql)
dbRunAsync(createPlayerTableSql)
.then(() => resolve(repository))
.catch(error => reject(error));
......
'use strict';
module.exports = gameState => {
const nextId = (() => {
let id = 0;
return () => id++;
})();
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
// XXX: Might want to limit this later...
res.json(gameState.messages);
});
// TODO: Auth
router.post('/', express.json(), (req, res) => {
if (req.body.body == null) {
return res.sendStatus(400);
}
// TODO: Validate other fields
const id = nextId();
gameState.messages[id] = req.body;
res.sendStatus(204);
});
return router;
};
module.exports = (world, playerRepository)=> {
const assert = require('assert');
module.exports = (gameState, playerRepository) => {
/* Verify HTTP Basic Auth username/password match a username/password hash in
* the database, and that the given username is also expected. */
function verifyBasicAuthPlayerIs(username) {
assert(username && username.length > 0);
return async (req, res, next) => {
const user = require('basic-auth')(req);
if (user == null
|| user.name.length == 0
|| !(await playerRepository.authPlayer(user.name, user.pass))) {
return res.set('WWW-Authenticate', `Basic realm="Wayfarer's Rest"`).sendStatus(401); // Unauthorized
}
if (user.name != username) {
return res.sendStatus(403); // Forbidden
}
next();
};
}
const express = require('express');
const router = express.Router();
router.post('/', express.json(), (req, res) => {
/* Create player */
// TODO: json validation middleware
// TODO: data validation middleware ("must contain username and password")
router.post('/', express.json(), async (req, res, next) => {
if (req.body.username == null || req.body.password == null) {
return res.sendStatus(400); // Bad request
}
playerRepository.createUser(req.body.username, req.body.password)
.then(() => res.sendStatus(201))
.catch(next);
if (await playerRepository.playerExists(req.body.username)) {
return res.sendStatus(409); // Conflict
}
playerRepository.createPlayer(req.body.username, req.body.password)
.then(() => res.sendStatus(204))
.catch(next);
});
router.post('/:username/movements', (req, res) => {
/* Move player */
router.post('/:username/movements',
(req, res, next) => verifyBasicAuthPlayerIs(req.params.username)(req, res, next),
express.json(),
(req, res) => {
const position = { x: 0, y: 0 }; // TODO: Get from player entity
const newPosition = position;
});
switch (req.body.direction) {
case 'left':
--newPosition.x;
break;
case 'right':
++newPosition.x;
break;
case 'up':
--newPosition.y;
break;
case 'down':
++newPosition.y;
break;
default:
return res.sendStatus(400); // Bad request
}
console.log(newPosition); // XXX
// TODO: Collision check. If collide, 409 (Conflict).
res.sendStatus(204);
});
return router;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment