2026-02-11 23:22:20 +01:00
|
|
|
package ins.frontend;
|
|
|
|
|
|
|
|
|
|
import ins.frontend.utils.RegistrationService;
|
|
|
|
|
import ins.frontend.utils.WebApp;
|
2026-02-22 17:21:23 +01:00
|
|
|
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket;
|
2026-02-11 23:22:20 +01:00
|
|
|
import org.openautonomousconnection.protocol.side.web.ProtocolWebServer;
|
|
|
|
|
import org.openautonomousconnection.protocol.side.web.managers.SessionManager;
|
2026-02-22 17:21:23 +01:00
|
|
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketFlags;
|
|
|
|
|
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
|
2026-02-11 23:22:20 +01:00
|
|
|
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;
|
|
|
|
|
|
2026-02-22 17:21:23 +01:00
|
|
|
import java.lang.reflect.Method;
|
2026-02-11 23:22:20 +01:00
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
|
|
import java.util.*;
|
|
|
|
|
|
|
|
|
|
/**
|
2026-02-22 17:21:23 +01:00
|
|
|
* Register page (v1.0.1).
|
2026-02-11 23:22:20 +01:00
|
|
|
*/
|
|
|
|
|
@Route(path = "/register.html")
|
2026-03-02 18:41:19 +01:00
|
|
|
public final class register extends WebPage {
|
2026-02-11 23:22:20 +01:00
|
|
|
|
2026-02-22 17:21:23 +01:00
|
|
|
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<String, List<String>> 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<String, String> 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);
|
2026-02-11 23:22:20 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-22 17:21:23 +01:00
|
|
|
private static WebResourceResponsePacket text(WebPageContext ctx, int code, String msg) {
|
|
|
|
|
byte[] body = (msg == null ? "" : msg).getBytes(StandardCharsets.UTF_8);
|
|
|
|
|
Map<String, String> 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);
|
2026-02-11 23:22:20 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-22 17:21:23 +01:00
|
|
|
private static WebResourceResponsePacket redirect302(WebPageContext ctx, String location, String session) {
|
2026-02-11 23:22:20 +01:00
|
|
|
Map<String, String> headers = HeaderMaps.mutable();
|
2026-02-22 17:21:23 +01:00
|
|
|
headers.put("Location", location);
|
2026-02-11 23:22:20 +01:00
|
|
|
if (session != null && !session.isBlank()) {
|
|
|
|
|
headers.put("Set-Cookie", "session=" + session + "; Path=/; HttpOnly; SameSite=Lax");
|
|
|
|
|
headers.put("session", session);
|
|
|
|
|
}
|
2026-02-22 17:21:23 +01:00
|
|
|
return new WebResourceResponsePacket(outHeader(ctx), 302, "text/plain; charset=utf-8", headers, new byte[0], null);
|
2026-02-11 23:22:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static String renderForm(String errOrOk) {
|
|
|
|
|
String line = "";
|
|
|
|
|
if (errOrOk != null && !errOrOk.isBlank()) {
|
|
|
|
|
boolean ok = errOrOk.startsWith("OK:");
|
|
|
|
|
line = "<p class='" + (ok ? "ok" : "err") + "'>" + errOrOk + "</p>";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String body = """
|
|
|
|
|
<div class="card">
|
|
|
|
|
<h2>Register</h2>
|
|
|
|
|
%s
|
|
|
|
|
<form method="post" action="register.html" class="form">
|
|
|
|
|
<label><span class="muted">Username</span><input type="text" name="username" autocomplete="username" required></label>
|
|
|
|
|
<label><span class="muted">Password</span><input type="password" name="password" autocomplete="new-password" required></label>
|
|
|
|
|
<button type="submit">Create account</button>
|
|
|
|
|
</form>
|
|
|
|
|
<div class="row" style="margin-top: 12px;">
|
|
|
|
|
<div class="col"><a href="login.html">Login</a></div>
|
|
|
|
|
<div class="col"><a href="index.html">Home</a></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
""".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<String, String> headers, String key) {
|
|
|
|
|
if (headers == null || headers.isEmpty() || key == null) return null;
|
|
|
|
|
for (Map.Entry<String, String> e : headers.entrySet()) {
|
|
|
|
|
if (e.getKey() != null && e.getKey().equalsIgnoreCase(key)) return e.getValue();
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static Map<String, List<String>> parseFormUrlEncoded(byte[] body) {
|
|
|
|
|
if (body == null || body.length == 0) return Map.of();
|
|
|
|
|
|
|
|
|
|
String raw = new String(body, StandardCharsets.UTF_8);
|
|
|
|
|
Map<String, List<String>> 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<String, List<String>> frozen = new LinkedHashMap<>();
|
|
|
|
|
for (Map.Entry<String, List<String>> e : out.entrySet()) frozen.put(e.getKey(), List.copyOf(e.getValue()));
|
|
|
|
|
return Map.copyOf(frozen);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static String first(Map<String, List<String>> params, String key) {
|
|
|
|
|
if (params == null || key == null) return null;
|
|
|
|
|
List<String> 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()) {
|
2026-02-22 17:21:23 +01:00
|
|
|
int hi = hex(c = s.charAt(i + 1));
|
2026-02-11 23:22:20 +01:00
|
|
|
int lo = hex(s.charAt(i + 2));
|
|
|
|
|
if (hi >= 0 && lo >= 0) {
|
|
|
|
|
tmp[n++] = (byte) ((hi << 4) | lo);
|
|
|
|
|
i += 2;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-22 17:21:23 +01:00
|
|
|
byte[] b = String.valueOf(s.charAt(i)).getBytes(StandardCharsets.UTF_8);
|
2026-02-11 23:22:20 +01:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-22 17:21:23 +01:00
|
|
|
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());
|
2026-02-11 23:22:20 +01:00
|
|
|
}
|
2026-02-22 17:21:23 +01:00
|
|
|
return new WebPacketHeader(
|
|
|
|
|
in.getRequestId(),
|
|
|
|
|
in.getTabId(),
|
|
|
|
|
in.getPageId(),
|
|
|
|
|
in.getFrameId(),
|
|
|
|
|
in.getFlags() | WebPacketFlags.RESOURCE,
|
|
|
|
|
System.currentTimeMillis()
|
2026-02-11 23:22:20 +01:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|