Mercurial Hosting > chat
changeset 3:2c63b10781e1
add login
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Thu, 24 Oct 2024 21:43:44 -0600 |
parents | ee1f91e67509 |
children | 2da10ece826f |
files | src/account.html.luan src/do_login.html.luan src/error_log.js.luan src/index.html.luan src/lib/Shared.luan src/lib/User.luan src/lib/Utils.luan src/login.html.luan src/login.js.luan src/login_sent.html.luan src/private/tools/config.html.luan src/private/tools/index.html.luan src/site.css src/site.js |
diffstat | 14 files changed, 344 insertions(+), 10 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/account.html.luan Thu Oct 24 21:43:44 2024 -0600 @@ -0,0 +1,27 @@ +local Luan = require "luan:Luan.luan" +local error = Luan.error +local Io = require "luan:Io.luan" +local Http = require "luan:http/Http.luan" +local Shared = require "site:/lib/Shared.luan" +local head = Shared.head or error() +local header = Shared.header or error() + + +return function() + Io.stdout = Http.response.text_writer() +%> +<!doctype html> +<html> + <head> +<% head() %> + </head> + <body> +<% header() %> + <div content> + <h1>Your Account</h1> + <p><a href="javascript:logout()">Logout</a></p> + </div> + </body> +</html> +<% +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/do_login.html.luan Thu Oct 24 21:43:44 2024 -0600 @@ -0,0 +1,40 @@ +local Luan = require "luan:Luan.luan" +local error = Luan.error +local Io = require "luan:Io.luan" +local Http = require "luan:http/Http.luan" +local Shared = require "site:/lib/Shared.luan" +local head = Shared.head or error() +local header = Shared.header or error() +local User = require "site:/lib/User.luan" + + +return function() + local user_id = Http.request.parameters.user or error() + local password = Http.request.parameters.password or error() + local user = User.get_by_id(user_id) + Io.stdout = Http.response.text_writer() +%> +<!doctype html> +<html> + <head> +<% head() %> + </head> + <body> +<% header() %> + <div content> + <h1>Login / Register</h1> +<% if user == nil or user.password ~= password then %> + <p>Login failed</p> +<% + else + user.login() +%> + <script> location = '/'; </script> +<% + end +%> + </div> + </body> +</html> +<% +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/error_log.js.luan Thu Oct 24 21:43:44 2024 -0600 @@ -0,0 +1,23 @@ +local Luan = require "luan:Luan.luan" +local error = Luan.error +local String = require "luan:String.luan" +local trim = String.trim or error() +local regex = String.regex or error() +local contains = String.contains or error() +local Http = require "luan:http/Http.luan" +local Logging = require "luan:logging/Logging.luan" +local logger = Logging.logger "error_log.js" + + +local function priority(err) + return "error" +end + +return function() + local err = Http.request.parameters.err + if err == nil then + return -- stupid bots + end + local call = priority(err) + logger[call](trim(err).."\n"..trim(Http.request.raw_head).."\n") +end
--- a/src/index.html.luan Thu Oct 24 15:37:35 2024 -0600 +++ b/src/index.html.luan Thu Oct 24 21:43:44 2024 -0600 @@ -14,7 +14,6 @@ <html> <head> <% head() %> - <title>Web Chat</title> <style> h1 { margin-bottom: 0;
--- a/src/lib/Shared.luan Thu Oct 24 15:37:35 2024 -0600 +++ b/src/lib/Shared.luan Thu Oct 24 21:43:44 2024 -0600 @@ -4,18 +4,26 @@ local parse = Luan.parse or error() local Io = require "luan:Io.luan" local uri = Io.uri or error() +local Time = require "luan:Time.luan" +local Thread = require "luan:Thread.luan" +local thread_run = Thread.run or error() +local Mail = require "luan:mail/Mail.luan" local User = require "site:/lib/User.luan" local current_user = User.current or error() local Shared = {} +local started = Time.now() + function Shared.head() %> <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>Web Chat</title> <style> - @import "/site.css"; + @import "/site.css?s=<%=started%>"; </style> + <script src="/site.js?s=<%=started%>"></script> <% end @@ -66,4 +74,12 @@ end end +local send_mail = Mail.sender(Shared.config.mail_server).send + +function Shared.send_mail_async(mail) + thread_run( function() + send_mail(mail) + end ) +end + return Shared
--- a/src/lib/User.luan Thu Oct 24 15:37:35 2024 -0600 +++ b/src/lib/User.luan Thu Oct 24 21:43:44 2024 -0600 @@ -1,13 +1,21 @@ local Luan = require "luan:Luan.luan" local error = Luan.error local ipairs = Luan.ipairs or error() +local range = Luan.range or error() local to_string = Luan.to_string or error() local get_local_only = Luan.get_local_only or error() local set_local_only = Luan.set_local_only or error() +local String = require "luan:String.luan" +local sub_string = String.sub or error() +local Math = require "luan:Math.luan" +local random = Math.random or error() +local Table = require "luan:Table.luan" +local concat = Table.concat or error() local Lucene = require "luan:lucene/Lucene.luan" local lucene_quote = Lucene.quote or error() local Http = require "luan:http/Http.luan" local Db = require "site:/lib/Db.luan" +local run_in_transaction = Db.run_in_transaction or error() local Logging = require "luan:logging/Logging.luan" local logger = Logging.logger "User" @@ -61,9 +69,38 @@ end User.get_by_id = get_by_id -function User.get_by_email(email) - local doc = Db.get_document("user_email:"..lucene_quote(email)) - return doc and from_doc(doc) +local password_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +do + local t = {} + for i in range(1,#password_chars) do + t[#t+1] = sub_string(password_chars,i,i) + end + password_chars = t +end + +local function new_password() + local n = #password_chars + local t = {} + for _ in range(1,10) do + t[#t+1] = password_chars[random(n)] + end + return concat(t) +end + +function User.get_or_create_by_email(email) + return run_in_transaction( function() + local doc = Db.get_document("user_email:"..lucene_quote(email)) + if doc ~= nil then + return from_doc(doc) + else + local user = User.new{ + email = email + password = new_password() + } + user.save() + return user + end + end ) end function User.search(query,sort,rows) @@ -76,7 +113,7 @@ return users end -function User.current() +local function current() local user = get_local_only(User,"current") if user == nil then local id = Http.request.cookies.user @@ -85,7 +122,7 @@ user = "nil" else user = get_by_id(id) - if user == nil or user.registered == nil or user.password ~= password then + if user == nil or user.password ~= password then user = "nil" end end @@ -93,9 +130,10 @@ end return user ~= "nil" and user or nil end +User.current = current function User.current_required() - local user = User.current() + local user = current() user or Http.response.send_redirect "/login.html" return user end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/Utils.luan Thu Oct 24 21:43:44 2024 -0600 @@ -0,0 +1,13 @@ +local Luan = require "luan:Luan.luan" +local error = Luan.error +local Http = require "luan:http/Http.luan" + + +local Utils = {} + +function Utils.base_url() + local request = Http.request + return request.scheme.."://"..request.headers["Host"] +end + +return Utils
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login.html.luan Thu Oct 24 21:43:44 2024 -0600 @@ -0,0 +1,36 @@ +local Luan = require "luan:Luan.luan" +local error = Luan.error +local Io = require "luan:Io.luan" +local Http = require "luan:http/Http.luan" +local Shared = require "site:/lib/Shared.luan" +local head = Shared.head or error() +local header = Shared.header or error() + + +return function() + Io.stdout = Http.response.text_writer() +%> +<!doctype html> +<html> + <head> +<% head() %> + </head> + <body> +<% header() %> + <div content> + <h1>Login / Register</h1> + <p>A link to login will be emailed to you.</p> + <form page onsubmit="ajaxForm('/login.js',this)" action="javascript:"> + <p> + <label prompt>Your email address</label> + <input type=email name=email required autofocus> + </p> + <p> + <input type=submit> + </p> + </form> + </div> + </body> +</html> +<% +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login.js.luan Thu Oct 24 21:43:44 2024 -0600 @@ -0,0 +1,30 @@ +local Luan = require "luan:Luan.luan" +local error = Luan.error +local Io = require "luan:Io.luan" +local Http = require "luan:http/Http.luan" +local Shared = require "site:/lib/Shared.luan" +local send_mail_async = Shared.send_mail_async or error() +local Utils = require "site:/lib/Utils.luan" +local base_url = Utils.base_url or error() +local User = require "site:/lib/User.luan" + + +return function() + local email = Http.request.parameters.email or error() + local user = User.get_or_create_by_email(email) + local url = base_url().."/do_login.html?user="..user.id.."&password="..user.password + send_mail_async { + From = "Web Chat <chat@reactionary.software>" + To = email + Subject = "Login" + body = `%> +Here is the link to login: + +<%= url %> +<% ` + } + Io.stdout = Http.response.text_writer() +%> + location = '/login_sent.html'; +<% +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login_sent.html.luan Thu Oct 24 21:43:44 2024 -0600 @@ -0,0 +1,27 @@ +local Luan = require "luan:Luan.luan" +local error = Luan.error +local Io = require "luan:Io.luan" +local Http = require "luan:http/Http.luan" +local Shared = require "site:/lib/Shared.luan" +local head = Shared.head or error() +local header = Shared.header or error() + + +return function() + Io.stdout = Http.response.text_writer() +%> +<!doctype html> +<html> + <head> +<% head() %> + </head> + <body> +<% header() %> + <div content> + <h1>Login / Register</h1> + <p>A link to login has been emailed to you.</p> + </div> + </body> +</html> +<% +end
--- a/src/private/tools/config.html.luan Thu Oct 24 15:37:35 2024 -0600 +++ b/src/private/tools/config.html.luan Thu Oct 24 21:43:44 2024 -0600 @@ -16,7 +16,6 @@ <html> <head> <% head() %> - <title>Chat Private Tools</title> <style> textarea { width: 90%;
--- a/src/private/tools/index.html.luan Thu Oct 24 15:37:35 2024 -0600 +++ b/src/private/tools/index.html.luan Thu Oct 24 21:43:44 2024 -0600 @@ -14,7 +14,6 @@ <html> <head> <% head() %> - <title>Chat Private Tools</title> </head> <body> <% header() %>
--- a/src/site.css Thu Oct 24 15:37:35 2024 -0600 +++ b/src/site.css Thu Oct 24 21:43:44 2024 -0600 @@ -14,6 +14,10 @@ text-decoration: underline; } +input[type="submit"] { + cursor: pointer; +} + div[header] { font-size: 14px; background-color: #ddd; @@ -27,3 +31,10 @@ margin-right: 3%; margin-bottom: 2em; } + +label[prompt] { + display: block; + font-size: small; + margin-top: 2px; + margin-bottom: 4px; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/site.js Thu Oct 24 21:43:44 2024 -0600 @@ -0,0 +1,76 @@ +'use strict'; + +function ajax(url,postData,context) { + let request = new XMLHttpRequest(); + let method = postData ? 'POST' : 'GET'; + request.open( method, url ); + if( postData ) + request.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' ); + request.onload = function() { + if( request.status !== 200 ) { + let err = 'ajax failed: ' + request.status; + if( request.responseText ) { + err += '\n' + request.responseText.trim(); + document.write('<pre>'+request.responseText+'</pre>'); + } + err += '\nurl = ' + url; + err += '\npage = ' + window.location; + ajax( '/error_log.js', 'err='+encodeURIComponent(err) ); + return; + } + try { + eval( request.responseText ); + } catch(e) { + console.log( request.responseText ); + window.err = '\najax-url = ' + url; + throw e; + } + }; + request.send(postData); +} + +window.onerror = function(msg, url, line, col, error) { + if( !url ) + return; + let err = msg; + err += '\nurl = ' + url; + if( url != window.location ) + err += '\npage = ' + window.location; + err += '\nline = '+line; + if( col ) + err += '\ncolumn = ' + col; + if( error ) { + if( error.stack ) + err += '\nstack = ' + error.stack; + if( error.cause ) + err += '\ncause= ' + error.cause; + if( error.fileName ) + err += '\nfileName= ' + error.fileName; + } + if( window.err ) { + err += window.err; + window.err = null; + } + ajax( '/error_log.js', 'err='+encodeURIComponent(err) ); +}; + +function ajaxForm(url,form) { + let post = ''; + for( let i=0; i<form.length; i++ ) { + let input = form[i]; + let name = input.name; + if( name === '' ) + continue; + let type = input.type; + if( (type==='radio' || type==='checkbox') && !input.checked ) + continue; + post += name + '=' + encodeURIComponent(input.value) + '&'; + } + ajax(url,post,{form:form}); +} + +function logout() { + document.cookie = 'user=; Max-Age=0; path=/;'; + document.cookie = 'password=; Max-Age=0; path=/;'; + location = '/'; +}