From 544ea51c3663070dadd588d9de3e22d6e045743e Mon Sep 17 00:00:00 2001 From: Kaarle Ritvanen Date: Wed, 27 Feb 2013 07:39:26 +0000 Subject: basic login session management --- LICENSE | 2 +- acf/modules/aaa.lua | 3 ++- config/aaa.json | 2 +- protocol.txt | 10 +++++++ server.lua | 76 ++++++++++++++++++++++++++++++++++++++++++----------- 5 files changed, 74 insertions(+), 19 deletions(-) diff --git a/LICENSE b/LICENSE index a0b36bf..da09525 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2012 Kaarle Ritvanen +Copyright (c) 2012-2013 Kaarle Ritvanen All rights reserved. diff --git a/acf/modules/aaa.lua b/acf/modules/aaa.lua index f8e28ca..1704376 100644 --- a/acf/modules/aaa.lua +++ b/acf/modules/aaa.lua @@ -1,5 +1,5 @@ --[[ -Copyright (c) 2012 Kaarle Ritvanen +Copyright (c) 2012-2013 Kaarle Ritvanen See LICENSE file for license details --]] @@ -14,6 +14,7 @@ User = M.new() User.password = M.String User.real_name = M.String User.roles = M.Collection{type=M.Reference{scope='../../roles'}} +function User:check_password(password) return password == self.password end Authentication = M.new() Authentication.users = M.Collection{type=User} diff --git a/config/aaa.json b/config/aaa.json index 0967ef4..248bd32 100644 --- a/config/aaa.json +++ b/config/aaa.json @@ -1 +1 @@ -{} +{"users":{"admin":{"password":"admin"}}} \ No newline at end of file diff --git a/protocol.txt b/protocol.txt index 7e0f3c2..018006a 100644 --- a/protocol.txt +++ b/protocol.txt @@ -4,6 +4,16 @@ ACF2 HTTP Protocol Load JavaScript client: req: GET / +Log in: +req: POST /login + - body is a JSON object with username and password attributes +resp: authentication token (in header as X-ACF-Auth-Token) + - use X-ACF-Auth-Token in the header of subsequent requests + +Log out: +req: DELETE /login + X-ACF-Auth-Token: + Start transaction: req: POST / resp: txn ID (in header as X-ACF-Transaction-ID) diff --git a/server.lua b/server.lua index 11bfe55..c6fe586 100644 --- a/server.lua +++ b/server.lua @@ -1,5 +1,5 @@ --[[ -Copyright (c) 2012 Kaarle Ritvanen +Copyright (c) 2012-2013 Kaarle Ritvanen See LICENSE file for license details --]] @@ -10,13 +10,7 @@ require 'json' require 'stringy' -local function handle(env, txn, path) - local method = env.REQUEST_METHOD - local data - if env.CONTENT_LENGTH then - data = json.decode(env.input:read(env.CONTENT_LENGTH)) - end - +local function handle(txn, method, path, data) local parent, name if path ~= '/' then parent = txn:search(acf.path.parent(path)) @@ -60,30 +54,80 @@ local function handle(env, txn, path) end +-- TODO shared storage for login sessions +local last_sid = 0 +local sessions = {} + -- TODO implement transactions as threads or store their state in -- shared storage -local last_id = 0 +local last_txn_id = 0 local txns = {} +-- TODO expire stale sessions and transactions + return function(env) local method = env.REQUEST_METHOD local path = env.REQUEST_URI - -- TODO login session management + local data + if env.CONTENT_LENGTH then + data = json.decode(env.input:read(env.CONTENT_LENGTH)) + end + + local sid = tonumber(env.HTTP_X_ACF_AUTH_TOKEN) + local user, txn_id + if sid then + user = sessions[sid] + if not user then return 401 end + txn_id = tonumber(env.HTTP_X_ACF_TRANSACTION_ID) + end - local txn_id = tonumber(env.HTTP_X_ACF_TRANSACTION_ID) local txn if txn_id then txn = txns[txn_id] if not txn then return 404 end else txn = acf.transaction.start() end + local function fetch_user(name) + user = name and txn:search('/auth/users')[name] + end + if user then + fetch_user(user) + if not user then return 401 end + end + + if path == '/login' then + if method == 'POST' then + if not data.username or not data.password then return 401 end + fetch_user(data.username) + if user and user:check_password(data.password) then + last_sid = last_sid + 1 + local sid = last_sid + sessions[sid] = data.username + return 200, {['X-ACF-Auth-Token']=sid} + end + return 401 + end + + if not user then return 401 end + + if method == 'DELETE' then + sessions[sid] = nil + return 200 + end + + return 405 + end + + if not user then return 401 end + if stringy.startswith(path, '/config/') then -- TODO catch and forward relevant errors to the client - local code, hdr, body = handle(env, - txn, - string.sub(path, 8, -1)) + local code, hdr, body = handle(txn, + method, + string.sub(path, 8, -1), + data) if not txn_id and method ~= 'GET' and code == 200 then txn:commit() end @@ -105,8 +149,8 @@ return function(env) if method == 'DELETE' then return 405 end - last_id = last_id + 1 - local txn_id = last_id + last_txn_id = last_txn_id + 1 + local txn_id = last_txn_id txns[txn_id] = txn return 200, {['X-ACF-Transaction-ID']=txn_id} end -- cgit v1.2.3