in site/api/preferences.lua [63:412]
function handle(r)
cross.contentType(r, "application/json")
local DEBUG = config.debug or false
local START = DEBUG and r:clock() or nil
local get = r:parseargs()
local login = {
loggedIn = false
}
local prefs = nil
local account = user.get(r)
if get.logout and account then
user.logout(r, account)
r:puts[[{"logout": true}]]
return cross.OK
end
if get.associate and account and get.associate:match("^%S+@%S+$") then
local fp, lp = get.associate:match("([^@]+)@([^@]+)")
if config.no_association then
for k, v in pairs(config.no_association) do
if r.strcmp_match(lp:lower(), v) or v == "*" then
r:puts(JSON.encode{error="You cannot associate email addresses from this domain"})
return cross.OK
end
end
end
if get.associate == account.credentials.email then
r:puts(JSON.encode{error="The primary mail address cannot be added as an alternate"})
return cross.OK
end
account.credentials.altemail = account.credentials.altemail or {}
filtertable(account.credentials.altemail)
local duplicateRequest = false
for k, v in pairs(account.credentials.altemail) do
if v.email == get.associate then
if v.verified then
r:puts(JSON.encode{error="That email is already defined as an alternate"})
return cross.OK
else
v.hash = hash
duplicateRequest = true
end
end
end
local hash = r:md5(math.random(1,999999) .. os.time() .. account.cid)
local scheme = "https"
if r.port == 80 then
scheme = "http"
end
local domain = ("%s://%s:%u/"):format(scheme, r.hostname, r.port)
if r.headers_in['Referer'] and r.headers_in['Referer']:match("merge%.html") then
domain = r.headers_in['Referer']:gsub("/merge%.html", "/")
end
local vURL = ("%sapi/preferences.lua?verify=true&hash=%s"):format(domain, hash)
local mldom = r.headers_in['Referer'] and r.headers_in['Referer']:match("https?://([^/:]+)") or r.hostname
if not mldom then mldom = r.hostname end
local source = smtp.message{
headers = {
subject = "Confirm email address association in Pony Mail",
to = get.associate,
from = ("\"Pony Mail\"<no-reply@%s>"):format(mldom)
},
body = ([[
You (or someone else) has requested to associate the email address '%s' with the account '%s' in Pony Mail.
If you wish to complete this association, please visit
%s
whilst logged in to Pony Mail.
Note: if you have repeated the association request, only the last URL will work.
...Or if you didn't request this, just ignore this email.
With regards,
Pony Mail - Email for Ponies and People.
]]):format(get.associate, account.credentials.email, vURL)
}
-- send email!
local rv, er = smtp.send{
from = ("\"Pony Mail\"<no-reply@%s>"):format(r.hostname),
rcpt = get.associate,
source = source,
server = config.mailserver,
port = config.mailport or nil -- if not specified, use the default
}
-- only update the account if the mail was sent OK
if rv then
if not duplicateRequest then
table.insert(account.credentials.altemail, { email = get.associate, hash = hash, verified = false})
end
user.save(r, account, true)
end
r:puts(JSON.encode{requested = rv or er})
return cross.OK
end
-- verify alt email?
if get.verify and get.hash and account and account.credentials.altemail then
filtertable(account.credentials.altemail)
local verified = false
for k, v in pairs(account.credentials.altemail) do
if v.hash == get.hash then
account.credentials.altemail[k].verified = true
account.credentials.altemail[k].hash = nil
verified = true
-- fix all the matches
end
end
user.save(r, account, true)
-- response goes back to the browser direct
cross.contentType(r, "text/plain")
if verified then
r:puts("Email address verified! Thanks for shopping at Pony Mail!\n")
else
r:puts("Either you supplied an invalid hash or something else went wrong.\n")
end
return cross.OK
end
-- remove alt email?
if get.removealt and account and account.credentials.altemail then
filtertable(account.credentials.altemail)
for k, v in pairs(account.credentials.altemail) do
if v.email == get.removealt then
table.remove(account.credentials.altemail, k)
break
end
end
user.save(r, account, true)
r:puts(JSON.encode{removed = true})
return cross.OK
end
-- Or are you saving your preferences?
if get.save and account then
prefs = {}
for k, v in pairs(get) do
if k ~= 'save' then
prefs[k] = v
end
end
account.preferences = prefs
user.save(r, account)
r:puts[[{"saved": true}]]
return cross.OK
end
-- Adding a favorite list
if get.addfav and account then
local add = get.addfav
local favs = account.favorites or {}
local found = false
-- ensure it's not already there....
for k, v in pairs(favs) do
if v == add then
found = true
break
end
end
if not found then
table.insert(favs, add)
end
account.favorites = favs
user.favs(r, account)
r:puts[[{"saved": true}]]
return cross.OK
end
if get.remfav and account then
local rem = get.remfav
local favs = account.favorites or {}
for k, v in pairs(favs) do
if v == rem then
table.remove(favs, k)
break
end
end
account.favorites = favs
user.favs(r, account)
r:puts[[{"saved": true}]]
return cross.OK
end
for _, v in pairs({'associate', 'verify', 'removealt', 'save', 'addfav', 'remfav'}) do
if get[v] then
if not account then
r:puts(JSON.encode{error="Not logged in"})
else
r:puts(JSON.encode{error="Missing or invalid parameter(s)"})
end
return cross.OK
end
end
local NOWISH = math.floor(os.time() / 600)
local PM_LISTS_KEY = "pm_lists_counts_" .. r.hostname .. "-" .. NOWISH
local cache = r:ivm_get(PM_LISTS_KEY)
local listcounts = {}
if cache then
listcounts = JSON.decode(cache)
else
local alldocs = elastic.raw{
size = 0,
aggs = {
listnames = {
terms = {
field = "list_raw",
size = utils.MAX_LIST_COUNT
},
aggs = {
privacy = {
terms = {
field = "private"
},
aggs = {
recent = {
range = {
field = "date",
ranges = { {from = "now-90d"} }
}
}
}
}
}
}
}
}
for _, entry in pairs (alldocs.aggregations.listnames.buckets) do
local listname = entry.key:lower()
listcounts[listname] = {}
for _, privacy in pairs(entry.privacy.buckets) do
listcounts[listname][privacy.key_as_string] = privacy.recent.buckets[1].doc_count
end
end
r:ivm_set(PM_LISTS_KEY, JSON.encode(listcounts))
end
local lists = {}
for listname, entry in pairs(listcounts) do
local _, list, domain = aaa.parseLid(listname)
if list and domain and #list > 0 and #domain > 0 then
for privacy, recent_count in pairs(entry) do
local isPublic = privacy == 'false'
if isPublic or aaa.canAccessList(r, listname, account) then
lists[domain] = lists[domain] or {}
if lists[domain][list] then
lists[domain][list] = lists[domain][list] + recent_count
else
lists[domain][list] = recent_count
end
end
end
end
end
if config.listsDisplay then
for k, v in pairs(lists) do
if not k:match(config.listsDisplay) then
lists[k] = nil
end
end
end
local notifications = 0
if account then
local _, notifs = pcall(function() return elastic.find("seen:0 AND recipient:" .. r:sha1(account.cid), 10, "notifications") end)
if notifs and #notifs > 0 then
notifications = #notifs
end
end
account = account or {}
local stat, descs = pcall(function() return elastic.find("*", 9999, "mailinglists", "name") end)
if not stat or not descs then
descs = {}
end
for k, v in pairs(descs) do
local _, l, d = aaa.parseLid(v.list:lower())
if l and d then
descs[k].lid = ("%s@%s"):format(l, d)
else
descs[k].lid = v.list
end
end
local alts = {}
if account and account.credentials and type(account.credentials.altemail) == "table" then
filtertable(account.credentials.altemail)
for k, v in pairs(account.credentials.altemail) do
if v.verified then
table.insert(alts, v.email)
end
end
end
r:puts(JSON.encode{
lists = lists,
descriptions = descs,
preferences = account.preferences,
login = {
favorites = account.favorites,
credentials = account.credentials,
notifications = notifications,
alternates = alts
},
took = DEBUG and (r:clock() - START) or nil
})
return cross.OK
end