summaryrefslogtreecommitdiff
path: root/account.lua
blob: c51c3b2e07ebec80152c5c03b7ebd7211537bea8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
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