diff options
Diffstat (limited to 'account.lua')
-rw-r--r-- | account.lua | 139 |
1 files changed, 139 insertions, 0 deletions
diff --git a/account.lua b/account.lua new file mode 100644 index 0000000..c51c3b2 --- /dev/null +++ b/account.lua @@ -0,0 +1,139 @@ +local digest = require 'openssl.digest' +local rand = require 'openssl.rand' +local pbkdf2 = require 'pbkdf2' +local json = require 'json' +local db = require 'db' + +local M = {} +M.__index = M + +local function to_text(bytes) + return (tostring(bytes):gsub(".", function(b) + return ("%02x"):format(b:byte()) + end)) +end + +function M.uid_of(txn, email) + local emails = txn:open("emails", true) + return emails[email] +end + +function M.lookup_username(txn, username) + local usernames = txn:open("usernames", true) + return usernames[username] +end + +local function account(txn, uid) + local udb = txn:open("users", true) + local user = setmetatable({txn = txn, uid = uid}, M) + return user +end + +function M.get_user(txn, uid) + local user = account(txn, uid) + if user:get "exists" then + return user + end +end + +function M.create_user(txn, email, password, username) + if M.uid_of(txn, email) then + return + end + local uid = to_text(rand.bytes(8)) + local user = account(txn, uid) + assert(not user:get "exists") + user:set("exists", "exists") + user:set_email(email) + user:set_password(password) + user:set_username(username) + local tokens = txn:open("tokens", true) + tokens[uid] = uid + return user +end + +function M.user_from_email(txn, email) + local uid = M.uid_of(txn, email) + if uid then return M.get_user(txn, uid) end +end + +function M:set(property, value) + local users = self.txn:open("users", true) + users[self.uid.."."..property] = value + return value +end + +function M:get(property) + local users = self.txn:open("users", true) + return users[self.uid.."."..property] +end + +function M:set_password(password) + local params = {pbkdf2(password)} + self:set("password", json.encode { + hash = params[1], params = {table.unpack(params, 2)}, + }) +end + +function M:check_password(password) + local pass = json.decode(self:get "password") + local hash = pbkdf2(password, table.unpack(pass.params)) + return hash == pass.hash +end + +function M:set_email(email) + local uid = M.uid_of(self.txn, email) + assert(not uid or uid == self.uid) + local emails = self.txn:open("emails", true) + if self:get "email" then + emails[self:get "email"] = nil + end + emails[email] = self.uid + self:set("email", email) +end + +function M:set_username(username) + local uid = M.lookup_username(self.txn, username) + assert(not uid or uid == self.uid) + local usernames = self.txn:open("usernames", true) + if self:get "username" then + usernames[self:get "username"] = nil + end + usernames[username] = self.uid + self:set("username", username) +end + +function M:issue_token(service) + local tokens = self.txn:open("tokens", true) + local token = to_text(rand.bytes(8)) + tokens[self.uid.."."..token] = service + return token +end + +function M:check_token(token) + local tokens = self.txn:open("tokens", true) + if tokens[self.uid.."."..token] then + return tokens[self.uid.."."..token] + end +end + +function M:revoke_token(token) + local tokens = self.txn:open("tokens", true) + tokens[self.uid.."."..token] = nil +end + +function M:revoke_tokens() + local tokens = self.txn:open("tokens", true) + local to_revoke = {} + for token in db.next, tokens, self.uid do + if not token:match("^"..self.uid..".") then + break + end + table.insert(to_revoke, token) + end + for _, v in ipairs(to_revoke) do + tokens[v] = nil + end +end + +return M |