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:
John "Elwin" Edwards 2012-06-07 15:43:06 -07:00
parent 94e9fa330c
commit 012c86faa8
3 changed files with 193 additions and 98 deletions

View file

@ -85,14 +85,8 @@
<span onclick="textsize(true)">Larger</span> <span onclick="textsize(true)">Larger</span>
</div> </div>
</div> </div>
<div class="modal" id="login"> <div class="modal" id="startgame">
<form id="loginform" action="/login" method="post"> <form id="startform" action="/play" method="post">
<div>
Name: <input type="text" name="name" id="input_name">
</div>
<div>
Password: <input type="password" name="pw" id="input_pw">
</div>
<div> <div>
Choose game: <select name="game" id="input_game"> Choose game: <select name="game" id="input_game">
<option label="Rogue V3" value="rogue3">Rogue V3</option> <option label="Rogue V3" value="rogue3">Rogue V3</option>
@ -102,11 +96,24 @@ Choose game: <select name="game" id="input_game">
</select> </select>
</div> </div>
<div> <div>
<input type="submit" value="Play" onclick="formlogin(event)"> <input type="submit" value="Play" onclick="startgame(event)">
</div> </div>
</form> </form>
</div> </div>
<div class="modal" id="login">
<form id="loginform" action="/login" method="post">
<div>
Name: <input type="text" name="name" id="input_name">
</div>
<div>
Password: <input type="password" name="pw" id="input_pw">
</div>
<div>
<input type="submit" value="Log in" onclick="formlogin(event)">
</div>
</form>
<div class="rbutton" onclick="setmode('register')">Register</div> <div class="rbutton" onclick="setmode('register')">Register</div>
</div>
<div class="modal" id="register"> <div class="modal" id="register">
<form id="regform" action="/addacct" method="post"> <form id="regform" action="/addacct" method="post">
<div> <div>

View file

@ -48,6 +48,10 @@ var ajaxstate = {
} }
}; };
/* Login name and key */
var lname = null;
var lcred = null;
function writeData(hexstr) { function writeData(hexstr) {
var codenum; var codenum;
var codes = []; var codes = [];
@ -138,8 +142,8 @@ function processMsg(msg) {
if (!msgDict.t) if (!msgDict.t)
return null; return null;
else if (msgDict.t == "E") { else if (msgDict.t == "E") {
if (msgDict.c == 1 || msgDict.c == 6) { if (msgDict.c == 1 || msgDict.c == 6 || msgDict == 7) {
logout(); gameover();
} }
debug(1, "Server error: " + msgDict.s); debug(1, "Server error: " + msgDict.s);
} }
@ -174,7 +178,7 @@ function processMsg(msg) {
setTitle(msgDict.d); setTitle(msgDict.d);
} }
else if (msgDict.t == "q") { else if (msgDict.t == "q") {
logout(); gameover();
} }
else { else {
debug(1, "Unrecognized server message " + msg); debug(1, "Unrecognized server message " + msg);
@ -357,9 +361,39 @@ function formlogin(ev) {
var loginmsg = {}; var loginmsg = {};
loginmsg["name"] = document.getElementById("input_name").value; loginmsg["name"] = document.getElementById("input_name").value;
loginmsg["pw"] = document.getElementById("input_pw").value; loginmsg["pw"] = document.getElementById("input_pw").value;
loginmsg["game"] = document.getElementById("input_game").value; var req = new XMLHttpRequest();
loginmsg["h"] = 24; req.onreadystatechange = function () {
loginmsg["w"] = 80; if (req.readyState != 4 || req.status != 200)
return;
var reply = JSON.parse(req.responseText);
if (reply.t == 'l') {
/* Success */
lcred = reply.k;
lname = reply.u;
setTitle("Logged in as " + reply.u);
debug(1, "Logged in as " + reply.u + " with id " + reply.k);
setmode("choose");
}
else if (reply.t == 'E') {
debug(1, "Could not log in: " + reply.s);
document.getElementById("input_name").value = "";
document.getElementById("input_pw").value = "";
}
};
req.open('POST', '/login', true);
req.send(JSON.stringify(loginmsg));
return;
}
function startgame(ev) {
ev.preventDefault();
if (termemu.sessid != null || !lcred)
return;
var smsg = {};
smsg["key"] = lcred;
smsg["game"] = document.getElementById("input_game").value;
smsg["h"] = 24;
smsg["w"] = 80;
var req = new XMLHttpRequest(); var req = new XMLHttpRequest();
req.onreadystatechange = function () { req.onreadystatechange = function () {
if (req.readyState != 4 || req.status != 200) if (req.readyState != 4 || req.status != 200)
@ -369,20 +403,17 @@ function formlogin(ev) {
/* Success */ /* Success */
termemu.sessid = reply.id; termemu.sessid = reply.id;
termemu.resize(reply.h, reply.w); termemu.resize(reply.h, reply.w);
setTitle("Playing as " + loginmsg["name"]); setTitle("Playing as " + lname);
debug(1, "Logged in with id " + termemu.sessid); debug(1, "Playing with id " + termemu.sessid);
//document.getElementById("loginform").style.display = "none";
setmode("play"); setmode("play");
getData(); getData();
} }
else if (reply.t == 'E') { else if (reply.t == 'E') {
debug(1, "Could not start game: " + reply.s); debug(1, "Could not start game: " + reply.s);
document.getElementById("input_name").value = "";
document.getElementById("input_pw").value = "";
} }
}; };
req.open('POST', '/login', true); req.open('POST', '/play', true);
req.send(JSON.stringify(loginmsg)); req.send(JSON.stringify(smsg));
return; return;
} }
@ -402,7 +433,11 @@ function formreg(ev) {
if (reply.t == 'r') { if (reply.t == 'r') {
/* Success */ /* Success */
debug(1, "Registered account: " + reply.d); debug(1, "Registered account: " + reply.d);
setmode("login"); lcred = reply.k;
lname = reply.u;
setTitle("Logged in as " + lname);
debug(1, "Logged in as " + lname + "with id " + lcred);
setmode("choose");
} }
else if (reply.t == 'E') { else if (reply.t == 'E') {
debug(1, "Could not register: " + reply.s); debug(1, "Could not register: " + reply.s);
@ -416,7 +451,7 @@ function formreg(ev) {
return; return;
} }
function logout() { function gameover() {
if (termemu.sessid == null) if (termemu.sessid == null)
return; return;
/* TODO IFACE2 If the end was unexpected, tell player the game was saved. */ /* TODO IFACE2 If the end was unexpected, tell player the game was saved. */
@ -426,11 +461,13 @@ function logout() {
nsend = 0; nsend = 0;
nrecv = 0; nrecv = 0;
msgQ = []; msgQ = [];
setmode("login"); setmode("choose");
return; return;
} }
function stop() { function stop() {
if (!termemu.sessid)
return;
var req = new XMLHttpRequest(); var req = new XMLHttpRequest();
req.onreadystatechange = function () { req.onreadystatechange = function () {
if (req.readyState == 4 && req.status == 200) { if (req.readyState == 4 && req.status == 200) {
@ -448,16 +485,25 @@ function setmode(mode, ev) {
ev.preventDefault(); ev.preventDefault();
if (mode == "play") { if (mode == "play") {
document.getElementById("keyboard").style.display = "block"; document.getElementById("keyboard").style.display = "block";
document.getElementById("startgame").style.display = "none";
document.getElementById("login").style.display = "none";
document.getElementById("register").style.display = "none";
}
if (mode == "choose") {
document.getElementById("keyboard").style.display = "none";
document.getElementById("startgame").style.display = "block";
document.getElementById("login").style.display = "none"; document.getElementById("login").style.display = "none";
document.getElementById("register").style.display = "none"; document.getElementById("register").style.display = "none";
} }
else if (mode == "login") { else if (mode == "login") {
document.getElementById("keyboard").style.display = "none"; document.getElementById("keyboard").style.display = "none";
document.getElementById("startgame").style.display = "none";
document.getElementById("login").style.display = "block"; document.getElementById("login").style.display = "block";
document.getElementById("register").style.display = "none"; document.getElementById("register").style.display = "none";
} }
else if (mode == "register") { else if (mode == "register") {
document.getElementById("keyboard").style.display = "none"; document.getElementById("keyboard").style.display = "none";
document.getElementById("startgame").style.display = "none";
document.getElementById("login").style.display = "none"; document.getElementById("login").style.display = "none";
document.getElementById("register").style.display = "block"; document.getElementById("register").style.display = "block";
} }

View file

@ -21,10 +21,7 @@ var dropToGID = 501;
var serveStaticRoot = "/var/www/"; // inside the chroot var serveStaticRoot = "/var/www/"; // inside the chroot
var playtimeout = 3600000; // Idle time before games are autosaved, in ms var playtimeout = 3600000; // Idle time before games are autosaved, in ms
/* Global state */ /* Data on the games available. */
var sessions = {};
var allowlogin = true;
var games = { var games = {
"rogue3": { "rogue3": {
"name": "Rogue V3", "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 /* Constructor for TermSessions. Note that it opens the terminal and
* adds itself to the sessions dict. It currently assumes the user has * adds itself to the sessions dict. It currently assumes the user has
* been authenticated. * been authenticated.
@ -64,9 +66,9 @@ function TermSession(game, user, files, dims) {
this.player = user; this.player = user;
/* This order seems to best avoid race conditions... */ /* This order seems to best avoid race conditions... */
this.alive = false; this.alive = false;
this.sessid = randkey(); this.sessid = randkey(2);
while (this.sessid in sessions) { while (this.sessid in sessions) {
this.sessid = randkey(); this.sessid = randkey(2);
} }
/* Grab a spot in the sessions table. */ /* Grab a spot in the sessions table. */
sessions[this.sessid] = this; sessions[this.sessid] = this;
@ -225,12 +227,20 @@ function timestamp() {
return sd.replace("T", "."); return sd.replace("T", ".");
} }
function randkey() { function randkey(words) {
if (!words || !(words > 0))
words = 1;
function rand32() {
rnum = Math.floor(Math.random() * 65536 * 65536); rnum = Math.floor(Math.random() * 65536 * 65536);
hexstr = rnum.toString(16); hexstr = rnum.toString(16);
while (hexstr.length < 8) while (hexstr.length < 8)
hexstr = "0" + hexstr; hexstr = "0" + hexstr;
return hexstr; return hexstr;
}
var key = "";
for (var i = 0; i < words; i++)
key += rand32();
return key;
} }
function tslog() { function tslog() {
@ -295,11 +305,7 @@ function login(req, res, formdata) {
sendError(res, 6, null); sendError(res, 6, null);
return; return;
} }
if (!("game" in formdata)) { if (!("name" in formdata)) {
sendError(res, 2, "No game specified.");
return;
}
else if (!("name" in formdata)) {
sendError(res, 2, "Username not given."); sendError(res, 2, "Username not given.");
return; return;
} }
@ -307,8 +313,60 @@ function login(req, res, formdata) {
sendError(res, 2, "Password not given."); sendError(res, 2, "Password not given.");
return; return;
} }
var username = formdata["name"]; var username = String(formdata["name"]);
var password = formdata["pw"]; 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 gname = formdata["game"];
var dims = [formdata["h"], formdata["w"]]; var dims = [formdata["h"], formdata["w"]];
if (!(gname in games)) { if (!(gname in games)) {
@ -316,10 +374,20 @@ function login(req, res, formdata) {
tslog("Request for nonexistant game \"%s\"", gname); tslog("Request for nonexistant game \"%s\"", gname);
return; return;
} }
// check for an existing game
var progressdir = "/dgldir/inprogress-" + games[gname].uname; var progressdir = "/dgldir/inprogress-" + games[gname].uname;
fs.readdir(progressdir, function(err, files) {
// This sets up the game once starting is approved. if (!err) {
function startgame() { 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 ts = timestamp();
var lockfile = path.join(progressdir, username + ":node:" + ts + ".ttyrec"); var lockfile = path.join(progressdir, username + ":node:" + ts + ".ttyrec");
var ttyrec = path.join("/dgldir/ttyrec", username, gname, 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"); sendError(res, 5, "Failed to open TTY");
tslog("Unable to allocate TTY for %s", gname); 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] */ /* Sets things up for a new user, like dgamelaunch's commands[register] */
@ -408,7 +443,19 @@ function register(req, res, formdata) {
else else
email = formdata["email"]; email = formdata["email"];
function checkreg(code, signal) { 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."); sendError(res, 2, "Invalid characters in name or email.");
tslog("Attempted registration: %s %s", uname, 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."); sendError(res, 2, "Username " + uname + " is already being used.");
tslog("Attempted duplicate registration: %s", uname); tslog("Attempted duplicate registration: %s", uname);
} }
else if (code != 0) { else {
sendError(res, 0, null); sendError(res, 0, null);
tslog("sqlickrypt register failed with code %d", code); 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"]); var child_adder = child_process.spawn("/bin/sqlickrypt", ["register"]);
child_adder.on("exit", checkreg); child_adder.on("exit", checkreg);
@ -435,9 +474,9 @@ function register(req, res, formdata) {
return; return;
} }
function logout(term, res) { function endgame(term, res) {
if (!term.alive) { if (!term.alive) {
sendError(res, 1, null); sendError(res, 7, null);
return; return;
} }
term.close(); term.close();
@ -534,7 +573,7 @@ function readFeed(res, term) {
res.end(); res.end();
} }
else { 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", var errorcodes = [ "Generic Error", "Not logged in", "Invalid data",
"Login failed", "Already playing", "Game launch failed", "Login failed", "Already playing", "Game launch failed",
"Server shutting down" ]; "Server shutting down", "Game not in progress" ];
function sendError(res, ecode, msg) { function sendError(res, ecode, msg) {
res.writeHead(200, { "Content-Type": "application/json" }); res.writeHead(200, { "Content-Type": "application/json" });
@ -593,12 +632,12 @@ function webHandler(req, res) {
if (req.method == 'POST') { if (req.method == 'POST') {
if (target == '/feed') { if (target == '/feed') {
if (!cterm) { if (!cterm) {
sendError(res, 1, null); sendError(res, 7, null);
return; return;
} }
if (formdata.t == "q") { if (formdata.t == "q") {
/* The client wants to terminate the process. */ /* The client wants to terminate the process. */
logout(cterm, res); endgame(cterm, res);
} }
else if (formdata.t == "d" && typeof(formdata.d) == "string") { else if (formdata.t == "d" && typeof(formdata.d) == "string") {
/* process the keys */ /* process the keys */
@ -619,6 +658,9 @@ function webHandler(req, res) {
else if (target == "/addacct") { else if (target == "/addacct") {
register(req, res, formdata); register(req, res, formdata);
} }
else if (target == "/play") {
startgame(req, res, formdata);
}
else { else {
res.writeHead(405, resheaders); res.writeHead(405, resheaders);
res.end(); res.end();
@ -632,7 +674,7 @@ function webHandler(req, res) {
return; return;
} }
if (!cterm) { if (!cterm) {
sendError(res, 1, null); sendError(res, 7, null);
return; return;
} }
readFeed(res, cterm); readFeed(res, cterm);