diff --git a/.gitignore b/.gitignore index 07051a7..ff424ad 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ target config/config.json *.sqlite3 +jquery-3.6.0.min.js \ 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 f56546a..b85bda8 100644 --- a/src/main/java/de/srsoftware/widerhall/Application.java +++ b/src/main/java/de/srsoftware/widerhall/Application.java @@ -35,7 +35,7 @@ public class Application { SessionHandler sh = new SessionHandler(); server.setConnectors(new Connector[]{connector}); ServletContextHandler context = new ServletContextHandler(server, "/",sh,null,null,null); - context.addServlet(Rest.class,"/api"); + context.addServlet(Rest.class,"/api/*"); context.addServlet(Web.class,"/web/*"); server.start(); diff --git a/src/main/java/de/srsoftware/widerhall/Constants.java b/src/main/java/de/srsoftware/widerhall/Constants.java index d220f9c..c78c05d 100644 --- a/src/main/java/de/srsoftware/widerhall/Constants.java +++ b/src/main/java/de/srsoftware/widerhall/Constants.java @@ -1,18 +1,21 @@ package de.srsoftware.widerhall; public class Constants { + public static final String ADMIN = "Admin"; + public static final String EMAIL = "email"; + public static final String ERROR = "error"; + public static final String HOST = "host"; public static final String IMAPS = "imaps"; + public static final String INBOX = "inbox"; + public static final String INDEX = "index"; + public static final String NAME = "name"; + public static final String NOTES = "notes"; + public static final String PASSWORD = "password"; public static final String PROTOCOL = "mail.store.protocol"; - public static final String HOST = "host"; public static final String USER = "user"; - public static final String PASSWORD = "password"; - public static final String INBOX = "inbox"; + public static final Object PORT = "port"; - public static final String TOKEN_URL = "token_url"; - public static final String LOGIN_URL = "login_url"; 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 = "database"; public static final String BASE = "base"; public static final String CONFIG = "configuration"; diff --git a/src/main/java/de/srsoftware/widerhall/Util.java b/src/main/java/de/srsoftware/widerhall/Util.java index 94fe18a..a962d32 100644 --- a/src/main/java/de/srsoftware/widerhall/Util.java +++ b/src/main/java/de/srsoftware/widerhall/Util.java @@ -62,4 +62,26 @@ public class Util { public static boolean isEmail(String email) { return email.matches(EMAIL_PATTERN); } + + public static boolean simplePassword(String pass) { + if (pass.length() < 6) return true; + if (pass.length() < 8){ + for (int i=0; i list() { + var userList = new ArrayList(); + try { + var rs = database.query("SELECT * FROM Users").exec(); + while (rs.next()){ + var email = rs.getString(EMAIL); + var name = rs.getString(NAME); + var salt = rs.getString(SALT); + var hashedPassword = rs.getString(HASHED_PASS); + userList.add(new User(email,name,salt,hashedPassword)); + } + } catch (SQLException e) { + LOG.warn("Error loading user list!",e); + } + return userList; + } + + public Map map() { + return Map.of(EMAIL,email,NAME,name); + } + + private boolean matching(String password) { + return hashedPass.equals(Util.sha256(password+salt)); + } + + public String name() { + return name; + } + + public static boolean noUsers() throws SQLException { var rs = database.query("SELECT count(*) FROM users").exec(); try { if (rs.next()) { @@ -63,10 +113,6 @@ public class User { 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)) @@ -74,7 +120,4 @@ public class User { return this; } - public Map map() { - return Map.of(EMAIL,email,NAME,name); - } } diff --git a/src/main/java/de/srsoftware/widerhall/web/Rest.java b/src/main/java/de/srsoftware/widerhall/web/Rest.java index 1b82471..5c68b6d 100644 --- a/src/main/java/de/srsoftware/widerhall/web/Rest.java +++ b/src/main/java/de/srsoftware/widerhall/web/Rest.java @@ -1,5 +1,7 @@ package de.srsoftware.widerhall.web; +import de.srsoftware.widerhall.data.User; +import org.json.simple.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -8,14 +10,47 @@ import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Map; -public class Rest extends HttpServlet { +import static de.srsoftware.widerhall.Constants.*; +import static de.srsoftware.widerhall.Util.t; +public class Rest extends HttpServlet { private static final Logger LOG = LoggerFactory.getLogger(Rest.class); + private static final String USER_LIST = "user/list"; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - String method = req.getMethod(); - LOG.debug("GET {}"+method); + String error = handleGet(req, resp); + if (error != null) resp.sendError(400,error); + } + + public String handleGet(HttpServletRequest req, HttpServletResponse resp){ + Object o = req.getSession().getAttribute(USER); + JSONObject json = new JSONObject(); + if (o instanceof User user){ + var path = req.getPathInfo(); + json.put(USER,safeMapUser(user)); + path = path == null ? INDEX : path.substring(1); + switch (path) { + case USER_LIST: + json.put("users", (user.is(ADMIN) ? User.list() : List.of(user)).stream().map(Rest::safeMapUser).toList()); + break; + + } + } else { + json.put(ERROR,"Not logged in!"); + } + try { + resp.getWriter().println(json.toJSONString()); + return null; + } catch (IOException e) { + return t("Failed to handle request: {}",e.getMessage()); + } + } + private static Map safeMapUser(User user){ + return Map.of(NAME,user.name(),EMAIL,user.email(),PASSWORD,user.hashedPassword() == null ? "no" : "yes"); } } diff --git a/src/main/java/de/srsoftware/widerhall/web/Web.java b/src/main/java/de/srsoftware/widerhall/web/Web.java index 444dcd3..e84bc8f 100644 --- a/src/main/java/de/srsoftware/widerhall/web/Web.java +++ b/src/main/java/de/srsoftware/widerhall/web/Web.java @@ -15,23 +15,27 @@ import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.sql.SQLException; +import java.util.HashMap; import java.util.Map; +import static de.srsoftware.widerhall.Constants.*; import static de.srsoftware.widerhall.Util.t; public class Web extends HttpServlet { private static final Logger LOG = LoggerFactory.getLogger(Web.class); private static final String LOGIN = "login"; + private static final String LOGOUT = "logout"; + private static final String REGISTER = "register"; private final String baseDir; - private final STGroup templates; + private STGroup templates; private static final String WEB_ROOT = "/web"; public Web(){ var config = Configuration.instance(); baseDir = config.baseDir(); - var templateDir = String.join(File.separator,baseDir,"static","templates"); - templates = new STRawGroupDir(templateDir,'«','»'); + loadTemplates(); } @Override @@ -42,22 +46,44 @@ public class Web extends HttpServlet { private String handleGet(HttpServletRequest req, HttpServletResponse resp) { var path = req.getPathInfo(); - path = path == null ? "index" : path.substring(1); + path = path == null ? INDEX : path.substring(1); + String notes = null; switch (path){ + case "reload": + loadTemplates(); + path = INDEX; + notes = t("Templates have been reloaded"); + break; case "css": case "js": - case "login": return loadTemplate(path,null,resp); + case LOGIN: + try { + if (User.noUsers()) return loadTemplate(REGISTER, Map.of(NOTES,t("User database is empty. Create admin user first:")), resp); + return loadTemplate(path,null,resp); + } catch (SQLException throwables) { + return "Error reading user database!"; + } + case LOGOUT: + req.getSession().invalidate(); + return redirectTo(INDEX,resp); case "jquery": return loadFile("jquery-3.6.0.min.js",resp); } - var u = req.getSession().getAttribute("user"); - if (u instanceof User user){ - Map data = Map.of("user",user.map()); + var o = req.getSession().getAttribute("user"); + if (o instanceof User user){ + var data = new HashMap(); + data.put(USER,user.map()); + data.put(NOTES,notes); return loadTemplate(path,data,resp); } - return loginRedirect(resp); + return redirectTo(LOGIN,resp); + } + + private void loadTemplates() { + var templateDir = String.join(File.separator,baseDir,"static","templates"); + templates = new STRawGroupDir(templateDir,'«','»'); } private String loadTemplate(String path, Map data, HttpServletResponse resp) { @@ -75,17 +101,17 @@ public class Web extends HttpServlet { } - private String loginRedirect(HttpServletResponse resp) { + private String redirectTo(String page, HttpServletResponse resp) { try { - resp.sendRedirect(String.join("/",WEB_ROOT,LOGIN)); + resp.sendRedirect(String.join("/",WEB_ROOT,page)); return null; } catch (IOException e) { - return t("Was not able to redirect to login page: {}", e.getMessage()); + return t("Was not able to redirect to {} page: {}", page, e.getMessage()); } } private String loadFile(String filename, HttpServletResponse resp) { - var path = String.join(File.separator,baseDir,filename); + var path = String.join(File.separator,baseDir,"static",filename); LOG.debug("loading {}",path); var file = new File(path); if (!file.exists()) return t("File {} does not exist!",filename); @@ -106,19 +132,55 @@ public class Web extends HttpServlet { private String handlePost(HttpServletRequest req, HttpServletResponse resp) { var path = req.getPathInfo(); - if (path == null) path = "/"; + path = path == null ? INDEX : path.substring(1); switch (path){ - case "/login": + case LOGIN: return handleLogin(req,resp); + case REGISTER: + return registerUser(req,resp); } return t("No handler for path {}!",path); } + private String registerUser(HttpServletRequest req, HttpServletResponse resp) { + + var email = req.getParameter("email"); + var pass = req.getParameter("pass"); + var pass_repeat = req.getParameter("pass_repeat"); + var name = req.getParameter("name"); + + if (email == null || email.isBlank() || + name == null || name.isBlank() || + pass == null || pass.isBlank() || + pass_repeat == null || pass_repeat.isBlank()) return loadTemplate(REGISTER,Map.of(ERROR,"Fill all fields, please!",NAME,name,EMAIL,email),resp); + if (!pass.equals(pass_repeat)) return loadTemplate(REGISTER,Map.of(ERROR,"Passwords do not match!",NAME,name,EMAIL,email),resp); + if (Util.simplePassword(pass)) return loadTemplate(REGISTER,Map.of(ERROR,"Password to short or to simple!",NAME,name,EMAIL,email),resp); + + try { + if (User.noUsers()) { // we are registering the first user, which is forced to be „Admin“ + name = ADMIN; + } else { + if (ADMIN.equals(name)) return loadTemplate(REGISTER,Map.of(ERROR,t("Name must not be „{}“",ADMIN),NAME,name,EMAIL,email),resp); + } + } catch (SQLException e) { + return t("Failed to access user database: {}",e.getMessage()); + } + + + try { + var user = User.create(email, name, pass); + req.getSession().setAttribute("user",user); + return redirectTo(INDEX,resp); + } catch (SQLException e) { + return t("Failed to create new user: {}",e.getMessage()); + } + } + 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 (email == null || pass == null) return loadTemplate("login", Map.of("error",t("Missing username or password!")), 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); diff --git a/static/templates/css.st b/static/templates/css.st index 0ab22cd..0a28940 100644 --- a/static/templates/css.st +++ b/static/templates/css.st @@ -18,6 +18,13 @@ h1 { background: orange; } +.notes{ + display: block; + text-align: center; + background: yellow; +} + .user{ background: lime; + float: right; } \ No newline at end of file diff --git a/static/templates/index.st b/static/templates/index.st index 4a42af9..d6f570f 100644 --- a/static/templates/index.st +++ b/static/templates/index.st @@ -1,3 +1,4 @@ + @@ -6,8 +7,9 @@ - Logged in as «data.user.name» -

Users

-

Lists

+ «userinfo()» + «messages()» + «userlist()» + «listlist()» \ No newline at end of file diff --git a/static/templates/js.st b/static/templates/js.st index 6d38746..4c457a4 100644 --- a/static/templates/js.st +++ b/static/templates/js.st @@ -1 +1,25 @@ +function loadUserList(){ + $.getJSON("/api/user/list", showUserList); +} + +function showUserList(data){ + for (let i in data.users){ + let user = data.users[i]; + let row = $(''); + $('').text(user.name).appendTo(row); + $('').text(user.email).appendTo(row); + $('').text(user.password).appendTo(row); + row.appendTo('#userlist'); + } + if (data.user.name == 'Admin'){ + $('a[href=register]').show(); + } else { + $('a[href=register]').hide(); + } +} + +function start(){ + console.log("application started"); +} + $(start); // document.on ready \ No newline at end of file diff --git a/static/templates/listlist.st b/static/templates/listlist.st new file mode 100644 index 0000000..530910f --- /dev/null +++ b/static/templates/listlist.st @@ -0,0 +1,10 @@ +
+ List of mailinglists + + + + + + +
List nameList emailDetails
+
\ No newline at end of file diff --git a/static/templates/login.st b/static/templates/login.st index 01d1a5f..d69542f 100644 --- a/static/templates/login.st +++ b/static/templates/login.st @@ -1,3 +1,4 @@ + @@ -7,9 +8,7 @@

Widerhall login

- «if(data.error)» - «data.error» - «endif» + «messages()»
Login-Daten diff --git a/static/templates/messages.st b/static/templates/messages.st new file mode 100644 index 0000000..21f0dea --- /dev/null +++ b/static/templates/messages.st @@ -0,0 +1,6 @@ +«if(data.error)» + «data.error» +«endif» +«if(data.notes)» + «data.notes» +«endif» diff --git a/static/templates/register.st b/static/templates/register.st new file mode 100644 index 0000000..55caea3 --- /dev/null +++ b/static/templates/register.st @@ -0,0 +1,36 @@ + + + + + + + + + + «userinfo()» +

Widerhall user registration

+ «messages()» + +
+ User credentials + + + + + +
+ + + \ No newline at end of file diff --git a/static/templates/userinfo.st b/static/templates/userinfo.st new file mode 100644 index 0000000..b25c1f6 --- /dev/null +++ b/static/templates/userinfo.st @@ -0,0 +1,7 @@ +«if(data.user)» +
+ Logged in as «data.user.name» + Reload templates + Logout +
+«endif» \ No newline at end of file diff --git a/static/templates/userlist.st b/static/templates/userlist.st new file mode 100644 index 0000000..2e5c77c --- /dev/null +++ b/static/templates/userlist.st @@ -0,0 +1,14 @@ +
+ User list + + + + + + +
User nameEmailPassword
+ Register new user + +
\ No newline at end of file