box.cfg { log_level = 5; } local log = require('log') local crypto = require('crypto') local digest = require('digest') local config = require('config') local fio = require('fio') local yaml = require('yaml') local json = require('json') local aes_key = digest.urandom(32) local aes_iv = digest.urandom(16) -- Init configuration config.output_directory = config.output_directory or fio.pathjoin(config.hugo_directory,'public') local hugo_config, err = fio.open(fio.pathjoin(config.hugo_directory,"config.yaml"),{'O_WRONLY','O_CREAT'}) if (err) then error(err) end hugo_config:truncate(0) hugo_config:write(yaml.encode(config.hugo_config)) hugo_config:close() fio.chmod(fio.pathjoin(config.hugo_directory,"config.yaml"),tonumber('0664',8)) box.once('init', function() box.schema.create_space('users') box.space.users:format({ {name='email',type='string'}, {name='pass', type='string'}, {name='salt', type='string'}, {name='token',type='string'}, {name='role', type='unsigned'} }) box.space.users:create_index( 'primary', {unique=true, type='HASH',parts={1,'string'}} ) box.schema.user.grant('guest','read,write,execute','universe') local usalt = digest.urandom(32) local upass = digest.pbkdf2(config.admin_password, usalt) box.space.users:insert({config.admin_email, upass, usalt, '', 1}) end) local create_account = function(spec) if auth_user:match("[A-Za-z0-9%.%%%+%-]+@[A-Za-z0-9%.%%%+%-]+%.%w%w%w?%w?") == nil then box.error(1,auth_user.." is not a valid e-mail address") end end local make_token = function(user) local token = digest.urandom(32) box.space.users:update(user.email,{{'=',4,token}}) return digest.base64_encode(digest.aes256cbc.encrypt(user.email..':'..token,aes_key,aes_iv),{urlsafe=true}) end local text_response = function(req, code, msg, headers) local resp = req:render({text=msg or ""}) resp.status = code if headers then for key,val in pairs(headers) do resp.headers[key] = val end end return resp end local json_response = function(req, code, msg, headers) local resp = req:render({json=msg or "{}"}) resp.status = code if headers then for key,val in pairs(headers) do resp.headers[key] = val end end return resp end local auth_methods = { Basic = function(auth_encoded) local auth_user, auth_pass = digest.base64_decode(auth_encoded):match("(.*):(.*)") local user = box.space.users:select({auth_user})[1] if not user or digest.pbkdf2(auth_pass, user.salt) ~= user.pass then return nil end return user end, Bearer = function(auth_encoded) local token_enc = digest.base64_decode(auth_encoded) if token_enc:len() ~= 64 then return nil end local token local decrypt = function(enc,key,iv) token = digest.aes256cbc.decrypt(token_enc,aes_key,aes_iv) end if not pcall(decrypt) then return nil end local auth_user, auth_token = token:match("(.*):(.*)") local user = box.space.users:select({auth_user})[1] if not user or auth_token ~= user.token then return nil end return user end } local file_commands = { GET = function(user, req, target) if not fio.path.exists(target) then return text_response(req,404) end if fio.path.is_dir(target) then local files = fio.listdir(target) local file_dict = {} for i, f in ipairs(files) do file_dict[f] = fio.path.is_dir(fio.pathjoin(target,f)) end return json_response(req,200,json.encode(file_dict)) end local file, err = fio.open(target, {'O_RDONLY'}) if err then return text_response(req,500,err) end local data = file:read() file:close() return text_response(req,200,data) end, POST = function(user, req, target) if fio.path.is_dir(target) then return text_response(req,405,"File is a directory") end fio.mktree(fio.dirname(target)) local file, err = fio.open(target, {'O_WRONLY','O_CREAT'}) if err then return text_response(req,500,err) end file:truncate(0) file:write(req:read()) file:close() fio.chmod(target,tonumber('0664',8)) os.execute('hugo -s '..config.hugo_directory..' -d '..config.output_directory)--..' 1> /dev/null') return text_response(req,200) end, PUT = function(user, req, target) if fio.path.is_dir(target) then return text_response(req,405,"File is a directory") end fio.mktree(fio.dirname(target)) local file, err = fio.open(target, {'O_WRONLY','O_CREAT','O_APPEND'}) if err then return text_response(req,500,err) end file:write(req:read()) file:close() fio.chmod(target,tonumber('0664',8)) os.execute('hugo -s '..config.hugo_directory..' -d '..config.output_directory)--..' 1> /dev/null') return text_response(req,200) end, DELETE = function(user, req, target) if not fio.path.exists(target) then return text_response(req,404) end if fio.path.is_dir(target) then local _ok, err = fio.rmtree(target) if not _ok then return text_response(req,500,err) end else os.remove(target) end os.execute('hugo -s '..config.hugo_directory..' -d '..config.output_directory)--..' 1> /dev/null') return text_response(req,200) end } local routes = { ['/login'] = function(user,req) local token = make_token(user) local token_header = { ["Set-Cookie"] = { "auth_token="..token.."; Secure; HttpOnly; SameSite=Strict", "live-edit=true; path=/" }, ["Location"] = ".." } local response = text_response(req,302,"",token_header) return response end, } local function webapi(self,req) local user = nil local auth_cookie = req:cookie('auth_token') log.info(req.headers.authorization) log.info(auth_cookie) if req.headers.authorization then local auth_type, auth_encoded = req.headers.authorization:match("(.*) (.*)") local auth_func = auth_methods[auth_type] if auth_func then user = auth_func(auth_encoded) end end if not user and auth_cookie then user = auth_methods.Bearer(auth_cookie) end if not user then return text_response(req,401,nil,{['WWW-Authenticate']='Basic'}) end local command = routes[req.path] if not command then local path = req.path:match("^([-_a-zA-Z0-9/%.]*)") local target = fio.abspath(fio.pathjoin(config.hugo_directory,path)) if not target:match("^"..config.hugo_directory) then return text_response(req,403) end command = file_commands[req.method] if not command then return text_response(req,405) end return command(user,req,target) end return command(user,req) end require('http.server').new(config.host,config.port,{handler=webapi,display_errors=true,log_errors=true,log_requests=true}):start()