added login, logout, signup page. refined abstractions. added preliminary buttons. added preliminary error screen (will finish tomorrow.) added hashed passwords. added authentication. added users. added death and whatnot aaaaaaghhh kill meeee
This commit is contained in:
parent
34fee31395
commit
9e29afb525
35 changed files with 741 additions and 58 deletions
149
authors.lua
Normal file
149
authors.lua
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
local authors = {}
|
||||||
|
local foppy = require "./foppy"
|
||||||
|
local fs = require "fs"
|
||||||
|
local json = require "json"
|
||||||
|
local utils = require "./utils"
|
||||||
|
|
||||||
|
authors.from_id = function(id, writeable)
|
||||||
|
return foppy.open("database/authors/" .. id .. ".json", writeable)
|
||||||
|
end
|
||||||
|
|
||||||
|
authors.id_from_token = function(token, writeable)
|
||||||
|
local data = foppy.open("database/tokens/" .. token .. ".json", writeable)
|
||||||
|
if not data then
|
||||||
|
return nil, 404
|
||||||
|
end
|
||||||
|
|
||||||
|
if data.expires <= os.time() then
|
||||||
|
data.dead = true
|
||||||
|
return nil, 401
|
||||||
|
end
|
||||||
|
|
||||||
|
if data.dead then
|
||||||
|
return nil, 401
|
||||||
|
end
|
||||||
|
|
||||||
|
local exists = fs.existsSync("database/authors/" .. data.of .. ".json")
|
||||||
|
if not exists then
|
||||||
|
data.dead = true
|
||||||
|
return nil, 404
|
||||||
|
end
|
||||||
|
|
||||||
|
return data.of
|
||||||
|
end
|
||||||
|
|
||||||
|
authors.id_from_blog = function(blog, writeable)
|
||||||
|
local data = foppy.open("database/blogs/" .. blog .. ".json")
|
||||||
|
if not data then
|
||||||
|
return nil, 404
|
||||||
|
end
|
||||||
|
|
||||||
|
return data.owner
|
||||||
|
end
|
||||||
|
|
||||||
|
local is_right_password = function(id, password)
|
||||||
|
local author, code = authors.from_id(id, true)
|
||||||
|
if not author then
|
||||||
|
return nil, 404
|
||||||
|
end
|
||||||
|
|
||||||
|
-- check salted password with openssl
|
||||||
|
local hashed = utils.hash(author.salt .. password)
|
||||||
|
if author.password ~= hashed then
|
||||||
|
return nil, 401
|
||||||
|
end
|
||||||
|
|
||||||
|
return author
|
||||||
|
end
|
||||||
|
|
||||||
|
authors.auth = function(id, password)
|
||||||
|
local author, code = is_right_password(id, password)
|
||||||
|
if not author then
|
||||||
|
return nil, code
|
||||||
|
end
|
||||||
|
|
||||||
|
local hash, filename
|
||||||
|
repeat
|
||||||
|
hash = utils.uuid()
|
||||||
|
filename = "database/tokens/" .. hash .. ".json"
|
||||||
|
until not fs.existsSync(hash)
|
||||||
|
|
||||||
|
local token_data = json.encode {
|
||||||
|
of = id,
|
||||||
|
expires = os.time() + 2628000, -- 4 months, approximately.
|
||||||
|
dead = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(filename, token_data)
|
||||||
|
|
||||||
|
|
||||||
|
author.tokens[#author.tokens + 1] = hash
|
||||||
|
|
||||||
|
foppy.sync("database/authors/" .. id .. ".json")
|
||||||
|
|
||||||
|
return hash
|
||||||
|
end
|
||||||
|
|
||||||
|
authors.revoke_all_tokens = function(id, password)
|
||||||
|
local author, code = is_right_password(id, password)
|
||||||
|
|
||||||
|
if not author then
|
||||||
|
return nil, code
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, v in ipairs(author.tokens) do
|
||||||
|
local f = "database/tokens/" .. v .. ".json"
|
||||||
|
local token = foppy.open(f, true)
|
||||||
|
token.dead = true
|
||||||
|
foppy.sync(f, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
author.tokens = {}
|
||||||
|
|
||||||
|
foppy.sync("database/authors/" .. id .. ".json")
|
||||||
|
end
|
||||||
|
|
||||||
|
authors.change_password = function(id, password, new_password)
|
||||||
|
local author, code = is_right_password(id, password)
|
||||||
|
|
||||||
|
if not author then
|
||||||
|
return nil, code
|
||||||
|
end
|
||||||
|
|
||||||
|
author.salt = utils.uuid()
|
||||||
|
author.password = utils.data(author.salt .. new_password)
|
||||||
|
|
||||||
|
foppy.sync("database/authors/" .. id .. ".json")
|
||||||
|
end
|
||||||
|
|
||||||
|
authors.get_available_id = function()
|
||||||
|
local id
|
||||||
|
repeat
|
||||||
|
id = utils.pin()
|
||||||
|
until not fs.existsSync("database/authors/" .. id .. ".json")
|
||||||
|
|
||||||
|
return id
|
||||||
|
end
|
||||||
|
|
||||||
|
authors.create_account = function(password)
|
||||||
|
local id = authors.get_available_id()
|
||||||
|
local salt = utils.uuid()
|
||||||
|
|
||||||
|
local account_data = json.encode {
|
||||||
|
blogs = {},
|
||||||
|
password = utils.hash(salt .. password),
|
||||||
|
salt = salt,
|
||||||
|
tokens = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
"database/authors/" .. id .. ".json",
|
||||||
|
account_data
|
||||||
|
)
|
||||||
|
|
||||||
|
return id
|
||||||
|
end
|
||||||
|
|
||||||
|
package.loaded["./authors"] = authors
|
||||||
|
|
||||||
|
return authors
|
|
@ -1,5 +1,5 @@
|
||||||
local config = {
|
local config = {
|
||||||
base_url = "example.com"
|
base_url = "test0.at.cyrneko.eu"
|
||||||
}
|
}
|
||||||
|
|
||||||
package.loaded["./config"] = config
|
package.loaded["./config"] = config
|
||||||
|
|
1
data.lua
Normal file
1
data.lua
Normal file
|
@ -0,0 +1 @@
|
||||||
|
-- retrieves a buncha useful data!
|
1
database/authors/0091.json
Normal file
1
database/authors/0091.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"blogs":[],"tokens":[],"password":"343feb9dcdab90413da9275282ae0481b084264430ba07a1b254a7b4d7298f11","salt":"3dbd8155-477c-419a-b50e-f74c772e59f5"}
|
1
database/authors/3437.json
Normal file
1
database/authors/3437.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"blogs":[],"tokens":[],"password":"01555d563d1976de96843a3d758572a858b82b892f03d45ca225e9fd5d8d81f6","salt":"51a6539a-bee9-4076-bc83-89e171a86844"}
|
1
database/authors/3919.json
Normal file
1
database/authors/3919.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"tokens":[],"password":"ff9a2d7497a68b179179f542cbb75aafb9aabcffe3d5335e94a3185144a341a2","salt":"599abac8-4c36-4bd1-8d29-2034dce92fbb","blogs":[]}
|
1
database/authors/5663.json
Normal file
1
database/authors/5663.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"tokens":[],"password":"4141faabc6212f8f1894efadbf5eb0ad2c3c76834822a8e380f46d9ad406165b","salt":"e03099c3-0715-4157-827b-6da5e35d4c53","blogs":[]}
|
1
database/authors/5698.json
Normal file
1
database/authors/5698.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"password":"713fc8760e23ccaef815965b2e0be0946b24e2d0419eacef308ecfa7e2ed6268","tokens":[],"blogs":[],"salt":"aaa27eb1-e49c-4aa3-b2b7-a3b557cfc58d"}
|
1
database/authors/6301.json
Normal file
1
database/authors/6301.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"blogs":[],"tokens":[],"password":"43af369fbf521df0634c37502bdfc6d6e7e20f0611a8c34207b95ea7b3fc1921","salt":"fc186676-5370-497b-a384-15781823bc3a"}
|
1
database/authors/8870.json
Normal file
1
database/authors/8870.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"blogs":[],"tokens":[],"password":"50ad6a00e7c93eff9eaf6b05501ca99ce1b7d15988b93c6a545cf49beb22ccf1","salt":"bfb52167-d0f7-404b-88f5-a2480a11d1d5"}
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "n-asterisk",
|
"name": "n-asterisk",
|
||||||
"biography": "hi, i am n*, we're a system of two, we love eachother and we love to code, hell yeah mf.",
|
"description": "hi, i am n*, we're a system of two, we love eachother and we love to code, hell yeah mf.",
|
||||||
|
"owner": "1234",
|
||||||
|
|
||||||
"index": [
|
"index": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
{
|
{
|
||||||
|
"maker": "3819",
|
||||||
|
"real_name": "why-napkin",
|
||||||
|
"title": "why napkin?",
|
||||||
|
|
||||||
"versions": [
|
"versions": [
|
||||||
{
|
{
|
||||||
"contents": "<h1>Why napkin?</h1>napkin is cool, that's it, really.",
|
"contents": "<h1>Why napkin?</h1>napkin is cool, that's it, really.",
|
||||||
|
|
1
database/redirects.json
Normal file
1
database/redirects.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[{ "from": "abcd", "to": "/@n-asterisk/why-napkin" }]
|
|
@ -0,0 +1 @@
|
||||||
|
{"of":"6301","expires":1743423851,"dead":false}
|
|
@ -0,0 +1 @@
|
||||||
|
{"expires":1743422868,"dead":false,"of":"5698"}
|
|
@ -0,0 +1 @@
|
||||||
|
{"of":"0091","expires":1743429866,"dead":false}
|
|
@ -0,0 +1 @@
|
||||||
|
{"expires":1743423490,"dead":false,"of":"5663"}
|
|
@ -0,0 +1 @@
|
||||||
|
{"of":"8870","expires":1743423679,"dead":false}
|
|
@ -0,0 +1 @@
|
||||||
|
{"expires":1743423622,"dead":false,"of":"3919"}
|
|
@ -0,0 +1 @@
|
||||||
|
{"of":"3437","expires":1743425720,"dead":false}
|
0
errors.lua
Normal file
0
errors.lua
Normal file
53
foppy.lua
53
foppy.lua
|
@ -2,7 +2,8 @@ local json = require "json"
|
||||||
local fs = require "fs"
|
local fs = require "fs"
|
||||||
|
|
||||||
local tick_precision = 2 -- tick each N seconds (decimal)
|
local tick_precision = 2 -- tick each N seconds (decimal)
|
||||||
local timeout = 30 -- 30 seconds
|
local timeout = 60 * 10 -- 10 minutes
|
||||||
|
local write_timeout = 30
|
||||||
|
|
||||||
local foppy = {}
|
local foppy = {}
|
||||||
foppy.cache = {}
|
foppy.cache = {}
|
||||||
|
@ -11,9 +12,7 @@ foppy.open = function(file, writeable)
|
||||||
local c = foppy.cache[file]
|
local c = foppy.cache[file]
|
||||||
if c then
|
if c then
|
||||||
c.timeout = timeout
|
c.timeout = timeout
|
||||||
if writeable then
|
c.write_timeout = writeable and write_timeout
|
||||||
c.written_to = true
|
|
||||||
end
|
|
||||||
return c.data
|
return c.data
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -25,9 +24,8 @@ foppy.open = function(file, writeable)
|
||||||
local data = json.decode(raw)
|
local data = json.decode(raw)
|
||||||
c = {
|
c = {
|
||||||
timeout = timeout,
|
timeout = timeout,
|
||||||
data = data,
|
write_timeout = writeable and write_timeout,
|
||||||
file = file,
|
data = data
|
||||||
written_to = writeable
|
|
||||||
}
|
}
|
||||||
foppy.cache[file] = c
|
foppy.cache[file] = c
|
||||||
|
|
||||||
|
@ -40,15 +38,50 @@ foppy.setup = function()
|
||||||
timer.setInterval(tick_precision * 1000, function()
|
timer.setInterval(tick_precision * 1000, function()
|
||||||
for name, object in pairs(foppy.cache) do
|
for name, object in pairs(foppy.cache) do
|
||||||
object.timeout = object.timeout - tick_precision
|
object.timeout = object.timeout - tick_precision
|
||||||
|
object.write_timeout = object.write_timeout - tick_precision
|
||||||
|
|
||||||
if object.timeout <= 0 then
|
if object.timeout <= 0 then
|
||||||
foppy.cache[name] = nil
|
foppy.cache[name] = nil
|
||||||
|
object.write_timeout = 0
|
||||||
if foppy.written_to then
|
|
||||||
fs.writeFileSync(object.file, json.encode(object.data))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if object.write_timeout <= 0 then
|
||||||
|
fs.writeFileSync(name, json.encode(object.data))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
-- TODO: Finish this.
|
||||||
|
--[[
|
||||||
|
local process = require("process").globalProcess()
|
||||||
|
|
||||||
|
local hecc_everything = function()
|
||||||
|
print("foppy says hecc!!!")
|
||||||
|
fs.writeFileSync("hecc", "hecc")
|
||||||
|
for name, object in pairs(foppy.cache) do
|
||||||
|
if object.write_timeout then
|
||||||
|
fs.writeFileSync(name, json.encode(object.data))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
process:on("sigint", hecc_everything)
|
||||||
|
process:on("sigterm", hecc_everything)
|
||||||
|
process:on("sighup", hecc_everything)
|
||||||
|
process:on("sigquit", hecc_everything)
|
||||||
|
]]
|
||||||
|
end
|
||||||
|
|
||||||
|
foppy.sync = function(what, close)
|
||||||
|
local object = foppy.cache[what]
|
||||||
|
if object then
|
||||||
|
if object.written_to then
|
||||||
|
fs.writeFileSync(what, json.encode(object.data))
|
||||||
|
end
|
||||||
|
if close then
|
||||||
|
foppy.cache[what] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
package.loaded["./foppy"] = foppy
|
package.loaded["./foppy"] = foppy
|
||||||
|
|
51
main.lua
51
main.lua
|
@ -3,55 +3,70 @@ local json = require("json")
|
||||||
local radon = require("./radon")
|
local radon = require("./radon")
|
||||||
local foppy = require("./foppy")
|
local foppy = require("./foppy")
|
||||||
local config = require("./config")
|
local config = require("./config")
|
||||||
|
local authors = require("./authors")
|
||||||
|
local utils = require("./utils")
|
||||||
|
|
||||||
local templater = function(template)
|
local templater = function(template)
|
||||||
return function(req, res)
|
return function(req, res)
|
||||||
local func = dofile("template/" .. template .. ".lua")
|
local func = dofile("template/" .. template .. ".lua")
|
||||||
p(req)
|
|
||||||
|
|
||||||
res.code = 200
|
res.code = 200
|
||||||
res.body = radon.compile(func, {
|
res.body = radon.compile(func, {
|
||||||
request = req,
|
request = req,
|
||||||
response = res,
|
response = res
|
||||||
base_url = "example.com/"
|
|
||||||
})
|
})
|
||||||
res.headers["Content-Type"] = "text/html"
|
res.headers["Content-Type"] = "text/html"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local api_wrapper = function()
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
weblit.app
|
weblit.app
|
||||||
.bind({ host = "0.0.0.0", port = 1337 })
|
.bind({ host = "0.0.0.0", port = 1337 })
|
||||||
|
|
||||||
-- Configure weblit server
|
-- Configure weblit server
|
||||||
.use(weblit.logger)
|
.use(weblit.logger)
|
||||||
.use(weblit.autoHeaders)
|
.use(weblit.autoHeaders)
|
||||||
|
.use(utils.cookies)
|
||||||
|
|
||||||
.route(
|
.route(
|
||||||
{ path = "/static/:path:" },
|
{ path = "/static/:path:" },
|
||||||
weblit.static("static")
|
weblit.static("static")
|
||||||
)
|
)
|
||||||
|
|
||||||
.route({ path = "/api/signup", method = "POST" }, function(req, res)
|
.route(
|
||||||
local body = json.decode(req.body)
|
{ path = "/media/:path:" },
|
||||||
|
weblit.static("database/media")
|
||||||
|
)
|
||||||
|
|
||||||
if body == nil or type(body) ~= "table" then
|
-- TODO: CLONE ALL OF THIS AS A REST API EVENTUALLY,
|
||||||
res.code = 400
|
.route({ path = "/signup" }, templater("signup"))
|
||||||
res.body = "Bad Request: Invalid request body"
|
.route({ path = "/signup2" }, templater("signup2"))
|
||||||
|
.route({ path = "/login" }, templater("login"))
|
||||||
|
.route({ path = "/logout" }, templater("logout"))
|
||||||
|
|
||||||
|
.route({ path = "/@:blog" }, templater("blog"))
|
||||||
|
.route({ path = "/@:blog/:post" }, templater("post"))
|
||||||
|
.route({ path = "/p/:path" }, function(req, res)
|
||||||
|
local path = req.params.path
|
||||||
|
if #path == 0 then
|
||||||
|
res.code = 301
|
||||||
|
res.headers["Location"] = "/"
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
res.code = 200
|
local func = dofile("template/error.lua")
|
||||||
res.body = "Signup successful"
|
local redirects = foppy.open("database/redirects.json")
|
||||||
|
assert(redirects)
|
||||||
|
|
||||||
|
local redirect = redirects[path]
|
||||||
|
if redirect then
|
||||||
|
res.code = 301
|
||||||
|
res.headers["Location"] = redirect
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
return func(404, "post", "/p/" .. path)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
.route({ path = "/p/:post" }, function() end)
|
|
||||||
.route({ path = "/@:blog" }, templater("blog"))
|
|
||||||
.route({ path = "/@:blog/:post" }, templater("post"))
|
|
||||||
.route({ path = "/" }, templater("index"))
|
.route({ path = "/" }, templater("index"))
|
||||||
|
|
||||||
-- Start the server
|
-- Start the server
|
||||||
|
|
|
@ -14,7 +14,10 @@ local tag = function(name)
|
||||||
if t == "table" then
|
if t == "table" then
|
||||||
for key, value in pairs(content) do
|
for key, value in pairs(content) do
|
||||||
if type(key) == "string" then
|
if type(key) == "string" then
|
||||||
data = data .. ' ' .. key .. '="' .. value .. '"'
|
data = data .. ' ' .. key
|
||||||
|
if value ~= true then
|
||||||
|
data = data .. '="' .. tostring(value) .. '"'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -32,27 +32,39 @@ p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
button {
|
||||||
|
padding: 5px 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
button,
|
button,
|
||||||
.button {
|
.button {
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
padding: 10px;
|
|
||||||
background-color: white;
|
background-color: white;
|
||||||
display: flex;
|
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
button:hover,
|
button:hover,
|
||||||
.button:hover {
|
.button:hover {
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.action-button {
|
||||||
|
color: black;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
/* post */
|
/* post */
|
||||||
.post {
|
.post {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-list {
|
.post-list {
|
||||||
|
@ -73,7 +85,7 @@ button:hover,
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.biography {
|
.description {
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
text-align: justify;
|
text-align: justify;
|
||||||
|
@ -89,11 +101,30 @@ section > h1 {
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tags-list {
|
.tags-list,
|
||||||
|
.gapped-row {
|
||||||
gap: 15px;
|
gap: 15px;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gapped-column {
|
||||||
|
gap: 15px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-card {
|
||||||
|
background-color: rgba(208, 134, 39, 0.21);
|
||||||
|
padding: 10px 15px;
|
||||||
|
border: 1px solid rgba(208, 134, 39, 0.21);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-card {
|
||||||
|
background-color: rgba(255, 204, 204, 0.6);
|
||||||
|
padding: 10px 15px;
|
||||||
|
border: 1px solid rgba(255, 204, 204, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
.tag {
|
.tag {
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
|
@ -108,9 +139,15 @@ section > h1 {
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-navigator {
|
.top-navigator {
|
||||||
gap: 5px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
height: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-navigator-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
|
@ -126,3 +163,13 @@ br {
|
||||||
.centered {
|
.centered {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.justified {
|
||||||
|
text-align: justify;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ph {
|
||||||
|
vertical-align: middle;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
|
@ -52,11 +52,11 @@ return function(data)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- return our whole flippin html document
|
-- return our whole flippin html document
|
||||||
return wrapper({ title = handle, url = handle }) {
|
return wrapper(data) {
|
||||||
-- render header!
|
-- render header!
|
||||||
section {
|
section {
|
||||||
h1(blog.name),
|
h1(blog.name),
|
||||||
p { class = "biography", blog.biography }
|
p { class = "description", blog.description }
|
||||||
},
|
},
|
||||||
|
|
||||||
-- render the pinned post list
|
-- render the pinned post list
|
||||||
|
|
|
@ -2,11 +2,12 @@
|
||||||
|
|
||||||
local wrapper = require "template.wrapper"
|
local wrapper = require "template.wrapper"
|
||||||
|
|
||||||
return function()
|
return function(data)
|
||||||
return wrapper({ title = "index!" }) {
|
return wrapper(data) {
|
||||||
section {
|
|
||||||
h1 "welcome to napkin!",
|
h1 "welcome to napkin!",
|
||||||
p "This is the index page."
|
|
||||||
|
section {
|
||||||
|
"the unfunkiest place on the webs"
|
||||||
},
|
},
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
|
61
template/login.lua
Normal file
61
template/login.lua
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
---@diagnostic disable:undefined-global
|
||||||
|
|
||||||
|
local wrapper = require "template.wrapper"
|
||||||
|
local phosphor = require "template.phosphor"
|
||||||
|
|
||||||
|
return function(data)
|
||||||
|
return wrapper(data) {
|
||||||
|
h1 { phosphor("user"), " log into your account!" },
|
||||||
|
|
||||||
|
section {
|
||||||
|
class = "justified",
|
||||||
|
|
||||||
|
p [[
|
||||||
|
heya! welcome again!
|
||||||
|
]],
|
||||||
|
p [[
|
||||||
|
you can log-in either using the number we provided you
|
||||||
|
or the name of one of your blogs
|
||||||
|
]],
|
||||||
|
p [[
|
||||||
|
remember! if you forget both your number and blog names,
|
||||||
|
you're toast! so please write them down or something.
|
||||||
|
]]
|
||||||
|
},
|
||||||
|
|
||||||
|
section {
|
||||||
|
form {
|
||||||
|
action = "/login2",
|
||||||
|
method = "POST",
|
||||||
|
class = "gapped-row",
|
||||||
|
|
||||||
|
input {
|
||||||
|
name = "user",
|
||||||
|
id = "user",
|
||||||
|
minlength = 8,
|
||||||
|
maxlength = 16,
|
||||||
|
pattern = "(^@[a-zA-Z0-9_.]+$)|(^\\d{4}$)",
|
||||||
|
required = true,
|
||||||
|
placeholder = "identifier/blog goes here",
|
||||||
|
},
|
||||||
|
|
||||||
|
input {
|
||||||
|
type = "password",
|
||||||
|
name = "password",
|
||||||
|
id = "password",
|
||||||
|
minlength = 8,
|
||||||
|
maxlength = 16,
|
||||||
|
pattern = "(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@$!%*?&]).{8,}",
|
||||||
|
required = true,
|
||||||
|
placeholder = "password goes here",
|
||||||
|
},
|
||||||
|
button "log in!",
|
||||||
|
},
|
||||||
|
|
||||||
|
a {
|
||||||
|
href = "/signup",
|
||||||
|
"don't have an account?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
end
|
56
template/login2.lua
Normal file
56
template/login2.lua
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
---@diagnostic disable:undefined-global
|
||||||
|
|
||||||
|
local wrapper = require "template.wrapper"
|
||||||
|
local utils = require "./utils"
|
||||||
|
local authors = require "./authors"
|
||||||
|
local phosphor = require "template.phosphor"
|
||||||
|
|
||||||
|
|
||||||
|
return function(data)
|
||||||
|
if data.request.cookies.auth_token then
|
||||||
|
data.response.code = 301
|
||||||
|
data.response.headers["Location"] = "/"
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
|
||||||
|
local form = utils.parse_form(data.request.body or "")
|
||||||
|
|
||||||
|
if not (form.password and form.confirm_password) or
|
||||||
|
form.password ~= form.confirm_password or
|
||||||
|
not utils.is_valid_password(form.password) then
|
||||||
|
data.response.code = 301
|
||||||
|
data.response.headers["Location"] = "/signup"
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
|
||||||
|
local id = authors.create_account(form.password)
|
||||||
|
local token = authors.auth(id, form.password)
|
||||||
|
|
||||||
|
-- TODO: Add Secure;!
|
||||||
|
data.response.headers["Set-Cookie"] = "auth_token=" .. token
|
||||||
|
.. "; Path=/; HttpOnly; SameSite=Strict"
|
||||||
|
|
||||||
|
data.request.cookies.auth_token = token
|
||||||
|
|
||||||
|
return wrapper(data) {
|
||||||
|
h1 { phosphor("identification-card"), " your identifier is ", id, "!" },
|
||||||
|
|
||||||
|
section {
|
||||||
|
p [[
|
||||||
|
that's your identifier! please do not lose it! it's important!
|
||||||
|
it's what allows us to tell you apart from everyone else.
|
||||||
|
]],
|
||||||
|
p [[
|
||||||
|
thanks for joining us! we hope you enjoy your stay, you rock!
|
||||||
|
]],
|
||||||
|
},
|
||||||
|
|
||||||
|
p {
|
||||||
|
"you can begin by ",
|
||||||
|
a {
|
||||||
|
href = "/new-blog",
|
||||||
|
"making a blog"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
6
template/logout.lua
Normal file
6
template/logout.lua
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
return function(data)
|
||||||
|
data.response.headers["Set-Cookie"] =
|
||||||
|
"auth_token=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=/; HttpOnly"
|
||||||
|
data.response.headers["Location"] = "/"
|
||||||
|
data.response.code = 302
|
||||||
|
end
|
|
@ -9,6 +9,7 @@ local foppy = require "./foppy"
|
||||||
return function(data)
|
return function(data)
|
||||||
-- if the blog name is empty, then redirect to the homepage
|
-- if the blog name is empty, then redirect to the homepage
|
||||||
-- todo: do this on the actual router (main.lua)
|
-- todo: do this on the actual router (main.lua)
|
||||||
|
local post_name = data.request.params.post
|
||||||
local blog_name = data.request.params.blog
|
local blog_name = data.request.params.blog
|
||||||
if #blog_name == 0 then
|
if #blog_name == 0 then
|
||||||
data.response.code = 301
|
data.response.code = 301
|
||||||
|
@ -17,24 +18,24 @@ return function(data)
|
||||||
end
|
end
|
||||||
|
|
||||||
local blog_path = "database/blogs/" .. blog_name .. ".json"
|
local blog_path = "database/blogs/" .. blog_name .. ".json"
|
||||||
|
local handle = "@" .. blog_name
|
||||||
|
local url = handle .. "/" .. post_name
|
||||||
|
|
||||||
-- attempt to read the blog
|
-- attempt to read the blog
|
||||||
local blog = foppy.open(blog_path)
|
local blog = foppy.open(blog_path)
|
||||||
-- could find no darn blog!!!
|
-- could find no darn blog!!!
|
||||||
if not blog then
|
if not blog then
|
||||||
return error(404, "blog", handle) -- so, fuck it, error screen.
|
return error(404, "blog", url) -- so, fuck it, error screen.
|
||||||
end
|
end
|
||||||
|
|
||||||
-- if the post name is empty, then redirect to the blog page
|
-- if the post name is empty, then redirect to the blog page
|
||||||
-- todo: do this on the actual router (main.lua)
|
-- todo: do this on the actual router (main.lua)
|
||||||
local post_name = data.request.params.post
|
|
||||||
if #post_name == 0 then
|
if #post_name == 0 then
|
||||||
data.response.code = 302
|
data.response.code = 302
|
||||||
data.response.headers["Location"] = "/@" .. blog_name
|
data.response.headers["Location"] = "/@" .. blog_name
|
||||||
return ""
|
return ""
|
||||||
end
|
end
|
||||||
|
|
||||||
local url = "@" .. blog_name .. "/" .. post_name
|
|
||||||
local post_path = "database/blogs/" .. blog_name .. "/" .. post_name .. ".json"
|
local post_path = "database/blogs/" .. blog_name .. "/" .. post_name .. ".json"
|
||||||
local post = foppy.open(post_path)
|
local post = foppy.open(post_path)
|
||||||
|
|
||||||
|
@ -51,12 +52,13 @@ return function(data)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- return our whole flippin html document
|
-- return our whole flippin html document
|
||||||
return wrapper({ title = handle, url = url }) {
|
return wrapper(data) {
|
||||||
section {
|
section {
|
||||||
div {
|
div {
|
||||||
class = "post-content",
|
class = "post-content",
|
||||||
latest_version.contents,
|
latest_version.contents,
|
||||||
},
|
},
|
||||||
|
|
||||||
div(function(o)
|
div(function(o)
|
||||||
o.class = "tags-list"
|
o.class = "tags-list"
|
||||||
table.insert(o, phosphor("tag"))
|
table.insert(o, phosphor("tag"))
|
||||||
|
@ -68,6 +70,12 @@ return function(data)
|
||||||
"#" .. tag
|
"#" .. tag
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
table.insert(o, a {
|
||||||
|
style = "text-align: right;",
|
||||||
|
href = "/" .. handle,
|
||||||
|
handle
|
||||||
|
})
|
||||||
end)
|
end)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
88
template/signup.lua
Normal file
88
template/signup.lua
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
---@diagnostic disable:undefined-global
|
||||||
|
|
||||||
|
local wrapper = require "template.wrapper"
|
||||||
|
local phosphor = require "template.phosphor"
|
||||||
|
|
||||||
|
return function(data)
|
||||||
|
if data.request.cookies.auth_token then
|
||||||
|
data.response.code = 301
|
||||||
|
data.response.headers["Location"] = "/"
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
|
||||||
|
return wrapper(data) {
|
||||||
|
h1 { phosphor("user-plus"), " get an account!" },
|
||||||
|
|
||||||
|
section {
|
||||||
|
class = "justified",
|
||||||
|
|
||||||
|
p [[
|
||||||
|
to create your own blogs and share your thoughts with the world you
|
||||||
|
will need to create an account right here!
|
||||||
|
]],
|
||||||
|
p [[
|
||||||
|
the way that napkin accounts work is a bit different, you will need
|
||||||
|
to provide a password and then we will give you a pin code, which
|
||||||
|
will serve as your credentials, don't forget it!
|
||||||
|
]],
|
||||||
|
p {
|
||||||
|
class = "info-card",
|
||||||
|
[[
|
||||||
|
password must contain at least one uppercase letter, one lowercase
|
||||||
|
letter, one number, and one special character (@$!%*?&)
|
||||||
|
]]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
section {
|
||||||
|
form {
|
||||||
|
action = "/signup2",
|
||||||
|
method = "POST",
|
||||||
|
class = "gapped-column",
|
||||||
|
|
||||||
|
--[[
|
||||||
|
textarea {
|
||||||
|
name = "message",
|
||||||
|
rows = 5, -- Adjust for height
|
||||||
|
cols = 40, -- Adjust for width
|
||||||
|
required = true,
|
||||||
|
placeholder = "Type your message here...",
|
||||||
|
wrap = "soft", -- Options: "soft" (no forced wrapping) or "hard" (includes newlines in submission)
|
||||||
|
},
|
||||||
|
]]
|
||||||
|
|
||||||
|
div {
|
||||||
|
class = "gapped-row",
|
||||||
|
|
||||||
|
input {
|
||||||
|
type = "password",
|
||||||
|
name = "password",
|
||||||
|
id = "password",
|
||||||
|
minlength = 8,
|
||||||
|
maxlength = 16,
|
||||||
|
pattern = "(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@$!%*?&]).{8,}",
|
||||||
|
required = true,
|
||||||
|
placeholder = "password goes here",
|
||||||
|
},
|
||||||
|
input {
|
||||||
|
type = "password",
|
||||||
|
name = "confirm_password",
|
||||||
|
minlength = 8,
|
||||||
|
maxlength = 16,
|
||||||
|
required = true,
|
||||||
|
placeholder = "confirm password",
|
||||||
|
pattern = "(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@$!%*?&]).{8,}",
|
||||||
|
title = "Passwords must match",
|
||||||
|
},
|
||||||
|
button "create account!",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
a {
|
||||||
|
href = "/login",
|
||||||
|
"already have an account?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
end
|
56
template/signup2.lua
Normal file
56
template/signup2.lua
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
---@diagnostic disable:undefined-global
|
||||||
|
|
||||||
|
local wrapper = require "template.wrapper"
|
||||||
|
local utils = require "./utils"
|
||||||
|
local authors = require "./authors"
|
||||||
|
local phosphor = require "template.phosphor"
|
||||||
|
|
||||||
|
|
||||||
|
return function(data)
|
||||||
|
if data.request.cookies.auth_token then
|
||||||
|
data.response.code = 301
|
||||||
|
data.response.headers["Location"] = "/"
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
|
||||||
|
local form = utils.parse_form(data.request.body or "")
|
||||||
|
|
||||||
|
if not (form.password and form.confirm_password) or
|
||||||
|
form.password ~= form.confirm_password or
|
||||||
|
not utils.is_valid_password(form.password) then
|
||||||
|
data.response.code = 301
|
||||||
|
data.response.headers["Location"] = "/signup"
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
|
||||||
|
local id = authors.create_account(form.password)
|
||||||
|
local token = authors.auth(id, form.password)
|
||||||
|
|
||||||
|
-- TODO: Add Secure;!
|
||||||
|
data.response.headers["Set-Cookie"] = "auth_token=" .. token
|
||||||
|
.. "; Path=/; HttpOnly; SameSite=Strict"
|
||||||
|
|
||||||
|
data.request.cookies.auth_token = token
|
||||||
|
|
||||||
|
return wrapper(data) {
|
||||||
|
h1 { phosphor("identification-card"), " your identifier is ", id, "!" },
|
||||||
|
|
||||||
|
section {
|
||||||
|
p [[
|
||||||
|
that's your identifier! please do not lose it! it's important!
|
||||||
|
it's what allows us to tell you apart from everyone else.
|
||||||
|
]],
|
||||||
|
p [[
|
||||||
|
thanks for joining us! we hope you enjoy your stay, you rock!
|
||||||
|
]],
|
||||||
|
},
|
||||||
|
|
||||||
|
p {
|
||||||
|
"you can begin by ",
|
||||||
|
a {
|
||||||
|
href = "/new-blog",
|
||||||
|
"making a blog"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
|
@ -1,19 +1,24 @@
|
||||||
---@diagnostic disable:undefined-global
|
---@diagnostic disable:undefined-global
|
||||||
|
|
||||||
local config = require "./config"
|
local config = require "./config"
|
||||||
|
local authors = require "./authors"
|
||||||
|
local phosphor = require "template.phosphor"
|
||||||
|
|
||||||
return function(settings)
|
return function(data)
|
||||||
-- create top navigation element
|
-- create top navigation element
|
||||||
local navigator = {
|
local left_side = {
|
||||||
class = "top-navigator",
|
class = "top-navigator-url",
|
||||||
a { href = "/", config.base_url .. "/" }
|
a { href = "/", config.base_url .. "/" }
|
||||||
}
|
}
|
||||||
|
|
||||||
if settings.url then
|
local url = data.request.path
|
||||||
|
local last
|
||||||
|
|
||||||
|
if url then
|
||||||
local segments = {}
|
local segments = {}
|
||||||
|
|
||||||
-- parse URL segments
|
-- parse URL segments
|
||||||
for segment in settings.url:gmatch("[^/]+") do
|
for segment in url:gmatch("[^/]+") do
|
||||||
table.insert(segments, segment)
|
table.insert(segments, segment)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -22,15 +27,59 @@ return function(settings)
|
||||||
for i, segment in ipairs(segments) do
|
for i, segment in ipairs(segments) do
|
||||||
path = path .. "/" .. segment
|
path = path .. "/" .. segment
|
||||||
|
|
||||||
|
last = segment .. (i < #segments and "/" or "")
|
||||||
|
|
||||||
-- add separator and link
|
-- add separator and link
|
||||||
table.insert(navigator, " ")
|
table.insert(left_side, " ")
|
||||||
table.insert(navigator, a {
|
table.insert(left_side, a {
|
||||||
href = path,
|
href = path,
|
||||||
segment .. (i < #segments and "/" or "")
|
last
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local right_side = {
|
||||||
|
class = "top-navigator-actions",
|
||||||
|
a { href = "/signup", "start blogging!" }
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.request.cookies.auth_token then
|
||||||
|
local id = authors.id_from_token(data.request.cookies.auth_token)
|
||||||
|
if not id then
|
||||||
|
data.response.headers["Set-Cookie"] =
|
||||||
|
"auth_token=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=/; HttpOnly"
|
||||||
|
data.response.headers["Refresh"] = "0"
|
||||||
|
end
|
||||||
|
|
||||||
|
right_side = {
|
||||||
|
class = "top-navigator-actions",
|
||||||
|
a {
|
||||||
|
class = "action-button",
|
||||||
|
href = "/new-post",
|
||||||
|
phosphor("note-pencil")
|
||||||
|
},
|
||||||
|
a {
|
||||||
|
class = "action-button",
|
||||||
|
href = "/profile",
|
||||||
|
phosphor("user")
|
||||||
|
},
|
||||||
|
a {
|
||||||
|
class = "action-button",
|
||||||
|
href = "/logout",
|
||||||
|
phosphor("sign-out")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local error_card = ""
|
||||||
|
if error then
|
||||||
|
error_card = div {
|
||||||
|
class = "error-card",
|
||||||
|
phosphor("smiley-x-eyes"),
|
||||||
|
p "hey piece of shit"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
return function(contents)
|
return function(contents)
|
||||||
-- ensure contents is always a table
|
-- ensure contents is always a table
|
||||||
if type(contents) == "string" then
|
if type(contents) == "string" then
|
||||||
|
@ -42,14 +91,20 @@ return function(settings)
|
||||||
meta { charset = "UTF-8" },
|
meta { charset = "UTF-8" },
|
||||||
meta { ["http-equiv"] = "X-UA-Compatible", content = "IE=edge" },
|
meta { ["http-equiv"] = "X-UA-Compatible", content = "IE=edge" },
|
||||||
meta { name = "viewport", content = "width=device-width, initial-scale=1.0" },
|
meta { name = "viewport", content = "width=device-width, initial-scale=1.0" },
|
||||||
title(settings.title),
|
title { "/", last },
|
||||||
|
|
||||||
link { href = "/static/baseline.css", rel = "stylesheet" },
|
link { href = "/static/baseline.css", rel = "stylesheet" },
|
||||||
script { src = "https://unpkg.com/@phosphor-icons/web@2.1.1" }
|
script { src = "https://unpkg.com/@phosphor-icons/web@2.1.1" }
|
||||||
},
|
},
|
||||||
|
|
||||||
body {
|
body {
|
||||||
nav(navigator),
|
nav {
|
||||||
|
class = "top-navigator",
|
||||||
|
div(left_side),
|
||||||
|
div(right_side)
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
table.unpack(contents)
|
table.unpack(contents)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
86
utils.lua
86
utils.lua
|
@ -1,4 +1,9 @@
|
||||||
return {
|
local ffi = require "ffi"
|
||||||
|
local openssl = require "openssl"
|
||||||
|
|
||||||
|
local utils = {}
|
||||||
|
|
||||||
|
utils.errors = {
|
||||||
[400] = "bad_request",
|
[400] = "bad_request",
|
||||||
[401] = "unauthorized",
|
[401] = "unauthorized",
|
||||||
[403] = "forbidden",
|
[403] = "forbidden",
|
||||||
|
@ -20,3 +25,82 @@ return {
|
||||||
[504] = "gateway_timeout",
|
[504] = "gateway_timeout",
|
||||||
[505] = "http_version_not_supported"
|
[505] = "http_version_not_supported"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- TODO: REWRITE
|
||||||
|
utils.random = function(min, max)
|
||||||
|
local buf = ffi.new("uint8_t[4]")
|
||||||
|
ffi.copy(buf, openssl.random(4), 4)
|
||||||
|
local num = 0
|
||||||
|
for i = 0, 3 do
|
||||||
|
num = bit.bor(bit.lshift(num, 8), buf[i])
|
||||||
|
end
|
||||||
|
return min + (num % (max - min + 1))
|
||||||
|
end
|
||||||
|
|
||||||
|
utils.pin = function()
|
||||||
|
local o = ""
|
||||||
|
for i = 1, 4 do
|
||||||
|
o = o .. tostring(utils.random(0, 9))
|
||||||
|
end
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
|
||||||
|
utils.uuid = function()
|
||||||
|
local template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
|
||||||
|
return string.gsub(template, '[xy]', function(c)
|
||||||
|
local v =
|
||||||
|
(c == 'x')
|
||||||
|
and utils.random(0, 0xf)
|
||||||
|
or utils.random(8, 0xb)
|
||||||
|
|
||||||
|
return string.format('%x', v)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
utils.hash = function(data)
|
||||||
|
return openssl.digest.digest("sha256", data)
|
||||||
|
end
|
||||||
|
|
||||||
|
utils.decode_url = function(str)
|
||||||
|
str = str:gsub('+', ' ') -- Convert + to space
|
||||||
|
return str:gsub("%%(%x%x)", function(hex)
|
||||||
|
return string.char(tonumber(hex, 16))
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
utils.parse_form = function(data)
|
||||||
|
local result = {}
|
||||||
|
for pair in data:gmatch("[^&]+") do
|
||||||
|
local key, value = pair:match("([^=]+)=?(.*)")
|
||||||
|
if key then
|
||||||
|
result[utils.decode_url(key)] = utils.decode_url(value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
utils.is_valid_password = function(password)
|
||||||
|
return #password >= 8
|
||||||
|
and #password <= 16
|
||||||
|
and password:match("%d")
|
||||||
|
and password:match("%l")
|
||||||
|
and password:match("%u")
|
||||||
|
and password:match("[@$!%%*?&]")
|
||||||
|
end
|
||||||
|
|
||||||
|
utils.cookies = function(req, res, go)
|
||||||
|
req.cookies = {}
|
||||||
|
for _, possible_cookie_jar in ipairs(req.headers) do
|
||||||
|
if possible_cookie_jar[1] == "Cookie" then
|
||||||
|
local form = utils.parse_form(possible_cookie_jar[2])
|
||||||
|
for key, value in pairs(form) do
|
||||||
|
req.cookies[key] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
go()
|
||||||
|
end
|
||||||
|
|
||||||
|
package.preload["./utils"] = utils
|
||||||
|
|
||||||
|
return utils
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue