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
|