|
| 1 | +-- Mini Content Management System (CMS) designed for the Pure.css dashboard. |
| 2 | + |
| 3 | +-- All LSP applications have a predefined 'dir' object, which is a resrdr instance. |
| 4 | +-- Ref resrdr: https://realtimelogic.com/ba/doc/en/lua/lua.html#ba_create_resrdr |
| 5 | +-- The dir object is not set when running as an Xedge xlua app (not LSP enabled app) |
| 6 | +-- https://realtimelogic.com/ba/doc/en/Xedge.html#using |
| 7 | +if not dir then |
| 8 | + error"This application must run as an LSP enabled app" |
| 9 | +end |
| 10 | + |
| 11 | +local fmt=string.format |
| 12 | + |
| 13 | +-- Load module for reading and writing raw-files and json-files. |
| 14 | +-- Details: https://realtimelogic.com/ba/doc/en/lua/auxlua.html#rwfile |
| 15 | +local rw=require"rwfile" |
| 16 | + |
| 17 | +-- Convert to closures so variables work in the cmsfunc() callback function |
| 18 | +-- Closure def: https://en.wikipedia.org/wiki/Closure_(computer_programming) |
| 19 | +local app,io,dir=app,app.io,app.dir |
| 20 | + |
| 21 | +-- Set on 'dir' and used by function cmsfunc() |
| 22 | +local securityPolicies={ |
| 23 | + ["Content-Security-Policy"]= "default-src 'self'; script-src 'self' cdn.jsdelivr.net unpkg.com 'unsafe-inline'; style-src 'self' cdn.jsdelivr.net unpkg.com 'unsafe-inline'", |
| 24 | + ["X-Content-Type-Options"]="nosniff", |
| 25 | +} |
| 26 | +-- Doc: https://realtimelogic.com/ba/doc/en/lua/lua.html#rsrdr_header |
| 27 | +dir:header(securityPolicies) |
| 28 | + |
| 29 | +-- Takes the data content of an LSP page (HTML with LSP tags) as |
| 30 | +-- argument, converts the LSP page to Lua code, and compiles the Lua |
| 31 | +-- code. The compiled Lua code is returned as a function. |
| 32 | +-- Details: https://realtimelogic.com/ba/doc/en/lua/lua.html#ba_parselsp |
| 33 | +local function parseLspPage(name) |
| 34 | + local func |
| 35 | + local data,err=rw.file(io,name) -- Read file content |
| 36 | + if data then |
| 37 | + data,err = ba.parselsp(data) |
| 38 | + if data then |
| 39 | + local func |
| 40 | + -- Compile Lua code |
| 41 | + -- ref load: https://realtimelogic.com/ba/doc/luaref_index.html?url=pdf-load |
| 42 | + func,err = load(data,name,"t") |
| 43 | + if func then return func end |
| 44 | + end |
| 45 | + end |
| 46 | + -- Failed |
| 47 | + err = fmt("parsing or running %s failed: %s",name,err) |
| 48 | + trace(err) |
| 49 | + return function(_ENV) print(err) end |
| 50 | +end |
| 51 | + |
| 52 | +-- Parse and cache the template page. |
| 53 | +local templatePage=parseLspPage(".lua/www/template.lsp") |
| 54 | + |
| 55 | + |
| 56 | +-- Load the JSON encoded menu |
| 57 | +local menuL=rw.json(io,".lua/menu.json") |
| 58 | +assert(menuL, "lua/menu.json parse error") |
| 59 | + |
| 60 | +--Create a key/value version of the menu list, where key is the relative path name 'href'. |
| 61 | +local menuT={} |
| 62 | +for _,m in ipairs(menuL) do |
| 63 | + menuT[m.href]=m |
| 64 | +end |
| 65 | + |
| 66 | +-- An LSP page expects a persistent table unique to each page. The |
| 67 | +-- pagesT is a table where the key is the relative path and the value |
| 68 | +-- is the LSP page's unique table. |
| 69 | +-- Details: https://realtimelogic.com/ba/doc/en/lua/lua.html#CMDE |
| 70 | +local pagesT={} |
| 71 | + |
| 72 | +-- The directory callback function. This callback is called by the |
| 73 | +-- server when a resource is accessed. Argument env is the |
| 74 | +-- request/response environment table and relpath is the relative path. |
| 75 | +-- See the following for details on the request/response environment: |
| 76 | +-- https://realtimelogic.com/ba/doc/en/lua/lua.html#CMDE |
| 77 | +local function cmsfunc(_ENV, relpath, notInMenuOK) |
| 78 | + trace("hx-request:",request:header"hx-request" and "yes" or "no") |
| 79 | + local response=response -- e.g. = _ENV.response. Now faster. |
| 80 | + |
| 81 | + -- Translate to (path/)index.html if only directory name is provided. |
| 82 | + if #relpath == 0 or relpath:find"/$" then |
| 83 | + relpath = relpath.."index.html" |
| 84 | + end |
| 85 | + |
| 86 | + -- Do we have the requested page (must be in file menu.json) |
| 87 | + if not menuT[relpath] and not notInMenuOK then |
| 88 | + if not relpath:find(".html",-5,true) then |
| 89 | + return false -- Not a html page. Let default 404 handle this |
| 90 | + end |
| 91 | + trace("Not found",relpath) -- For debug purposes |
| 92 | + response:setstatus(404) |
| 93 | + relpath = "404.html" |
| 94 | + end |
| 95 | + |
| 96 | + -- Fetch the LSP page's persistent page table. Create the table if |
| 97 | + -- the page has so far not been accessed. |
| 98 | + local pageT=pagesT[relpath] |
| 99 | + if not pageT then |
| 100 | + pageT={} |
| 101 | + pagesT[relpath]=pageT |
| 102 | + end |
| 103 | + |
| 104 | + --Remove the following line and xrsp:finalize() if you do not want to |
| 105 | + --compress the response. |
| 106 | + local xrsp = response:setresponse() -- Activate compression |
| 107 | + response:setdefaultheaders() |
| 108 | + |
| 109 | + local lspPage=parseLspPage(".lua/www/"..relpath) |
| 110 | + if request:header"hx-request" then |
| 111 | + lspPage(_ENV,relpath,io,pageT,app) |
| 112 | + else |
| 113 | + for k,v in pairs(securityPolicies) do response:setheader(k,v) end |
| 114 | + -- Make the following available to template.lsp |
| 115 | + -- Note, we explicitly use the _ENV tab for code readability. |
| 116 | + -- Details: https://realtimelogic.com/ba/doc/en/lua/man/manual.html#2.2 |
| 117 | + -- https://realtimelogic.com/ba/doc/en/lua/lua.html#CMDE |
| 118 | + _ENV.menuL=menuL |
| 119 | + _ENV.menuT=menuT |
| 120 | + _ENV.relpath=relpath |
| 121 | + -- lspPage is the parsed page (function as explained in parseLspPage above) |
| 122 | + _ENV.lspPage=lspPage |
| 123 | + -- Call the template page and pass in the required arguments. |
| 124 | + -- Arg details: https://realtimelogic.com/ba/doc/en/lua/lua.html#ba_parselsp |
| 125 | + templatePage(_ENV,relpath,io,pageT,app) |
| 126 | + -- non cached version of above. Use if testing new template. |
| 127 | + --parseLspPage(".lua/www/template.lsp")(_ENV,relpath,io,pageT,app) |
| 128 | + end |
| 129 | + xrsp:finalize(true) -- Send compressed data to client |
| 130 | + |
| 131 | + return true |
| 132 | +end |
| 133 | + |
| 134 | +-- Create the directory function used by our mini CMS system and |
| 135 | +-- install the callback function. A directory with no name is in |
| 136 | +-- effect a sibling when installed as a sub-directory. The following |
| 137 | +-- construction makes sure we do not trigger the callback for any |
| 138 | +-- static assets. In other words, static assets take precedence. The |
| 139 | +-- callback is called when no static asset is found. |
| 140 | +-- Ref dir: https://realtimelogic.com/ba/doc/en/lua/lua.html#ba_create_dir |
| 141 | +local cmsDir = ba.create.dir() |
| 142 | +cmsDir:setfunc(cmsfunc) |
| 143 | + |
| 144 | + |
| 145 | +-- Insert cmsDir as 'dir' siblings. This means the parent will be |
| 146 | +-- searched first. The parent resource reader (dir) manages the static |
| 147 | +-- content. See www/static for content returned to browser |
| 148 | +dir:insert(cmsDir,true) |
| 149 | + |
| 150 | + |
| 151 | + |
| 152 | +----------------------- AUTHENTICATION ----------------------------- |
| 153 | + |
| 154 | +if ba.tpm then |
| 155 | + -- The following code is based on example from: |
| 156 | + -- https://realtimelogic.com/ba/doc/en/lua/auxlua.html#TPM |
| 157 | + |
| 158 | + local cfgio = ba.openio"home" or ba.openio"disk" -- mako or xedge |
| 159 | + local rw=require"rwfile" |
| 160 | + |
| 161 | + -- Read/write encrypted db. Write if 'encdb' provided |
| 162 | + local function rwdb(encdb) |
| 163 | + trace(encdb and "Writing" or "Reading","userdb.encrypted") |
| 164 | + return rw.file(cfgio,"userdb.encrypted",encdb) |
| 165 | + end |
| 166 | + |
| 167 | + -- ba.create.authenticator() callback function |
| 168 | + local function loginresponse(_ENV, authinfo) |
| 169 | + -- How _ENV is used: https://realtimelogic.com/ba/doc/en/lua/lua.html#CMDE |
| 170 | + _ENV.authinfo = authinfo -- Makes it possible for .login-form.lsp to use authinfo |
| 171 | + -- The following prints the content of the authinfo table |
| 172 | + trace("loginresponse", ba.json.encode(authinfo)) |
| 173 | + cmsfunc(_ENV,"login.html", true) -- Let the CMS function emit the login page. |
| 174 | + end |
| 175 | + |
| 176 | + -- Create the wrapper and make the encrypted DB global |
| 177 | + local tju=ba.tpm.jsonuser("dashboard",true) |
| 178 | + |
| 179 | + local authDir=nil |
| 180 | + local function setOrRemoveAuth() |
| 181 | + if #tju.users() > 0 and not authDir then -- SET |
| 182 | + authDir = ba.create.dir(1) -- Set priority to a value greater than cmsDir. |
| 183 | + local authenticator=ba.create.authenticator(tju.getauth(),{response=loginresponse, type="form"}) |
| 184 | + authDir:setauth(authenticator) |
| 185 | + dir:insert(authDir,true) -- Higher prio than cmsDir thus executes before |
| 186 | + trace"Installing authenticator" |
| 187 | + elseif #tju.users() == 0 and authDir then -- REMOVE |
| 188 | + authDir:unlink() -- Remove authenticator |
| 189 | + authDir=nil |
| 190 | + trace"Removing authenticator" |
| 191 | + end |
| 192 | + end |
| 193 | + |
| 194 | + function setuser(name,pwd) -- Used by www/.lua/www/Users.html |
| 195 | + rwdb(tju.setuser(name,pwd)) |
| 196 | + setOrRemoveAuth() |
| 197 | + end |
| 198 | + |
| 199 | + local encdb=rwdb() -- Load the encryted DB, if any |
| 200 | + if encdb then |
| 201 | + -- Set the DB |
| 202 | + local ok,err=tju.setdb(encdb) |
| 203 | + if ok then |
| 204 | + setOrRemoveAuth() |
| 205 | + else |
| 206 | + trace("Authenticator not installed; User DB error:",err) |
| 207 | + end |
| 208 | + else |
| 209 | + trace"No user database; Authenticator not installed" |
| 210 | + end |
| 211 | +else |
| 212 | + trace"No TPM. Authenticator not installed" |
| 213 | +end |
| 214 | + |
| 215 | +-- Return onunload handler |
| 216 | +return function() end -- Not used |
0 commit comments