changeset 3:fc3ee39d7764

login
author Franklin Schmidt <fschmidt@gmail.com>
date Sun, 19 Jun 2022 20:47:31 -0600
parents fc2383eb48a9
children a17e400ddaa1
files src/account.html.luan src/index.html.luan src/lib/Db.luan src/lib/Shared.luan src/lib/User.luan src/login.html.luan src/logout.html.luan
diffstat 7 files changed, 343 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/account.html.luan	Sun Jun 19 20:47:31 2022 -0600
@@ -0,0 +1,33 @@
+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 footer = Shared.footer or error()
+local Forum = require "site:/lib/Forum.luan"
+local forum_title = Forum.title or error()
+
+
+return function()
+	Io.stdout = Http.response.text_writer()
+%>
+<!doctype html>
+<html>
+	<head>
+<%		head() %>
+		<title><%=forum_title%> - Your Account</title>
+	</head>
+	<body>
+<%		header() %>
+		<div content>
+			<h1>Your Account</h1>
+
+			<p><a href="/logout.html">logout</a></p>
+		</div>
+<%		footer() %>
+	</body>
+</html>
+<%
+end
--- a/src/index.html.luan	Thu Jun 16 20:52:24 2022 -0600
+++ b/src/index.html.luan	Sun Jun 19 20:47:31 2022 -0600
@@ -7,6 +7,7 @@
 local header = Shared.header or error()
 local footer = Shared.footer or error()
 local Forum = require "site:/lib/Forum.luan"
+local forum_title = Forum.title or error()
 
 
 return function()
@@ -16,7 +17,7 @@
 <html>
 	<head>
 <%		head() %>
-		<title><%=Forum.title%></title>
+		<title><%=forum_title%></title>
 	</head>
 	<body>
 <%		header() %>
--- a/src/lib/Db.luan	Thu Jun 16 20:52:24 2022 -0600
+++ b/src/lib/Db.luan	Sun Jun 19 20:47:31 2022 -0600
@@ -9,4 +9,7 @@
 
 local Db = Lucene.index( dir, {} )
 
+Db.indexed_fields.user_email = Lucene.type.lowercase
+Db.indexed_fields.user_name = Lucene.type.lowercase
+
 return Db
--- a/src/lib/Shared.luan	Thu Jun 16 20:52:24 2022 -0600
+++ b/src/lib/Shared.luan	Sun Jun 19 20:47:31 2022 -0600
@@ -1,6 +1,10 @@
 local Luan = require "luan:Luan.luan"
 local error = Luan.error
+local Http = require "luan:http/Http.luan"
 local Forum = require "site:/lib/Forum.luan"
+local User = require "site:/lib/User.luan"
+local Logging = require "luan:logging/Logging.luan"
+local logger = Logging.logger "Shared"
 
 
 local Shared = {}
@@ -15,9 +19,16 @@
 end
 
 function Shared.header()
+	local user = User.current()
 %>
 		<div header>
 			<a href="/"><%=Forum.title%></a>
+			-
+<%	if user == nil then %>
+			<a href="/login.html">login</a>
+<%	else %>
+			<a href="/account.html"><%=user.name_html%></a>
+<%	end %>
 		</div>
 <%
 end
@@ -30,4 +41,8 @@
 <%
 end
 
+function Shared.base_url()
+	return Http.request.scheme.."://"..Http.request.headers["host"]
+end
+
 return Shared
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/User.luan	Sun Jun 19 20:47:31 2022 -0600
@@ -0,0 +1,118 @@
+local Luan = require "luan:Luan.luan"
+local error = Luan.error
+local set_metatable = Luan.set_metatable or error()
+local range = Luan.range or error()
+local String = require "luan:String.luan"
+local sub_string = String.sub or error()
+local Table = require "luan:Table.luan"
+local concat = Table.concat or error()
+local Math = require "luan:Math.luan"
+local random = Math.random or error()
+local Time = require "luan:Time.luan"
+local time_now = Time.now or error()
+local Html = require "luan:Html.luan"
+local html_encode = Html.encode 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 User = {}
+
+local function from_doc(doc)
+	doc.type == "user" or error "wrong type"
+	return User.new {
+		id = doc.id
+		email = doc.user_email
+		password = doc.password
+		name = doc.user_name
+		created = doc.created
+	}
+end
+
+local function to_doc(user)
+	local email = user.email
+	return {
+		type = "user"
+		id = user.id
+		user_email = email
+		password = user.password
+		user_name = user.name
+		created = user.created or time_now()
+	}
+end
+
+local metatable = {}
+function metatable.__index(user,key)
+	if key == "name_html" then
+		user.name_html = html_encode(user.name)
+		return user.name_html
+	end
+	return nil
+end
+
+function User.new(user)
+
+	function user.save()
+		local doc = to_doc(user)
+		Db.save(doc)
+		user.id = doc.id
+	end
+
+	set_metatable(user,metatable)
+	return user
+end
+
+function User.get_by_email(email)
+	local doc = Db.get_document("user_email:"..lucene_quote(email))
+	return doc and from_doc(doc)
+end
+
+local function get_by_name(name)
+	local doc = Db.get_document("user_name:"..lucene_quote(name))
+	return doc and from_doc(doc)
+end
+User.get_by_name = get_by_name
+
+function User.current()
+	local name = Http.request.cookies.user
+	local password = Http.request.cookies.password
+	if name == nil or password == nil then
+		return nil
+	end
+	local user = get_by_name(name)
+	if user == nil or user.password ~= password then
+		return nil
+	end
+	return user
+end
+
+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)
+	local user = User.get_by_email(email)
+	if user == nil then
+		user = User.new{ email=email, password=new_password() }
+		user.save()
+	end
+	return user
+end
+
+return User
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/login.html.luan	Sun Jun 19 20:47:31 2022 -0600
@@ -0,0 +1,135 @@
+local Luan = require "luan:Luan.luan"
+local error = Luan.error
+local String = require "luan:String.luan"
+local trim = String.trim or error()
+local Html = require "luan:Html.luan"
+local url_encode = Html.url_encode or 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 footer = Shared.footer or error()
+local base_url = Shared.base_url or error()
+local Forum = require "site:/lib/Forum.luan"
+local forum_title = Forum.title or error()
+local User = require "site:/lib/User.luan"
+local Db = require "site:/lib/Db.luan"
+local run_in_transaction = Db.run_in_transaction or error()
+
+
+local function get_user(email,password)
+	local user = User.get_by_email(email)
+	user or error "email not found"
+	user.password == password or error "wrong password"
+	return user
+end
+
+local function login(user)
+	Http.response.set_persistent_cookie("user",user.name)
+	Http.response.set_persistent_cookie("password",user.password)
+	Http.request.cookies.user = user.name
+	Http.request.cookies.password = user.password
+end
+
+local function register_form(user,name,error_message)
+	if error_message ~= nil then %>
+			<p error>Error: <%= error_message %></p>
+<%	end %>
+			<form>
+				<input type="hidden" name="email" value="<%= user.email %>" >
+				<input type="hidden" name="password" value="<%= user.password %>" >
+				<label>User name for <%= user.email %></label>
+				<input type="text" name="name" value="<%= name or "" %>" autofocus required>
+				<input type="submit" value="Register">
+			</form>
+<%
+end
+
+local function page(contents)
+	Io.stdout = Http.response.text_writer()
+%>
+<!doctype html>
+<html>
+	<head>
+<%		head() %>
+		<title><%=forum_title%> - Login or Register</title>
+	</head>
+	<body>
+<%		header() %>
+		<div content>
+			<h1>Login or Register</h1>
+<%
+			contents()
+%>
+		</div>
+<%		footer() %>
+	</body>
+</html>
+<%
+end
+
+return function()
+	local email = Http.request.parameters.email
+	local password = Http.request.parameters.password
+	local name = Http.request.parameters.name
+	if email == nil then
+		page(function()
+%>
+			<form>
+				<label>Email address</label>
+				<input type="email" name="email" autofocus required>
+				<input type="submit" value="Login or Register">
+			</form>
+<%
+		end)
+	elseif password == nil then
+		local user = User.get_or_create_by_email(email)
+		page(function()
+%>
+			<p>We have sent you an email.  Please check your email to login or register.</p>
+			<p>hack - <a href="<%=base_url()%>/login.html?email=<%=url_encode(email)%>&password=<%=user.password%>">link</a></p>
+<%
+		end)
+	elseif name == nil then
+		local user = get_user(email,password)
+		if user.name == nil then
+			page(function()
+				register_form(user)
+			end)
+		else
+			login(user)
+			page(function()
+%>
+			<p>You are now logged in.</p>
+<%
+			end)
+		end
+	else
+		name = trim(name)
+		#name > 0 or error "empty name"
+		local error_message = nil
+		local user
+		run_in_transaction( function()
+			user = get_user(email,password)
+			if user.name ~= name and User.get_by_name(name) ~= nil then
+				error_message = "Name already in use"
+			else
+				user.name = name
+				user.save()
+			end
+		end )
+		if error_message ~= nil then
+			page(function()
+				register_form(user,name,error_message)
+			end)
+		else
+			login(user)
+			page(function()
+%>
+			<p>You are now registered.</p>
+<%
+			end)
+		end
+	end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/logout.html.luan	Sun Jun 19 20:47:31 2022 -0600
@@ -0,0 +1,37 @@
+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 footer = Shared.footer or error()
+local Forum = require "site:/lib/Forum.luan"
+local forum_title = Forum.title or error()
+
+
+return function()
+	Http.response.remove_cookie("user")
+	Http.response.remove_cookie("password")
+	Http.request.cookies.user = nil
+	Http.request.cookies.password = nil
+	Io.stdout = Http.response.text_writer()
+%>
+<!doctype html>
+<html>
+	<head>
+<%		head() %>
+		<title><%=forum_title%> - Logout</title>
+	</head>
+	<body>
+<%		header() %>
+		<div content>
+			<h1>Logout</h1>
+
+			<p>You have been logged out.</p>
+		</div>
+<%		footer() %>
+	</body>
+</html>
+<%
+end