RLG-Web: separate logging in and starting a game.
The user now logs in with a username and password, receiving a token which is then used for any actions requiring authentication. Starting a game is one such action. Games use a different set of id keys. This allows users to supply their passwords once and then play any number of successive games. Also, newly registered users do not need to supply their passwords again.
This commit is contained in:
parent
94e9fa330c
commit
012c86faa8
3 changed files with 193 additions and 98 deletions
188
rlgwebd.js
188
rlgwebd.js
|
|
@ -21,10 +21,7 @@ var dropToGID = 501;
|
|||
var serveStaticRoot = "/var/www/"; // inside the chroot
|
||||
var playtimeout = 3600000; // Idle time before games are autosaved, in ms
|
||||
|
||||
/* Global state */
|
||||
var sessions = {};
|
||||
var allowlogin = true;
|
||||
|
||||
/* Data on the games available. */
|
||||
var games = {
|
||||
"rogue3": {
|
||||
"name": "Rogue V3",
|
||||
|
|
@ -48,6 +45,11 @@ var games = {
|
|||
}
|
||||
};
|
||||
|
||||
/* Global state */
|
||||
var logins = {};
|
||||
var sessions = {};
|
||||
var allowlogin = true;
|
||||
|
||||
/* Constructor for TermSessions. Note that it opens the terminal and
|
||||
* adds itself to the sessions dict. It currently assumes the user has
|
||||
* been authenticated.
|
||||
|
|
@ -64,9 +66,9 @@ function TermSession(game, user, files, dims) {
|
|||
this.player = user;
|
||||
/* This order seems to best avoid race conditions... */
|
||||
this.alive = false;
|
||||
this.sessid = randkey();
|
||||
this.sessid = randkey(2);
|
||||
while (this.sessid in sessions) {
|
||||
this.sessid = randkey();
|
||||
this.sessid = randkey(2);
|
||||
}
|
||||
/* Grab a spot in the sessions table. */
|
||||
sessions[this.sessid] = this;
|
||||
|
|
@ -225,12 +227,20 @@ function timestamp() {
|
|||
return sd.replace("T", ".");
|
||||
}
|
||||
|
||||
function randkey() {
|
||||
rnum = Math.floor(Math.random() * 65536 * 65536);
|
||||
hexstr = rnum.toString(16);
|
||||
while (hexstr.length < 8)
|
||||
hexstr = "0" + hexstr;
|
||||
return hexstr;
|
||||
function randkey(words) {
|
||||
if (!words || !(words > 0))
|
||||
words = 1;
|
||||
function rand32() {
|
||||
rnum = Math.floor(Math.random() * 65536 * 65536);
|
||||
hexstr = rnum.toString(16);
|
||||
while (hexstr.length < 8)
|
||||
hexstr = "0" + hexstr;
|
||||
return hexstr;
|
||||
}
|
||||
var key = "";
|
||||
for (var i = 0; i < words; i++)
|
||||
key += rand32();
|
||||
return key;
|
||||
}
|
||||
|
||||
function tslog() {
|
||||
|
|
@ -295,11 +305,7 @@ function login(req, res, formdata) {
|
|||
sendError(res, 6, null);
|
||||
return;
|
||||
}
|
||||
if (!("game" in formdata)) {
|
||||
sendError(res, 2, "No game specified.");
|
||||
return;
|
||||
}
|
||||
else if (!("name" in formdata)) {
|
||||
if (!("name" in formdata)) {
|
||||
sendError(res, 2, "Username not given.");
|
||||
return;
|
||||
}
|
||||
|
|
@ -307,8 +313,60 @@ function login(req, res, formdata) {
|
|||
sendError(res, 2, "Password not given.");
|
||||
return;
|
||||
}
|
||||
var username = formdata["name"];
|
||||
var password = formdata["pw"];
|
||||
var username = String(formdata["name"]);
|
||||
var password = String(formdata["pw"]);
|
||||
function checkit(code, signal) {
|
||||
/* Checks the exit status, see sqlickrypt.c for details. */
|
||||
if (code != 0) {
|
||||
sendError(res, 3);
|
||||
if (code == 1)
|
||||
tslog("Password check failed for user %s", username);
|
||||
else if (code == 2)
|
||||
tslog("Attempted login by nonexistent user %s", username);
|
||||
else
|
||||
tslog("Login failed: sqlickrypt error %d", code);
|
||||
return;
|
||||
}
|
||||
var lkey = randkey(2);
|
||||
while (lkey in logins)
|
||||
lkey = randkey(2);
|
||||
logins[lkey] = {"name": username, "ts": new Date()};
|
||||
res.writeHead(200, {'Content-Type': 'application/json'});
|
||||
var reply = {"t": "l", "k": lkey, "u": username};
|
||||
res.write(JSON.stringify(reply));
|
||||
res.end();
|
||||
tslog("%s has logged in (key %s)", username, lkey);
|
||||
return;
|
||||
}
|
||||
/* Launch the sqlickrypt utility to check the password. */
|
||||
var pwchecker = child_process.spawn("/bin/sqlickrypt", ["check"]);
|
||||
pwchecker.on("exit", checkit);
|
||||
pwchecker.stdin.end(username + '\n' + password + '\n', "utf8");
|
||||
return;
|
||||
}
|
||||
|
||||
function startgame(req, res, formdata) {
|
||||
if (!allowlogin) {
|
||||
sendError(res, 6, null);
|
||||
return;
|
||||
}
|
||||
if (!("key" in formdata)) {
|
||||
sendError(res, 2, "No key given.");
|
||||
return;
|
||||
}
|
||||
else if (!("game" in formdata)) {
|
||||
sendError(res, 2, "No game specified.");
|
||||
return;
|
||||
}
|
||||
var lkey = String(formdata["key"]);
|
||||
if (!(lkey in logins)) {
|
||||
sendError(res, 1, null);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
logins[lkey].ts = new Date();
|
||||
}
|
||||
var username = logins[lkey].name;
|
||||
var gname = formdata["game"];
|
||||
var dims = [formdata["h"], formdata["w"]];
|
||||
if (!(gname in games)) {
|
||||
|
|
@ -316,10 +374,20 @@ function login(req, res, formdata) {
|
|||
tslog("Request for nonexistant game \"%s\"", gname);
|
||||
return;
|
||||
}
|
||||
// check for an existing game
|
||||
var progressdir = "/dgldir/inprogress-" + games[gname].uname;
|
||||
|
||||
// This sets up the game once starting is approved.
|
||||
function startgame() {
|
||||
fs.readdir(progressdir, function(err, files) {
|
||||
if (!err) {
|
||||
var fre = RegExp("^" + username + ":");
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
if (files[i].match(fre)) {
|
||||
sendError(res, 4, null);
|
||||
tslog("%s is already playing %s", username, gname);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Game starting has been approved.
|
||||
var ts = timestamp();
|
||||
var lockfile = path.join(progressdir, username + ":node:" + ts + ".ttyrec");
|
||||
var ttyrec = path.join("/dgldir/ttyrec", username, gname, ts + ".ttyrec");
|
||||
|
|
@ -340,40 +408,7 @@ function login(req, res, formdata) {
|
|||
sendError(res, 5, "Failed to open TTY");
|
||||
tslog("Unable to allocate TTY for %s", gname);
|
||||
}
|
||||
}
|
||||
function checkit(code, signal) {
|
||||
// check the password
|
||||
if (code != 0) {
|
||||
sendError(res, 3);
|
||||
if (code == 1)
|
||||
tslog("Password check failed for user %s", username);
|
||||
else if (code == 2)
|
||||
tslog("Attempted login by nonexistent user %s", username);
|
||||
else
|
||||
tslog("Login failed: sqlickrypt error %d", code);
|
||||
return;
|
||||
}
|
||||
// check for an existing game
|
||||
fs.readdir(progressdir, function(err, files) {
|
||||
if (!err) {
|
||||
var fre = RegExp("^" + username + ":");
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
if (files[i].match(fre)) {
|
||||
sendError(res, 4, null);
|
||||
tslog("%s is already playing %s", username, gname);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If progressdir isn't readable, start a new game anyway.
|
||||
startgame();
|
||||
});
|
||||
}
|
||||
/* Launch the sqlickrypt utility to check the password. */
|
||||
var checker = child_process.spawn("/bin/sqlickrypt", ["check"]);
|
||||
checker.on("exit", checkit);
|
||||
checker.stdin.end(username + '\n' + password + '\n', "utf8");
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
/* Sets things up for a new user, like dgamelaunch's commands[register] */
|
||||
|
|
@ -408,7 +443,19 @@ function register(req, res, formdata) {
|
|||
else
|
||||
email = formdata["email"];
|
||||
function checkreg(code, signal) {
|
||||
if (code == 4) {
|
||||
if (code === 0) {
|
||||
var lkey = randkey(2);
|
||||
while (lkey in logins)
|
||||
lkey = randkey(2);
|
||||
logins[lkey] = {"name": uname, "ts": new Date()};
|
||||
var reply = {"t": "r", "k": lkey, "u": uname};
|
||||
res.writeHead(200, {'Content-Type': 'application/json'});
|
||||
res.write(JSON.stringify(reply));
|
||||
res.end();
|
||||
tslog("Added new user: %s", uname);
|
||||
regsetup(uname);
|
||||
}
|
||||
else if (code == 4) {
|
||||
sendError(res, 2, "Invalid characters in name or email.");
|
||||
tslog("Attempted registration: %s %s", uname, email);
|
||||
}
|
||||
|
|
@ -416,18 +463,10 @@ function register(req, res, formdata) {
|
|||
sendError(res, 2, "Username " + uname + " is already being used.");
|
||||
tslog("Attempted duplicate registration: %s", uname);
|
||||
}
|
||||
else if (code != 0) {
|
||||
else {
|
||||
sendError(res, 0, null);
|
||||
tslog("sqlickrypt register failed with code %d", code);
|
||||
}
|
||||
else {
|
||||
res.writeHead(200, {'Content-Type': 'application/json'});
|
||||
var reply = {"t": "r", "d": uname};
|
||||
res.write(JSON.stringify(reply));
|
||||
res.end();
|
||||
tslog("Added new user: %s", uname);
|
||||
regsetup(uname);
|
||||
}
|
||||
}
|
||||
var child_adder = child_process.spawn("/bin/sqlickrypt", ["register"]);
|
||||
child_adder.on("exit", checkreg);
|
||||
|
|
@ -435,9 +474,9 @@ function register(req, res, formdata) {
|
|||
return;
|
||||
}
|
||||
|
||||
function logout(term, res) {
|
||||
function endgame(term, res) {
|
||||
if (!term.alive) {
|
||||
sendError(res, 1, null);
|
||||
sendError(res, 7, null);
|
||||
return;
|
||||
}
|
||||
term.close();
|
||||
|
|
@ -534,7 +573,7 @@ function readFeed(res, term) {
|
|||
res.end();
|
||||
}
|
||||
else {
|
||||
sendError(res, 1, null);
|
||||
sendError(res, 7, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -556,7 +595,7 @@ function statusmsg(req, res) {
|
|||
|
||||
var errorcodes = [ "Generic Error", "Not logged in", "Invalid data",
|
||||
"Login failed", "Already playing", "Game launch failed",
|
||||
"Server shutting down" ];
|
||||
"Server shutting down", "Game not in progress" ];
|
||||
|
||||
function sendError(res, ecode, msg) {
|
||||
res.writeHead(200, { "Content-Type": "application/json" });
|
||||
|
|
@ -593,12 +632,12 @@ function webHandler(req, res) {
|
|||
if (req.method == 'POST') {
|
||||
if (target == '/feed') {
|
||||
if (!cterm) {
|
||||
sendError(res, 1, null);
|
||||
sendError(res, 7, null);
|
||||
return;
|
||||
}
|
||||
if (formdata.t == "q") {
|
||||
/* The client wants to terminate the process. */
|
||||
logout(cterm, res);
|
||||
endgame(cterm, res);
|
||||
}
|
||||
else if (formdata.t == "d" && typeof(formdata.d) == "string") {
|
||||
/* process the keys */
|
||||
|
|
@ -619,6 +658,9 @@ function webHandler(req, res) {
|
|||
else if (target == "/addacct") {
|
||||
register(req, res, formdata);
|
||||
}
|
||||
else if (target == "/play") {
|
||||
startgame(req, res, formdata);
|
||||
}
|
||||
else {
|
||||
res.writeHead(405, resheaders);
|
||||
res.end();
|
||||
|
|
@ -632,7 +674,7 @@ function webHandler(req, res) {
|
|||
return;
|
||||
}
|
||||
if (!cterm) {
|
||||
sendError(res, 1, null);
|
||||
sendError(res, 7, null);
|
||||
return;
|
||||
}
|
||||
readFeed(res, cterm);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue