summaryrefslogtreecommitdiffstats
path: root/auth/session.lua
diff options
context:
space:
mode:
Diffstat (limited to 'auth/session.lua')
-rw-r--r--auth/session.lua104
1 files changed, 104 insertions, 0 deletions
diff --git a/auth/session.lua b/auth/session.lua
new file mode 100644
index 0000000..421e9ff
--- /dev/null
+++ b/auth/session.lua
@@ -0,0 +1,104 @@
+--[[
+
+* If user exist in acf db and passwd field is not 'x' then use this password.
+
+* If user exist in acf db and passwd field is 'x' then use password hash in
+ /etc/shadow.
+
+* If user does not exit in acf db, then authenticate against /etc/shadow
+
+]]--
+
+ml = require("ml")
+sha256 = require("sha256")
+shadow = require("auth.shadow")
+acfdb = require("auth.acfpasswd")
+
+base64url = require("base64url")
+
+local privkey_file = "/etc/acf/session-privkey"
+
+-- log function. If global 'logger' is a function, then use that, otherwise
+-- log to stderr.
+local function log(format, ...)
+ io.stderr:write(string.format(tostring(format), ...).."\n")
+end
+
+if type(logger) == "function" then
+ local log = logger
+end
+
+
+
+local session = {}
+session.ttl = 600
+session.privkey = nil --"supersecret"
+-- not an exported function as we dont want reveal our privkey
+local function get_privkey()
+ if session.privkey ~= nil then
+ return session.privkey
+ end
+ session.privkey = ml.readfile(privkey_file)
+ if session.privkey == nil then
+ local f = io.open("/dev/urandom")
+ session.privkey = f:read(128)
+ f:close()
+ posix.umask("rw-------")
+ f = io.open(privkey_file, "w")
+ if f then
+ f:write(session.privkey)
+ f:close()
+ end
+ end
+ return session.privkey
+end
+
+local function token_hash(user, expire)
+ local sha = sha256.new()
+ sha:update(string.format("%s:%s:%s", expire, user, get_privkey()))
+ return sha:digest()
+end
+
+local function generate_token(user, ttl)
+ local expire = os.time() + ttl
+ local tokenstr = string.format("%s:%s:%s", expire, user, token_hash(user, expire))
+ return base64url.encode(tokenstr)
+end
+
+function session.new(user, cleartextpassw)
+ local entry, errmsg = acfdb.getent(user)
+ local authenticate = acfdb.authenticate
+
+ if entry == nil then
+ log("User '%s' not found in %s", user, acfdb.file)
+ if not shadow.getent(user) then
+ log("User '%s' not found in %s. Authentication failed.", user, shadow.file)
+ return nil
+ end
+ authenticate = shadow.authenticate
+ elseif entry.passwd == "x" then
+ -- if passwd field is set to 'x' it means we use password in shadow
+ authenticate = shadow.authenticate
+ end
+
+ if not authenticate(user, cleartextpassw) then
+ log("User '%s' authentication failed.", user)
+ return nil
+ end
+
+ return generate_token(user, session.ttl)
+end
+
+function session.renew(token)
+ local tmp = base64url.decode(token)
+ local expire, user, hash = string.match(tmp, "(%d+):([^:]+):(.*)")
+ if os.time() > tonumber(expire) then
+ return nil, "Token expired"
+ end
+ if hash ~= token_hash(user, expire) then
+ return nil, "Invalid token hash"
+ end
+ return generate_token(user, session.ttl)
+end
+
+return session