Stephan Richter
3 years ago
17 changed files with 2074 additions and 207 deletions
@ -0,0 +1,65 @@
@@ -0,0 +1,65 @@
|
||||
package de.srsoftware.widerhall; |
||||
|
||||
import de.srsoftware.examples.translations.App; |
||||
import de.srsoftware.tools.translations.Translation; |
||||
|
||||
import java.net.URLEncoder; |
||||
import java.nio.charset.StandardCharsets; |
||||
import java.security.MessageDigest; |
||||
import java.security.NoSuchAlgorithmException; |
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.regex.Pattern; |
||||
import java.util.stream.Collectors; |
||||
|
||||
public class Util { |
||||
|
||||
private static final MessageDigest SHA256 = getSha256(); |
||||
private static final String EMAIL_PATTERN = "^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^-]+(?:\\.[a-zA-Z0-9_!#$%&'*+/=?`{|}~^-]+)*@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$"; |
||||
|
||||
public static String urlEncode(Map<String, Object> data) { |
||||
String params = data.entrySet() |
||||
.stream() |
||||
.map(entry -> encode(entry.getKey()) + "=" + encode(entry.getValue())) |
||||
.collect(Collectors.joining("&")); |
||||
return params; |
||||
} |
||||
|
||||
private static String encode(Object value) { |
||||
return URLEncoder.encode(value.toString(), StandardCharsets.UTF_8); |
||||
} |
||||
|
||||
|
||||
public static MessageDigest getSha256() { |
||||
try { |
||||
return MessageDigest.getInstance("SHA-256"); |
||||
} catch (NoSuchAlgorithmException e) { |
||||
e.printStackTrace(); |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
public static String sha256(String s) { |
||||
byte[] bytes = SHA256.digest(s.getBytes(StandardCharsets.UTF_8)); |
||||
return hex(bytes); |
||||
} |
||||
|
||||
private static String hex(byte[] bytes) { |
||||
StringBuffer buf = new StringBuffer(bytes.length*2); |
||||
for (var byt : bytes) buf.append(hex(byt)); |
||||
return buf.toString(); |
||||
} |
||||
|
||||
private static String hex(byte b){ |
||||
return (b<16 ? "0" : "") + Integer.toHexString(b); |
||||
} |
||||
|
||||
public static String t(String tx, Object ... fills){ |
||||
return Translation.get(Application.class,tx,fills); |
||||
} |
||||
|
||||
public static boolean isEmail(String email) { |
||||
return email.matches(EMAIL_PATTERN); |
||||
} |
||||
} |
@ -0,0 +1,167 @@
@@ -0,0 +1,167 @@
|
||||
package de.srsoftware.widerhall.data; |
||||
|
||||
import de.srsoftware.widerhall.Configuration; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
import java.io.File; |
||||
import java.sql.Connection; |
||||
import java.sql.DriverManager; |
||||
import java.sql.ResultSet; |
||||
import java.sql.SQLException; |
||||
import java.util.*; |
||||
|
||||
import static de.srsoftware.widerhall.Util.t; |
||||
|
||||
public class Database { |
||||
public static final String EMAIL = "email"; |
||||
public static final String NAME = "name"; |
||||
public static final String HASHED_PASS = "hashedPassword"; |
||||
public static final String SALT = "salt"; |
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Database.class); |
||||
private static final String VARCHAR = "VARCHAR(255)"; |
||||
private static Database singleton = null; |
||||
private static Connection conn; |
||||
|
||||
public Request insertInto(String tbName) { |
||||
return query("INSERT INTO "+tbName); |
||||
} |
||||
|
||||
public class Request{ |
||||
|
||||
private final String sql; |
||||
private HashMap<String,List<Object>> where = new HashMap<>(); |
||||
private HashMap<String,Object> values = new HashMap<>(); |
||||
|
||||
public Request(String sql) { |
||||
this.sql = sql; |
||||
} |
||||
|
||||
public Request where(String key, Object ... values) { |
||||
for (var val : values) where(key,val); |
||||
return this; |
||||
} |
||||
|
||||
public Request where(String key, Object value) { |
||||
var list = where.get(key); |
||||
if (list == null) where.put(key,list = new ArrayList<Object>()); |
||||
list.add(value); |
||||
return this; |
||||
} |
||||
|
||||
public Request values(Map<String,Object> newValues) { |
||||
values.putAll(newValues); |
||||
return this; |
||||
} |
||||
|
||||
public Request values(String key, Object value) { |
||||
values.put(key,value); |
||||
return this; |
||||
} |
||||
|
||||
public void run() throws SQLException { |
||||
var sb = new StringBuilder(sql); |
||||
var args = new ArrayList<Object>(); |
||||
if (!values.isEmpty()){ |
||||
var keys = new ArrayList<String>(); |
||||
for (var entry : values.entrySet()) { |
||||
keys.add(entry.getKey()); |
||||
args.add(entry.getValue()); |
||||
} |
||||
sb.append("("+String.join(", ",keys)+")"); |
||||
sb.append(" VALUES "); |
||||
var arr = new String[args.size()]; |
||||
Arrays.fill(arr,"?"); |
||||
var marks = String.join(", ",arr); |
||||
sb.append("(").append(marks).append(")"); |
||||
} |
||||
var sql = sb.toString(); |
||||
LOG.debug(sql); |
||||
try { |
||||
var stmt = conn.prepareStatement(sql); |
||||
if (!args.isEmpty()) { |
||||
for (int i = 0; i < args.size(); i++) stmt.setObject(i+1, args.get(i)); |
||||
} |
||||
stmt.execute(); |
||||
} catch (SQLException sqle) { |
||||
throw new SQLException(t("Query '{}' failed:",sql),sqle); |
||||
} |
||||
} |
||||
|
||||
public ResultSet exec() throws SQLException { |
||||
var sb = new StringBuilder(sql); |
||||
var args = new ArrayList<Object>(); |
||||
if (!where.isEmpty()){ |
||||
var clauses = new ArrayList<String>(); |
||||
sb.append(" WHERE "); |
||||
|
||||
for (var entry : where.entrySet()){ |
||||
var arr = new String[entry.getValue().size()]; |
||||
Arrays.fill(arr,"?"); |
||||
var marks = String.join(", ",arr); |
||||
clauses.add("("+entry.getKey()+" IN ("+marks+"))"); |
||||
args.addAll(entry.getValue()); |
||||
} |
||||
sb.append(String.join(" AND ",clauses)); |
||||
|
||||
} |
||||
var sql = sb.toString(); |
||||
LOG.debug(sql); |
||||
try { |
||||
var stmt = conn.prepareStatement(sql); |
||||
if (!args.isEmpty()) { |
||||
for (int i = 0; i < args.size(); i++) stmt.setObject(i+1, args.get(i)); |
||||
} |
||||
return stmt.executeQuery(); |
||||
} catch (SQLException sqle) { |
||||
throw new SQLException(t("Query '{}' failed:",sql),sqle); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public Database(Connection connection) { |
||||
this.conn = connection; |
||||
} |
||||
|
||||
public static Database open() { |
||||
if (singleton == null){ |
||||
Configuration config = Configuration.instance(); |
||||
String dbFile = config.dbLocation(); |
||||
String url = "jdbc:sqlite:"+dbFile; |
||||
LOG.debug("Opening {}",url); |
||||
new File(dbFile).getParentFile().mkdirs(); |
||||
try { |
||||
singleton = new Database(DriverManager.getConnection(url)).assertTables(); |
||||
} catch (SQLException sqle) { |
||||
sqle.printStackTrace(); |
||||
} |
||||
} |
||||
return singleton; |
||||
} |
||||
|
||||
private Database assertTables() throws SQLException { |
||||
if (!tableExists("Users")) createUsersTable(); |
||||
return this; |
||||
} |
||||
|
||||
private void createUsersTable() throws SQLException { |
||||
query("CREATE TABLE Users ("+EMAIL+" "+ VARCHAR +", "+SALT+" "+VARCHAR+", "+HASHED_PASS+" "+VARCHAR+", "+NAME+" "+VARCHAR+");").run(); |
||||
} |
||||
|
||||
private boolean tableExists(String tbName) throws SQLException { |
||||
try { |
||||
ResultSet rs = query("SELECT EXISTS (SELECT name FROM sqlite_schema WHERE type='table' AND name='" + tbName + "')").exec(); |
||||
int val = 0; |
||||
if (rs.next()) val = rs.getInt(1); |
||||
rs.close(); |
||||
return val > 0; |
||||
} catch (SQLException e) { |
||||
throw new SQLException(t("Was not able to check existence of table {}!",tbName),e); |
||||
} |
||||
} |
||||
|
||||
public Request query(String sql) { |
||||
return new Request(sql); |
||||
} |
||||
} |
@ -0,0 +1,80 @@
@@ -0,0 +1,80 @@
|
||||
package de.srsoftware.widerhall.data; |
||||
|
||||
import de.srsoftware.widerhall.Util; |
||||
|
||||
import java.security.InvalidKeyException; |
||||
import java.sql.ResultSet; |
||||
import java.sql.SQLException; |
||||
import java.time.LocalDate; |
||||
import java.util.Map; |
||||
|
||||
import static de.srsoftware.widerhall.data.Database.*; |
||||
|
||||
public class User { |
||||
private static Database database = Database.open(); |
||||
private String email, salt, hashedPass, name; |
||||
|
||||
public User(String email, String name, String salt, String hashedPass) { |
||||
this.email = email; |
||||
this.name = name; |
||||
this.salt = salt; |
||||
this.hashedPass = hashedPass; |
||||
} |
||||
|
||||
public static User create(String email, String name, String password) throws SQLException { |
||||
var salt = Util.sha256(email + name + LocalDate.now()); |
||||
var hashedPass = Util.sha256(password+salt); |
||||
return new User(email,name,salt,hashedPass).save(); |
||||
} |
||||
|
||||
public static User load(String email, String password) throws InvalidKeyException, SQLException { |
||||
ResultSet rs = database |
||||
.query("SELECT * FROM Users") |
||||
.where(EMAIL,email) |
||||
.exec(); |
||||
|
||||
try { |
||||
if (rs.next()) { |
||||
email = rs.getString(EMAIL); |
||||
var name = rs.getString(NAME); |
||||
var hashedPassword = rs.getString(HASHED_PASS); |
||||
var salt = rs.getString(SALT); |
||||
var loadedUser = new User(email, name, salt, hashedPassword); |
||||
if (loadedUser.matching(password)) return loadedUser; |
||||
} else if (noUsers()){ |
||||
return User.create(email,"Admin",password); |
||||
} |
||||
} finally { |
||||
rs.close(); |
||||
} |
||||
|
||||
throw new InvalidKeyException(); |
||||
} |
||||
|
||||
private static boolean noUsers() throws SQLException { |
||||
var rs = database.query("SELECT count(*) FROM users").exec(); |
||||
try { |
||||
if (rs.next()) { |
||||
return rs.getInt(1) < 1; |
||||
} |
||||
} finally { |
||||
rs.close(); |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
private boolean matching(String password) { |
||||
return hashedPass.equals(Util.sha256(password+salt)); |
||||
} |
||||
|
||||
private User save() throws SQLException { |
||||
database.insertInto("Users") |
||||
.values(Map.of(EMAIL,email,NAME,name,SALT,salt,HASHED_PASS,hashedPass)) |
||||
.run(); |
||||
return this; |
||||
} |
||||
|
||||
public Map<String,String> map() { |
||||
return Map.of(EMAIL,email,NAME,name); |
||||
} |
||||
} |
@ -1,43 +0,0 @@
@@ -1,43 +0,0 @@
|
||||
package de.srsoftware.widerhall.web; |
||||
|
||||
import de.srsoftware.tools.Tag; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
import javax.servlet.ServletException; |
||||
import javax.servlet.http.HttpServlet; |
||||
import javax.servlet.http.HttpServletRequest; |
||||
import javax.servlet.http.HttpServletResponse; |
||||
import java.io.IOException; |
||||
|
||||
public class Index extends HttpServlet { |
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Index.class); |
||||
|
||||
@Override |
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { |
||||
resp.setContentType("text/html"); |
||||
resp.setStatus(HttpServletResponse.SC_OK); |
||||
String auth = req.getHeader("Authorization"); |
||||
if (auth == null) { |
||||
resp.sendRedirect("login"); |
||||
return; |
||||
} |
||||
LOG.debug("Authorization: {}",auth); |
||||
|
||||
resp.getWriter().println(page(auth)); |
||||
|
||||
} |
||||
|
||||
private Tag head() { |
||||
return new Tag("meta") |
||||
.attr("charset","utf-8") |
||||
.addTo(new Tag("head")); |
||||
|
||||
} |
||||
|
||||
private Tag page(String auth) { |
||||
var body = new Tag("body").content(auth); |
||||
return body.addTo(head().addTo(new Tag("html"))); |
||||
} |
||||
} |
@ -1,127 +0,0 @@
@@ -1,127 +0,0 @@
|
||||
package de.srsoftware.widerhall.web; |
||||
|
||||
import de.srsoftware.widerhall.Configuration; |
||||
import de.srsoftware.widerhall.web.tags.Page; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
import javax.net.ssl.HttpsURLConnection; |
||||
import javax.servlet.http.HttpServlet; |
||||
import javax.servlet.http.HttpServletRequest; |
||||
import javax.servlet.http.HttpServletResponse; |
||||
import java.io.BufferedReader; |
||||
import java.io.DataOutputStream; |
||||
import java.io.IOException; |
||||
import java.io.InputStreamReader; |
||||
import java.net.URLEncoder; |
||||
import java.nio.charset.StandardCharsets; |
||||
import java.util.Map; |
||||
import java.util.stream.Collectors; |
||||
|
||||
public class Login extends HttpServlet { |
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Login.class); |
||||
private final Configuration config; |
||||
|
||||
public Login(){ |
||||
this.config = Configuration.instance(); |
||||
LOG.debug("Creating new instance of Login.class"); |
||||
} |
||||
|
||||
@Override |
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { |
||||
var error = req.getParameter("error"); |
||||
if (error != null){ |
||||
var description = req.getParameter("error_description"); |
||||
sendError(resp,error+": "+description); |
||||
return; |
||||
} |
||||
LOG.debug("params: {}",req.getParameterMap()); |
||||
var code = req.getParameter("code"); |
||||
if (code != null){ |
||||
getTokenFor(code,resp); |
||||
resp.getWriter().println(new Page("rceived code: "+code)); |
||||
return; |
||||
} |
||||
resp.sendRedirect(loginUrl()); |
||||
} |
||||
|
||||
private static String urlEncode(Map<String, Object> data) { |
||||
String params = data.entrySet() |
||||
.stream() |
||||
.map(entry -> encode(entry.getKey()) + "=" + encode(entry.getValue())) |
||||
.collect(Collectors.joining("&")); |
||||
return params; |
||||
} |
||||
|
||||
private static String encode(Object value) { |
||||
return URLEncoder.encode(value.toString(),StandardCharsets.UTF_8); |
||||
} |
||||
|
||||
private void getTokenFor(String code, HttpServletResponse resp) throws IOException { |
||||
var url = config.tokenUrl(); |
||||
LOG.debug("Sending 'POST' request to URL '{}'",url); |
||||
HttpsURLConnection httpClient = (HttpsURLConnection) url.openConnection(); |
||||
|
||||
//add reuqest header
|
||||
httpClient.setRequestMethod("POST"); |
||||
httpClient.setRequestProperty( "Content-Type", "application/x-www-form-urlencoded"); |
||||
httpClient.setRequestProperty( "Accept", "*/*" ); |
||||
//httpClient.setRequestProperty("User-Agent", "Mozilla/5.0");
|
||||
//httpClient.setRequestProperty("Accept-Language", "en-US,en;q=0.5");
|
||||
|
||||
String urlParameters = urlEncode(Map.of( |
||||
"code",code, |
||||
"client_id",config.clientId(), |
||||
"client_secret",config.clientSecret(), |
||||
"grant_type","authorization_code")); |
||||
|
||||
LOG.debug("Posting parameters '{}'",urlParameters); |
||||
|
||||
// Send post request
|
||||
httpClient.setDoOutput(true); |
||||
httpClient.setDoInput(true); |
||||
try (DataOutputStream wr = new DataOutputStream(httpClient.getOutputStream())) { |
||||
wr.writeBytes(urlParameters); |
||||
wr.flush(); |
||||
} |
||||
|
||||
int responseCode = httpClient.getResponseCode(); |
||||
LOG.debug("Response Code: {}",responseCode); |
||||
|
||||
try (BufferedReader in = new BufferedReader(new InputStreamReader(httpClient.getInputStream()))) { |
||||
|
||||
String line; |
||||
StringBuilder response = new StringBuilder(); |
||||
|
||||
while ((line = in.readLine()) != null) { |
||||
response.append(line); |
||||
} |
||||
|
||||
//print result
|
||||
//System.out.println(response.toString());
|
||||
resp.getWriter().println(response); |
||||
|
||||
} |
||||
} |
||||
|
||||
private void sendError(HttpServletResponse resp, String error) throws IOException { |
||||
LOG.debug("error: {}",error); |
||||
resp.sendError(HttpServletResponse.SC_BAD_REQUEST,error); |
||||
} |
||||
|
||||
private String loginUrl() { |
||||
return config.loginUrl()+"?"+urlEncode(Map.of( |
||||
"response_type","code", |
||||
"client_id",config.clientId(), |
||||
"state",123456, |
||||
"redirect_uri",redirectUri(), |
||||
"scope","openid" |
||||
)); |
||||
} |
||||
|
||||
private String redirectUri() { |
||||
int port = config.serverPort(); |
||||
return config.baseUrl()+(port == 80 ? "" : ":"+port)+"/login"; |
||||
} |
||||
} |
@ -0,0 +1,138 @@
@@ -0,0 +1,138 @@
|
||||
package de.srsoftware.widerhall.web; |
||||
|
||||
import de.srsoftware.widerhall.Configuration; |
||||
import de.srsoftware.widerhall.Util; |
||||
import de.srsoftware.widerhall.data.User; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.stringtemplate.v4.STGroup; |
||||
import org.stringtemplate.v4.STGroupDir; |
||||
|
||||
import javax.servlet.ServletException; |
||||
import javax.servlet.http.HttpServlet; |
||||
import javax.servlet.http.HttpServletRequest; |
||||
import javax.servlet.http.HttpServletResponse; |
||||
import java.io.File; |
||||
import java.io.IOException; |
||||
import java.nio.file.Files; |
||||
import java.util.Map; |
||||
|
||||
import static de.srsoftware.widerhall.Util.t; |
||||
|
||||
public class Static extends HttpServlet { |
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Static.class); |
||||
private final String baseDir; |
||||
private final STGroup templates; |
||||
|
||||
public Static(){ |
||||
var config = Configuration.instance(); |
||||
baseDir = config.baseDir(); |
||||
var templateDir = String.join(File.separator,baseDir,"templates"); |
||||
templates = new STGroupDir(templateDir,'{','}'); |
||||
} |
||||
|
||||
@Override |
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { |
||||
String error = handleGet(req, resp); |
||||
if (error != null) resp.sendError(400,error); |
||||
} |
||||
|
||||
private String handleGet(HttpServletRequest req, HttpServletResponse resp) { |
||||
var path = req.getPathInfo(); |
||||
path = path == null ? "index" : path.substring(1); |
||||
switch (path){ |
||||
case "css": |
||||
return loadFile("style.css",resp); |
||||
case "js": |
||||
return loadFile("widerhall.js",resp); |
||||
case "jquery": |
||||
return loadFile("jquery-3.6.0.min.js",resp); |
||||
case "login": |
||||
return loadTemplate(path, null, resp); |
||||
} |
||||
|
||||
var u = req.getSession().getAttribute("user"); |
||||
if (u instanceof User user){ |
||||
Map<String,Object> data = Map.of("user",user.map()); |
||||
return loadTemplate(path,data,resp); |
||||
} |
||||
return loginRedirect(resp); |
||||
} |
||||
|
||||
private String loadTemplate(String path, Map<String, ? extends Object> data, HttpServletResponse resp) { |
||||
var template = templates.getInstanceOf(path); |
||||
if (template != null){ |
||||
try { |
||||
template.add("data",data); |
||||
resp.getWriter().println(template.render()); |
||||
return null; |
||||
} catch (IOException e) { |
||||
return t("Failed to load template '{}'",path); |
||||
} |
||||
} |
||||
return t("No template for path {}!",path); |
||||
} |
||||
|
||||
|
||||
private String loginRedirect(HttpServletResponse resp) { |
||||
try { |
||||
resp.sendRedirect("/static/login"); |
||||
return null; |
||||
} catch (IOException e) { |
||||
return t("Was not able to redirect to login page: {}", e.getMessage()); |
||||
} |
||||
} |
||||
|
||||
private String loadFile(String filename, HttpServletResponse resp) { |
||||
var path = String.join(File.separator,baseDir,filename); |
||||
LOG.debug("loading {}",path); |
||||
var file = new File(path); |
||||
if (!file.exists()) return t("File {} does not exist!",filename); |
||||
try { |
||||
var content = Files.readString(file.toPath()); |
||||
resp.getWriter().println(content); |
||||
} catch (IOException e) { |
||||
return t("Failed to load file '{}'!",filename); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { |
||||
String error = handlePost(req, resp); |
||||
if (error != null) resp.sendError(400,error); |
||||
} |
||||
|
||||
private String handlePost(HttpServletRequest req, HttpServletResponse resp) { |
||||
var path = req.getPathInfo(); |
||||
if (path == null) path = "/"; |
||||
switch (path){ |
||||
case "/login": |
||||
return handleLogin(req,resp); |
||||
} |
||||
|
||||
return t("No handler for path {}!",path); |
||||
} |
||||
|
||||
private String handleLogin(HttpServletRequest req, HttpServletResponse resp) { |
||||
var email = req.getParameter("email"); |
||||
var pass = req.getParameter("pass"); |
||||
if (email == null || pass == null) return loginRedirect(resp); |
||||
if (!Util.isEmail(email)) return loadTemplate("login", Map.of("error",t("'{}' is not a valid email address!",email)), resp); |
||||
try { |
||||
var user = User.load(email,pass); |
||||
req.getSession().setAttribute("user",user); |
||||
resp.sendRedirect("/static"); |
||||
} catch (Exception e) { |
||||
try { |
||||
LOG.warn("Static.handleLogin failed:",e); |
||||
Thread.sleep(10000); |
||||
} finally { |
||||
return loadTemplate("login", Map.of("error",t("Invalid username/password")), resp); |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
} |
@ -1,10 +0,0 @@
@@ -1,10 +0,0 @@
|
||||
package de.srsoftware.widerhall.web.tags; |
||||
|
||||
import de.srsoftware.tools.Tag; |
||||
|
||||
public class Header extends Tag { |
||||
public Header() { |
||||
super("head"); |
||||
new Tag("meta").attr("charset","utf-8").addTo(this); |
||||
} |
||||
} |
@ -1,11 +0,0 @@
@@ -1,11 +0,0 @@
|
||||
package de.srsoftware.widerhall.web.tags; |
||||
|
||||
import de.srsoftware.tools.Tag; |
||||
|
||||
public class Page extends Tag { |
||||
public Page(String content) { |
||||
super("html"); |
||||
new Header().addTo(this); |
||||
new Tag("body").content(content).addTo(this); |
||||
} |
||||
} |
@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
|
||||
label { |
||||
display: block; |
||||
margin: 5px 0; |
||||
} |
||||
|
||||
#login form { |
||||
width: 450px; |
||||
margin: 0 auto; |
||||
} |
||||
|
||||
h1 { |
||||
text-align: center; |
||||
} |
||||
|
||||
.error{ |
||||
display: block; |
||||
text-align: center; |
||||
background: orange; |
||||
} |
@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
|
||||
index(data) ::= << |
||||
<html> |
||||
<head> |
||||
<meta charset="utf-8" /> |
||||
<script src="static/jquery"></script> |
||||
<script src="static/js"></script> |
||||
</head> |
||||
<body> |
||||
<span class="user">Logged in as <em>{data.user.name}</em></span> |
||||
<h2>Users</h2> |
||||
<h2>Lists</h2> |
||||
</body> |
||||
</html> |
||||
>> |
@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
|
||||
login(data) ::= << |
||||
<html> |
||||
<head> |
||||
<meta charset="utf-8" /> |
||||
<script src="/static/jquery"></script> |
||||
<script src="/static/js"></script> |
||||
<link rel="stylesheet" href="/static/css" /> |
||||
</head> |
||||
<body id="login"> |
||||
<h1>Widerhall login</h1> |
||||
{if(data.error)} |
||||
<span class="error">{data.error}</span> |
||||
{endif} |
||||
<form method="POST"> |
||||
<fieldset> |
||||
<legend>Login-Daten</legend> |
||||
<label> |
||||
<input type="text" name="email" value="" id="email" /> |
||||
E-Mail-Adresse |
||||
</label> |
||||
<label> |
||||
<input type="password" name="pass" value="" id="password" /> |
||||
Passwort |
||||
</label> |
||||
<button type="submit">Einloggen</button> |
||||
</fieldset> |
||||
</form> |
||||
</body> |
||||
</html> |
||||
>> |
Loading…
Reference in new issue