diff --git a/core/src/main/java/de/srsoftware/umbrella/core/BaseHandler.java b/core/src/main/java/de/srsoftware/umbrella/core/BaseHandler.java index d1d3b39..3e18abb 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/BaseHandler.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/BaseHandler.java @@ -15,7 +15,7 @@ import java.util.List; public abstract class BaseHandler extends PathHandler { - private static HttpExchange addCors(HttpExchange ex){ + public HttpExchange addCors(HttpExchange ex){ var headers = ex.getRequestHeaders(); var origin = nullable(headers.get("Origin")).orElse(List.of()).stream().filter(url -> url.contains("://localhost")||url.contains("://127.0.0.1")).findAny(); if (origin.isPresent()) { @@ -25,7 +25,7 @@ public abstract class BaseHandler extends PathHandler { headers.add("Access-Control-Allow-Origin", url); headers.add("Access-Control-Allow-Headers", "Content-Type"); headers.add("Access-Control-Allow-Credentials", "true"); - headers.add("Access-Control-Allow-Methods","GET, POST, PATCH"); + headers.add("Access-Control-Allow-Methods","DELETE, GET, POST, PATCH"); } return ex; } diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index f32a149..0b5ce71 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -8,6 +8,7 @@ import Footer from "./Components/Footer.svelte"; import Login from "./Components/Login.svelte"; import Menu from "./Components/Menu.svelte"; + import Search from "./routes/search/Search.svelte"; import User from "./routes/user/User.svelte"; import UserEdit from "./routes/user/Edit.svelte"; @@ -31,10 +32,11 @@ {#if user.name } - - - - + + + + +

Page not found

diff --git a/frontend/src/routes/search/Search.svelte b/frontend/src/routes/search/Search.svelte new file mode 100644 index 0000000..c53379d --- /dev/null +++ b/frontend/src/routes/search/Search.svelte @@ -0,0 +1,44 @@ + + + +{#if html} +
+ + {t('search.results')} + +{@html html} +
+{/if} \ No newline at end of file diff --git a/legacy/src/main/java/de/srsoftware/umbrella/legacy/LegacyApi.java b/legacy/src/main/java/de/srsoftware/umbrella/legacy/LegacyApi.java index d912dc6..4029eca 100644 --- a/legacy/src/main/java/de/srsoftware/umbrella/legacy/LegacyApi.java +++ b/legacy/src/main/java/de/srsoftware/umbrella/legacy/LegacyApi.java @@ -55,11 +55,11 @@ public class LegacyApi extends BaseHandler { allowOrigin(ex, "*"); // add CORS header yield load(path,ex); } - case LOGIN -> getLegacyLogin(ex); + case LOGIN -> getLogin(ex); case LOGOUT-> logout(ex); case SEARCH -> { try { - yield legacySearch(ex); + yield search(ex); } catch (UmbrellaException e){ yield send(ex,e); } @@ -68,54 +68,21 @@ public class LegacyApi extends BaseHandler { }; } - private boolean legacySearch(HttpExchange ex) throws IOException, UmbrellaException { - var optToken = SessionToken.from(ex).map(Token::of); - if (optToken.isEmpty()) return sendEmptyResponse(HTTP_UNAUTHORIZED,ex); - var token = optToken.get(); - var params = queryParam(ex); - var key = params.get(KEY) instanceof String s ? s : null; - if (key == null) throw new UmbrellaException(HTTP_BAD_REQUEST,"Missing search key"); - var fulltext = key.contains("+") || "on".equals(params.get("fulltext")); - StringBuilder searchResult = new StringBuilder(); - if (fulltext){ - - for (var module : config.keys()){ - var baseUrl = config.get(module + ".baseUrl"); - if (baseUrl.isEmpty()) continue; - - var res = request(baseUrl.get()+"/search",token.asMap().plus(KEY,key),MIME_FORM_URL,token.asBearer()); - if (!(res instanceof String content) || content.isBlank()) continue; - searchResult.append("
"); - searchResult.append(content).append("
\n"); - } - } else { - var bookmark = config.get("bookmark.baseUrl"); - if (bookmark.isEmpty()) throw new UmbrellaException(500,"Tag search not available: Bookmark module not configured!"); - var res = request(bookmark.get()+"/search",token.asMap().plus(KEY,key),MIME_FORM_URL,null); - if (!(res instanceof String content)) throw new UmbrellaException(500,"Search did not return html content!"); - searchResult.append(content); - } - return sendContent(ex,searchResult.toString()); - }; - - private boolean logout(HttpExchange ex) throws IOException { - var returnTo = queryParam(ex).get("returnTo"); - var optToken = SessionToken.from(ex).map(Token::of); - if (optToken.isPresent()) try{ - var token = optToken.get(); - users.dropSession(token); - var expiredToken = new SessionToken(token.toString(),"/", Instant.now().minus(1, DAYS),true); - expiredToken.addTo(ex); - if (returnTo instanceof String location) return sendRedirect(ex,location); - return sendContent(ex, Map.of(REDIRECT,"home")); - } catch (UmbrellaException e) { + @Override + public boolean doPost(Path path, HttpExchange ex) throws IOException { + try { + return switch (path.pop()) { + case JSON -> jsonUser(ex); + case NOTIFY -> legacyNotify(ex); + case VALIDATE_TOKEN -> validateToken(ex); + default -> super.doPost(path,ex); + }; + } catch (UmbrellaException e){ return send(ex,e); } - if (returnTo instanceof String location) return sendRedirect(ex,location); - return sendContent(ex,Map.of(REDIRECT,"home")); } - private boolean getLegacyLogin(HttpExchange ex) throws IOException { + private boolean getLogin(HttpExchange ex) throws IOException { var returnTo = queryParam(ex).get("returnTo"); if (returnTo instanceof String url) { var token = SessionToken.from(ex).map(SessionToken::sessionId) @@ -131,59 +98,8 @@ public class LegacyApi extends BaseHandler { return sendRedirect(ex,location); } - @Override - public boolean doPost(Path path, HttpExchange ex) throws IOException { - try { - return switch (path.pop()) { - case JSON -> legacyJson(ex); - case NOTIFY -> legacyNotify(ex); - case VALIDATE_TOKEN -> validateToken(ex); - default -> super.doPost(path,ex); - }; - } catch (UmbrellaException e){ - return send(ex,e); - } - } - - private static String stripTrailingSlash(Object o){ - String url = o.toString(); - if (url.endsWith("/")) return url.substring(0,url.length()-1); - return url; - } - - private boolean validateToken(HttpExchange ex) throws UmbrellaException, IOException { - String body; - try { - body = body(ex); - } catch (Exception e){ - throw new UmbrellaException(400,"Failed to read request body").causedBy(e); - } - var map = decode(body); - LOG.log(DEBUG,"validateToken(…, {0}), data: {1}",map); - - String domain = stripTrailingSlash(map.get(DOMAIN) instanceof String s ? s : ""); - var keys = config.keys(); - var match = false; - for (var key : keys){ - var baseUrl = config.get(key + ".baseUrl").map(LegacyApi::stripTrailingSlash).orElse(null); - if (domain.equals(baseUrl)){ - match = true; - break; - } - } - if (!match) throw new UmbrellaException(500,"Failed to verify request domain!"); - - var o = map.get(TOKEN); - if (!(o instanceof String token)) throw new UmbrellaException(500,"Request did not contain token!"); - var session = users.load(Token.of(token)); - var user = users.load(session); - var userMap = user.toMap(); - userMap.put(TOKEN,Map.of(TOKEN,token,EXPIRATION,session.expiration().getEpochSecond())); - return sendContent(ex,userMap); - } - - private boolean legacyJson(HttpExchange ex) throws UmbrellaException, IOException { - Map data = null; + private boolean jsonUser(HttpExchange ex) throws UmbrellaException, IOException { + Map data = null; try { data = decode(body(ex)); } catch (IOException e) { @@ -228,12 +144,6 @@ public class LegacyApi extends BaseHandler { return sendContent(ex, userList.getFirst().toMap()); } - protected Session requestSession(Token token) throws UmbrellaException { - var session = users.load(token); - session = users.extend(session); - return session; - } - private boolean legacyNotify(HttpExchange ex) throws UmbrellaException, IOException { if (messageUrl == null) throw new UmbrellaException(500,"missing configuration: umbrella.modules.message.baseUrl"); var mime = contentType(ex).orElse(null); @@ -291,4 +201,94 @@ public class LegacyApi extends BaseHandler { // TODO: should we return json? return sendEmptyResponse(HTTP_OK,ex); } + + private boolean logout(HttpExchange ex) throws IOException { + var returnTo = queryParam(ex).get("returnTo"); + var optToken = SessionToken.from(ex).map(Token::of); + if (optToken.isPresent()) try{ + var token = optToken.get(); + users.dropSession(token); + var expiredToken = new SessionToken(token.toString(),"/", Instant.now().minus(1, DAYS),true); + expiredToken.addTo(ex); + if (returnTo instanceof String location) return sendRedirect(ex,location); + return sendContent(ex, Map.of(REDIRECT,"home")); + } catch (UmbrellaException e) { + return send(ex,e); + } + if (returnTo instanceof String location) return sendRedirect(ex,location); + return sendContent(ex,Map.of(REDIRECT,"home")); + } + + protected Session requestSession(Token token) throws UmbrellaException { + var session = users.load(token); + session = users.extend(session); + return session; + } + + private boolean search(HttpExchange ex) throws IOException, UmbrellaException { + var optToken = SessionToken.from(ex).map(Token::of); + if (optToken.isEmpty()) return sendEmptyResponse(HTTP_UNAUTHORIZED,ex); + var token = optToken.get(); + var params = queryParam(ex); + var key = params.get(KEY) instanceof String s ? s : null; + if (key == null) throw new UmbrellaException(HTTP_BAD_REQUEST,"Missing search key"); + var fulltext = key.contains("+") || "on".equals(params.get("fulltext")); + StringBuilder searchResult = new StringBuilder(); + if (fulltext){ + for (var module : config.keys()){ + var baseUrl = config.get(module + ".baseUrl"); + if (baseUrl.isEmpty()) continue; + + var res = request(baseUrl.get()+"/search",token.asMap().plus(KEY,key),MIME_FORM_URL,token.asBearer()); + if (!(res instanceof String content) || content.isBlank()) continue; + searchResult.append("
"); + searchResult.append(content).append("
\n"); + } + } else { + var bookmark = config.get("bookmark.baseUrl"); + if (bookmark.isEmpty()) throw new UmbrellaException(500,"Tag search not available: Bookmark module not configured!"); + var res = request(bookmark.get()+"/search",token.asMap().plus(KEY,key),MIME_FORM_URL,null); + if (!(res instanceof String content)) throw new UmbrellaException(500,"Search did not return html content!"); + searchResult.append(content); + } + addCors(ex); + return sendContent(ex,searchResult.toString()); + }; + + private static String stripTrailingSlash(Object o){ + String url = o.toString(); + if (url.endsWith("/")) return url.substring(0,url.length()-1); + return url; + } + + private boolean validateToken(HttpExchange ex) throws UmbrellaException, IOException { + String body; + try { + body = body(ex); + } catch (Exception e){ + throw new UmbrellaException(400,"Failed to read request body").causedBy(e); + } + var map = decode(body); + LOG.log(DEBUG,"validateToken(…, {0}), data: {1}",map); + + String domain = stripTrailingSlash(map.get(DOMAIN) instanceof String s ? s : ""); + var keys = config.keys(); + var match = false; + for (var key : keys){ + var baseUrl = config.get(key + ".baseUrl").map(LegacyApi::stripTrailingSlash).orElse(null); + if (domain.equals(baseUrl)){ + match = true; + break; + } + } + if (!match) throw new UmbrellaException(500,"Failed to verify request domain!"); + + var o = map.get(TOKEN); + if (!(o instanceof String token)) throw new UmbrellaException(500,"Request did not contain token!"); + var session = users.load(Token.of(token)); + var user = users.load(session); + var userMap = user.toMap(); + userMap.put(TOKEN,Map.of(TOKEN,token,EXPIRATION,session.expiration().getEpochSecond())); + return sendContent(ex,userMap); + } } diff --git a/user/src/main/java/de/srsoftware/umbrella/user/UserModule.java b/user/src/main/java/de/srsoftware/umbrella/user/UserModule.java index bf31615..88f2514 100644 --- a/user/src/main/java/de/srsoftware/umbrella/user/UserModule.java +++ b/user/src/main/java/de/srsoftware/umbrella/user/UserModule.java @@ -72,20 +72,7 @@ public class UserModule extends BaseHandler { logins = loginDb; } - private HttpExchange addCors(HttpExchange ex){ - var headers = ex.getRequestHeaders(); - var origin = nullable(headers.get("Origin")).orElse(List.of()).stream().filter(url -> url.contains("://localhost")||url.contains("://127.0.0.1")).findAny(); - if (origin.isPresent()) { - var url = origin.get(); - headers = ex.getResponseHeaders(); - headers.add("Allow-Origin", url); - headers.add("Access-Control-Allow-Origin", url); - headers.add("Access-Control-Allow-Headers", "Content-Type"); - headers.add("Access-Control-Allow-Credentials", "true"); - headers.add("Access-Control-Allow-Methods","DELETE, GET, POST, PATCH"); - } - return ex; - } + private boolean deleteOIDC(HttpExchange ex, UmbrellaUser user, Path path) throws IOException { var head = path.pop(); diff --git a/web/src/main/java/de/srsoftware/umbrella/web/WebHandler.java b/web/src/main/java/de/srsoftware/umbrella/web/WebHandler.java index 23b52d0..c2732c3 100644 --- a/web/src/main/java/de/srsoftware/umbrella/web/WebHandler.java +++ b/web/src/main/java/de/srsoftware/umbrella/web/WebHandler.java @@ -9,26 +9,13 @@ import static java.net.HttpURLConnection.HTTP_NOT_FOUND; import com.sun.net.httpserver.HttpExchange; import de.srsoftware.tools.Path; import de.srsoftware.tools.PathHandler; +import de.srsoftware.umbrella.core.BaseHandler; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.List; -public class WebHandler extends PathHandler { - - private HttpExchange addCors(HttpExchange ex){ - var headers = ex.getRequestHeaders(); - var origin = nullable(headers.get("Origin")).orElse(List.of()).stream().filter(url -> url.contains("://localhost")||url.contains("://127.0.0.1")).findAny(); - if (origin.isPresent()) { - var url = origin.get(); - headers = ex.getResponseHeaders(); - headers.add("Allow-Origin", url); - headers.add("Access-Control-Allow-Origin", url); - headers.add("Access-Control-Allow-Headers", "Content-Type"); - headers.add("Access-Control-Allow-Credentials", "true"); - headers.add("Access-Control-Allow-Methods","GET, POST, PATCH"); - } - return ex; - } +public class WebHandler extends BaseHandler { @Override public boolean doGet(Path path, HttpExchange ex) throws IOException {