package ins.frontend; import ins.frontend.utils.RegistrationService; import ins.frontend.utils.WebApp; import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket; import org.openautonomousconnection.protocol.side.web.ProtocolWebServer; import org.openautonomousconnection.protocol.side.web.managers.SessionManager; import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketFlags; import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader; import org.openautonomousconnection.webserver.api.Route; import org.openautonomousconnection.webserver.api.WebPage; import org.openautonomousconnection.webserver.api.WebPageContext; import org.openautonomousconnection.webserver.utils.HeaderMaps; import org.openautonomousconnection.webserver.utils.Html; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.util.*; /** * Register page (v1.0.1). */ @Route(path = "/register.html") public final class register implements WebPage { private enum ReqMethod { GET, POST, OTHER } @Override public WebResourceResponsePacket handle(WebPageContext ctx) throws Exception { WebApp.init(); // If a valid session already exists -> go dashboard (keep session) if (ctx.session != null && ctx.session.isValid() && ctx.session.getUser() != null) { return redirect302(ctx, "dashboard.html", ctx.session.getSessionId()); } ReqMethod method = detectMethod(ctx); if (method == ReqMethod.GET) { return ok(ctx, renderForm(null)); } if (method != ReqMethod.POST) { return text(ctx, 405, "Method Not Allowed"); } String contentType = headerIgnoreCase(ctx.request.getHeaders(), "content-type"); String ctLower = (contentType == null) ? "" : contentType.toLowerCase(Locale.ROOT); if (!ctLower.startsWith("application/x-www-form-urlencoded")) { return ok(ctx, renderForm("Unsupported content-type: " + Html.esc(contentType))); } Map> form = parseFormUrlEncoded(ctx.request.getBody()); String username = first(form, "username"); String password = first(form, "password"); RegistrationService service = new RegistrationService(WebApp.get().users(), WebApp.get().passwordHasher()); RegistrationService.Result r = service.register(username, password); if (!r.ok()) { return ok(ctx, renderForm(r.error())); } // Create new session (user just registered) String ip = resolveIp(ctx); String ua = headerIgnoreCase(ctx.request.getHeaders(), "user-agent"); if (ua == null) ua = ""; String session = SessionManager.create( String.valueOf(r.userId()), ip, ua, (ProtocolWebServer) ctx.client.getServer() ); return redirect302(ctx, "dashboard.html", session); } private ReqMethod detectMethod(WebPageContext ctx) { try { Method m = ctx.request.getClass().getMethod("getMethod"); Object v = m.invoke(ctx.request); if (v != null) { String s = String.valueOf(v).trim().toUpperCase(Locale.ROOT); if ("GET".equals(s)) return ReqMethod.GET; if ("POST".equals(s)) return ReqMethod.POST; return ReqMethod.OTHER; } } catch (Exception ignored) { } byte[] body = ctx.request.getBody(); if (body != null && body.length > 0) return ReqMethod.POST; return ReqMethod.GET; } private static WebResourceResponsePacket ok(WebPageContext ctx, String html) { byte[] body = Html.utf8(html); Map headers = HeaderMaps.mutable(); headers.put("content-length", String.valueOf(body.length)); return new WebResourceResponsePacket(outHeader(ctx), 200, "text/html; charset=utf-8", headers, body, null); } private static WebResourceResponsePacket text(WebPageContext ctx, int code, String msg) { byte[] body = (msg == null ? "" : msg).getBytes(StandardCharsets.UTF_8); Map headers = HeaderMaps.mutable(); headers.put("content-length", String.valueOf(body.length)); return new WebResourceResponsePacket(outHeader(ctx), code, "text/plain; charset=utf-8", headers, body, null); } private static WebResourceResponsePacket redirect302(WebPageContext ctx, String location, String session) { Map headers = HeaderMaps.mutable(); headers.put("Location", location); if (session != null && !session.isBlank()) { headers.put("Set-Cookie", "session=" + session + "; Path=/; HttpOnly; SameSite=Lax"); headers.put("session", session); } return new WebResourceResponsePacket(outHeader(ctx), 302, "text/plain; charset=utf-8", headers, new byte[0], null); } private static String renderForm(String errOrOk) { String line = ""; if (errOrOk != null && !errOrOk.isBlank()) { boolean ok = errOrOk.startsWith("OK:"); line = "

" + errOrOk + "

"; } String body = """

Register

%s
""".formatted(line); return Html.page("Register", body); } private static String resolveIp(WebPageContext ctx) { if (ctx.client == null || ctx.client.getConnection() == null) return ""; if (ctx.client.getConnection().getTcpSocket() == null) return ""; if (ctx.client.getConnection().getTcpSocket().getInetAddress() == null) return ""; return ctx.client.getConnection().getTcpSocket().getInetAddress().getHostAddress(); } private static String headerIgnoreCase(Map headers, String key) { if (headers == null || headers.isEmpty() || key == null) return null; for (Map.Entry e : headers.entrySet()) { if (e.getKey() != null && e.getKey().equalsIgnoreCase(key)) return e.getValue(); } return null; } private static Map> parseFormUrlEncoded(byte[] body) { if (body == null || body.length == 0) return Map.of(); String raw = new String(body, StandardCharsets.UTF_8); Map> out = new LinkedHashMap<>(); int start = 0; while (start <= raw.length()) { int amp = raw.indexOf('&', start); if (amp < 0) amp = raw.length(); String pair = raw.substring(start, amp); if (!pair.isEmpty()) { int eq = pair.indexOf('='); String k = (eq < 0) ? pair : pair.substring(0, eq); String v = (eq < 0) ? "" : pair.substring(eq + 1); String key = decodeFormToken(k); String val = decodeFormToken(v); if (!key.isEmpty()) out.computeIfAbsent(key, __ -> new ArrayList<>(1)).add(val); } start = amp + 1; if (amp == raw.length()) break; } Map> frozen = new LinkedHashMap<>(); for (Map.Entry> e : out.entrySet()) frozen.put(e.getKey(), List.copyOf(e.getValue())); return Map.copyOf(frozen); } private static String first(Map> params, String key) { if (params == null || key == null) return null; List v = params.get(key); if (v == null || v.isEmpty()) return null; String t = v.getFirst(); if (t == null) return null; String s = t.trim(); return s.isEmpty() ? null : s; } private static String decodeFormToken(String s) { if (s == null || s.isEmpty()) return ""; byte[] tmp = new byte[s.length()]; int n = 0; for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (c == '+') { tmp[n++] = (byte) ' '; continue; } if (c == '%' && i + 2 < s.length()) { int hi = hex(c = s.charAt(i + 1)); int lo = hex(s.charAt(i + 2)); if (hi >= 0 && lo >= 0) { tmp[n++] = (byte) ((hi << 4) | lo); i += 2; continue; } } byte[] b = String.valueOf(s.charAt(i)).getBytes(StandardCharsets.UTF_8); for (byte bb : b) tmp[n++] = bb; } return new String(tmp, 0, n, StandardCharsets.UTF_8); } private static int hex(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return 10 + (c - 'a'); if (c >= 'A' && c <= 'F') return 10 + (c - 'A'); return -1; } private static WebPacketHeader outHeader(WebPageContext ctx) { WebPacketHeader in = (ctx != null && ctx.request != null) ? ctx.request.getHeader() : null; if (in == null) { return new WebPacketHeader(0, 0, 0, 0, WebPacketFlags.RESOURCE, System.currentTimeMillis()); } return new WebPacketHeader( in.getRequestId(), in.getTabId(), in.getPageId(), in.getFrameId(), in.getFlags() | WebPacketFlags.RESOURCE, System.currentTimeMillis() ); } }