summaryrefslogtreecommitdiff
path: root/account.lua
diff options
context:
space:
mode:
Diffstat (limited to 'account.lua')
-rw-r--r--account.lua139
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