From 0b13726d257a168f8e600b751dc81bea20e4ca4d Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Thu, 14 Apr 2022 20:22:35 +0200 Subject: [PATCH] working on string-templated based static pages --- doc/data structure.dia | 1519 +++++++++++++++++ pom.xml | 22 +- .../de/srsoftware/widerhall/Application.java | 18 +- .../srsoftware/widerhall/Configuration.java | 15 +- .../de/srsoftware/widerhall/Constants.java | 2 + .../java/de/srsoftware/widerhall/Util.java | 65 + .../srsoftware/widerhall/data/Database.java | 167 ++ .../de/srsoftware/widerhall/data/User.java | 80 + .../de/srsoftware/widerhall/web/Index.java | 43 - .../de/srsoftware/widerhall/web/Login.java | 127 -- .../de/srsoftware/widerhall/web/Static.java | 138 ++ .../srsoftware/widerhall/web/tags/Header.java | 10 - .../srsoftware/widerhall/web/tags/Page.java | 11 - static/style.css | 19 + static/templates/index.st | 14 + static/templates/login.st | 30 + static/widerhall.js | 1 + 17 files changed, 2074 insertions(+), 207 deletions(-) create mode 100644 doc/data structure.dia create mode 100644 src/main/java/de/srsoftware/widerhall/Util.java create mode 100644 src/main/java/de/srsoftware/widerhall/data/Database.java create mode 100644 src/main/java/de/srsoftware/widerhall/data/User.java delete mode 100644 src/main/java/de/srsoftware/widerhall/web/Index.java delete mode 100644 src/main/java/de/srsoftware/widerhall/web/Login.java create mode 100644 src/main/java/de/srsoftware/widerhall/web/Static.java delete mode 100644 src/main/java/de/srsoftware/widerhall/web/tags/Header.java delete mode 100644 src/main/java/de/srsoftware/widerhall/web/tags/Page.java create mode 100644 static/style.css create mode 100644 static/templates/index.st create mode 100644 static/templates/login.st create mode 100644 static/widerhall.js diff --git a/doc/data structure.dia b/doc/data structure.dia new file mode 100644 index 0000000..bff630a --- /dev/null +++ b/doc/data structure.dia @@ -0,0 +1,1519 @@ + + + + + + + + + + + + + #A4# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #Lists# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #Name# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #Email# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #Users# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #Email# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #HashedPassword# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #Salt# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #ListMembers# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #ListEmail# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #UserEmail# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #Name# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #State# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #imap_host# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #imap_port# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #imap_user# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #imap_pass# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #smtp_host# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #smtp_port# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #smtp_user# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #smtp_pass# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #Posts# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #ListEmail# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #From# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #Id# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #Parent# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #Subject# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #Date# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #Content# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index 659ab99..cc2ad6f 100644 --- a/pom.xml +++ b/pom.xml @@ -39,10 +39,16 @@ 10.0.9 + + org.eclipse.jetty + jetty-util + 10.0.9 + + de.srsoftware - tools - 1.1.18 + tools.translations + 1.1.3 @@ -70,6 +76,18 @@ 1.1.1 + + org.xerial + sqlite-jdbc + 3.36.0.3 + + + + org.antlr + stringtemplate + 4.0.2 + + \ No newline at end of file diff --git a/src/main/java/de/srsoftware/widerhall/Application.java b/src/main/java/de/srsoftware/widerhall/Application.java index e531bcf..a65c605 100644 --- a/src/main/java/de/srsoftware/widerhall/Application.java +++ b/src/main/java/de/srsoftware/widerhall/Application.java @@ -3,23 +3,16 @@ package de.srsoftware.widerhall; import de.srsoftware.widerhall.mail.Forwarder; import de.srsoftware.widerhall.mail.ImapClient; import de.srsoftware.widerhall.mail.MessageHandler; -import de.srsoftware.widerhall.web.Index; -import de.srsoftware.widerhall.web.Login; +import de.srsoftware.widerhall.web.Static; import de.srsoftware.widerhall.web.Rest; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.session.SessionHandler; import org.eclipse.jetty.servlet.ServletContextHandler; import org.json.simple.JSONObject; -import org.json.simple.parser.JSONParser; -import org.json.simple.parser.ParseException; import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Map; -import static de.srsoftware.widerhall.Constants.*; public class Application { @@ -33,11 +26,12 @@ public class Application { var server = new Server(); var connector = new ServerConnector(server); connector.setPort(config.serverPort()); + + SessionHandler sh = new SessionHandler(); server.setConnectors(new Connector[]{connector}); - ServletContextHandler context = new ServletContextHandler(server, "/"); + ServletContextHandler context = new ServletContextHandler(server, "/",sh,null,null,null); context.addServlet(Rest.class,"/api"); - context.addServlet(Login.class,"/login"); - context.addServlet(Index.class,"/"); + context.addServlet(Static.class,"/static/*"); server.start(); } diff --git a/src/main/java/de/srsoftware/widerhall/Configuration.java b/src/main/java/de/srsoftware/widerhall/Configuration.java index 25a19da..6da7154 100644 --- a/src/main/java/de/srsoftware/widerhall/Configuration.java +++ b/src/main/java/de/srsoftware/widerhall/Configuration.java @@ -36,6 +36,7 @@ public class Configuration { private void setDefaults() throws MalformedURLException { if (data == null) data = new JSONObject(); + baseDir(); serverPort(); tokenUrl(); loginUrl(); @@ -70,12 +71,22 @@ public class Configuration { } public String clientId() { - if (!data.containsKey(Constants.CLIENT_ID)) data.put(CLIENT_ID,"widerhall"); + if (!data.containsKey(CLIENT_ID)) data.put(CLIENT_ID,"widerhall"); return (String) data.get(CLIENT_ID); } public Object clientSecret() { - if (!data.containsKey(Constants.CLIENT_SECRET)) data.put(CLIENT_SECRET,"changeme"); + if (!data.containsKey(CLIENT_SECRET)) data.put(CLIENT_SECRET,"changeme"); return (String) data.get(CLIENT_SECRET); } + + public String dbLocation() { + if (!data.containsKey(DB_FILE)) data.put(DB_FILE,System.getProperty("user.home")+"/.config/widerhall/db.sqlite3"); + return (String) data.get(DB_FILE); + } + + public String baseDir() { + if (!data.containsKey(Constants.BASE_DIR)) data.put(BASE_DIR,System.getProperty("user.dir")+"/static"); + return (String) data.get(BASE_DIR); + } } diff --git a/src/main/java/de/srsoftware/widerhall/Constants.java b/src/main/java/de/srsoftware/widerhall/Constants.java index 504771a..520c846 100644 --- a/src/main/java/de/srsoftware/widerhall/Constants.java +++ b/src/main/java/de/srsoftware/widerhall/Constants.java @@ -13,4 +13,6 @@ public class Constants { public static final String BASE_URL = "base_url"; public static final String CLIENT_ID = "client_id"; public static final String CLIENT_SECRET = "client_secret"; + public static final String DB_FILE = "db_file"; + public static final String BASE_DIR = "base_dir"; } diff --git a/src/main/java/de/srsoftware/widerhall/Util.java b/src/main/java/de/srsoftware/widerhall/Util.java new file mode 100644 index 0000000..94fe18a --- /dev/null +++ b/src/main/java/de/srsoftware/widerhall/Util.java @@ -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 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); + } +} diff --git a/src/main/java/de/srsoftware/widerhall/data/Database.java b/src/main/java/de/srsoftware/widerhall/data/Database.java new file mode 100644 index 0000000..7ed26e3 --- /dev/null +++ b/src/main/java/de/srsoftware/widerhall/data/Database.java @@ -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> where = new HashMap<>(); + private HashMap 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()); + list.add(value); + return this; + } + + public Request values(Map 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(); + if (!values.isEmpty()){ + var keys = new ArrayList(); + 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(); + if (!where.isEmpty()){ + var clauses = new ArrayList(); + 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); + } +} diff --git a/src/main/java/de/srsoftware/widerhall/data/User.java b/src/main/java/de/srsoftware/widerhall/data/User.java new file mode 100644 index 0000000..3e0345c --- /dev/null +++ b/src/main/java/de/srsoftware/widerhall/data/User.java @@ -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 map() { + return Map.of(EMAIL,email,NAME,name); + } +} diff --git a/src/main/java/de/srsoftware/widerhall/web/Index.java b/src/main/java/de/srsoftware/widerhall/web/Index.java deleted file mode 100644 index 7a76cda..0000000 --- a/src/main/java/de/srsoftware/widerhall/web/Index.java +++ /dev/null @@ -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"))); - } -} diff --git a/src/main/java/de/srsoftware/widerhall/web/Login.java b/src/main/java/de/srsoftware/widerhall/web/Login.java deleted file mode 100644 index eb82a73..0000000 --- a/src/main/java/de/srsoftware/widerhall/web/Login.java +++ /dev/null @@ -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 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"; - } -} diff --git a/src/main/java/de/srsoftware/widerhall/web/Static.java b/src/main/java/de/srsoftware/widerhall/web/Static.java new file mode 100644 index 0000000..6b4e872 --- /dev/null +++ b/src/main/java/de/srsoftware/widerhall/web/Static.java @@ -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 data = Map.of("user",user.map()); + return loadTemplate(path,data,resp); + } + return loginRedirect(resp); + } + + private String loadTemplate(String path, Map 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; + } + +} diff --git a/src/main/java/de/srsoftware/widerhall/web/tags/Header.java b/src/main/java/de/srsoftware/widerhall/web/tags/Header.java deleted file mode 100644 index a302815..0000000 --- a/src/main/java/de/srsoftware/widerhall/web/tags/Header.java +++ /dev/null @@ -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); - } -} diff --git a/src/main/java/de/srsoftware/widerhall/web/tags/Page.java b/src/main/java/de/srsoftware/widerhall/web/tags/Page.java deleted file mode 100644 index 4a079ac..0000000 --- a/src/main/java/de/srsoftware/widerhall/web/tags/Page.java +++ /dev/null @@ -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); - } -} diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..763451e --- /dev/null +++ b/static/style.css @@ -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; +} \ No newline at end of file diff --git a/static/templates/index.st b/static/templates/index.st new file mode 100644 index 0000000..be3a403 --- /dev/null +++ b/static/templates/index.st @@ -0,0 +1,14 @@ +index(data) ::= << + + + + + + + + Logged in as {data.user.name} +

Users

+

Lists

+ + +>> \ No newline at end of file diff --git a/static/templates/login.st b/static/templates/login.st new file mode 100644 index 0000000..a9fa1d0 --- /dev/null +++ b/static/templates/login.st @@ -0,0 +1,30 @@ +login(data) ::= << + + + + + + + + +

Widerhall login

+ {if(data.error)} + {data.error} + {endif} +
+
+ Login-Daten + + + +
+
+ + +>> \ No newline at end of file diff --git a/static/widerhall.js b/static/widerhall.js new file mode 100644 index 0000000..6d38746 --- /dev/null +++ b/static/widerhall.js @@ -0,0 +1 @@ +$(start); // document.on ready \ No newline at end of file