Updated frontend to new Protocol

This commit is contained in:
UnlegitDqrk
2026-02-22 17:21:23 +01:00
parent 2d829fe341
commit 09dd207bb1
5 changed files with 355 additions and 221 deletions

View File

@@ -2,22 +2,24 @@ package ins.frontend;
import ins.frontend.utils.RegistrarDao; import ins.frontend.utils.RegistrarDao;
import ins.frontend.utils.WebApp; import ins.frontend.utils.WebApp;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket; import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket;
import org.openautonomousconnection.protocol.side.web.ProtocolWebServer; 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.Route;
import org.openautonomousconnection.webserver.api.SessionContext;
import org.openautonomousconnection.webserver.api.WebPage; import org.openautonomousconnection.webserver.api.WebPage;
import org.openautonomousconnection.webserver.api.WebPageContext; import org.openautonomousconnection.webserver.api.WebPageContext;
import org.openautonomousconnection.webserver.utils.HeaderMaps;
import org.openautonomousconnection.webserver.utils.Html; import org.openautonomousconnection.webserver.utils.Html;
import org.openautonomousconnection.webserver.utils.MergedRequestParams; import org.openautonomousconnection.webserver.utils.MergedRequestParams;
import org.openautonomousconnection.webserver.utils.QuerySupport; import org.openautonomousconnection.webserver.utils.WebUrlUtil;
import java.nio.charset.StandardCharsets;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
/** /**
* INS registrar ins.frontend (TLN / InfoName / Records) with proper POST parameter parsing. * INS registrar dashboard (TLN / InfoName / Records) for protocol v1.0.1 resource packets.
* *
* <p>Supported actions (POST recommended for mutations):</p> * <p>Supported actions (POST recommended for mutations):</p>
* <ul> * <ul>
@@ -28,11 +30,8 @@ import java.util.Map;
* <li>delete_infoname</li> * <li>delete_infoname</li>
* <li>add_record</li> * <li>add_record</li>
* </ul> * </ul>
*
* <p>Important: Listing/editing/deleting records requires DAO methods that are not part of the provided snippet.
* This page currently supports adding records only.</p>
*/ */
@Route(path = "dashboard.html") @Route(path = "/dashboard.html")
public final class dashboard implements WebPage { public final class dashboard implements WebPage {
private static Integer normalizeNullableInt(String s) { private static Integer normalizeNullableInt(String s) {
@@ -47,30 +46,26 @@ public final class dashboard implements WebPage {
} }
@Override @Override
public WebResponsePacket handle(WebPageContext ctx) throws Exception { public WebResourceResponsePacket handle(WebPageContext ctx) throws Exception {
WebApp.init(); WebApp.init();
SessionContext session = SessionContext.from( if (ctx.session == null || !ctx.session.isValid() || ctx.session.getUser() == null) {
ctx.client, return plain(ctx, 401, "Authentication required (session).");
(ProtocolWebServer) ctx.client.getServer(),
ctx.request.getHeaders()
);
if (!session.isValid() || session.getUser() == null) {
return plain(401, "Authentication required (session).");
} }
int userId; int userId;
try { try {
userId = Integer.parseInt(session.getUser()); userId = Integer.parseInt(ctx.session.getUser());
} catch (Exception e) { } catch (Exception e) {
return plain(401, "Invalid session user."); return plain(ctx, 401, "Invalid session user.");
} }
RegistrarDao dao = WebApp.get().dao(); RegistrarDao dao = WebApp.get().dao();
// Raw target and merged params (GET + POST). // Build target for param merging: "/path?query"
String rawTarget = org.openautonomousconnection.webserver.utils.QuerySupport.extractRawTarget(ctx.request); String rawTarget = WebUrlUtil.extractPathAndQuery(ctx.request.getUrl());
if (rawTarget == null) rawTarget = "/dashboard.html";
Map<String, String> headers = ctx.request.getHeaders(); Map<String, String> headers = ctx.request.getHeaders();
byte[] body = ctx.request.getBody(); byte[] body = ctx.request.getBody();
@@ -90,7 +85,7 @@ public final class dashboard implements WebPage {
} }
} }
return render(userId, msg, err, dao); return render(ctx, userId, msg, err, dao);
} }
private ActionResult executeAction(String action, MergedRequestParams p, int userId, RegistrarDao dao) throws Exception { private ActionResult executeAction(String action, MergedRequestParams p, int userId, RegistrarDao dao) throws Exception {
@@ -171,7 +166,6 @@ public final class dashboard implements WebPage {
if (!dao.isOwnerOfInfoName(infonameId, userId)) return ActionResult.err("Not owner of this infoname."); if (!dao.isOwnerOfInfoName(infonameId, userId)) return ActionResult.err("Not owner of this infoname.");
if (type.isBlank() || value == null || value.isBlank()) return ActionResult.err("Missing type/value."); if (type.isBlank() || value == null || value.isBlank()) return ActionResult.err("Missing type/value.");
// Validate allow_subdomains against TLN of this infoname (owned list contains TLN metadata).
RegistrarDao.InfoNameRow[] owned = dao.listOwnedInfoNames(userId); RegistrarDao.InfoNameRow[] owned = dao.listOwnedInfoNames(userId);
RegistrarDao.InfoNameRow row = null; RegistrarDao.InfoNameRow row = null;
for (RegistrarDao.InfoNameRow r : owned) { for (RegistrarDao.InfoNameRow r : owned) {
@@ -194,7 +188,7 @@ public final class dashboard implements WebPage {
return ActionResult.err("Unknown action: " + action); return ActionResult.err("Unknown action: " + action);
} }
private WebResponsePacket render(int userId, String msg, String err, RegistrarDao dao) throws Exception { private WebResourceResponsePacket render(WebPageContext ctx, int userId, String msg, String err, RegistrarDao dao) throws Exception {
RegistrarDao.TlnRow[] tlns = dao.listVisibleTlns(userId); RegistrarDao.TlnRow[] tlns = dao.listVisibleTlns(userId);
RegistrarDao.InfoNameRow[] owned = dao.listOwnedInfoNames(userId); RegistrarDao.InfoNameRow[] owned = dao.listOwnedInfoNames(userId);
@@ -259,7 +253,7 @@ public final class dashboard implements WebPage {
); );
String html = Html.page("INS Registrar", body); String html = Html.page("INS Registrar", body);
return new WebResponsePacket(200, "text/html", new HashMap<>(), Html.utf8(html)); return html(ctx, 200, html);
} }
private String renderTlnSection(RegistrarDao.TlnRow[] tlns, int userId) { private String renderTlnSection(RegistrarDao.TlnRow[] tlns, int userId) {
@@ -348,8 +342,33 @@ public final class dashboard implements WebPage {
return sb.toString(); return sb.toString();
} }
private WebResponsePacket plain(int code, String text) { private WebResourceResponsePacket plain(WebPageContext ctx, int code, String text) {
return new WebResponsePacket(code, "text/plain", new HashMap<>(), Html.utf8(text)); byte[] body = (text == null ? "" : text).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);
}
private WebResourceResponsePacket html(WebPageContext ctx, int code, 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), code, "text/html; charset=utf-8", headers, body, null);
}
private 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()
);
} }
private String safeMsg(Exception e) { private String safeMsg(Exception e) {

View File

@@ -1,36 +1,59 @@
package ins.frontend; package ins.frontend;
import ins.frontend.utils.WebApp; import ins.frontend.utils.WebApp;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket; import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket;
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.Route;
import org.openautonomousconnection.webserver.api.WebPage; import org.openautonomousconnection.webserver.api.WebPage;
import org.openautonomousconnection.webserver.api.WebPageContext; import org.openautonomousconnection.webserver.api.WebPageContext;
import org.openautonomousconnection.webserver.utils.HeaderMaps;
import org.openautonomousconnection.webserver.utils.Html; import org.openautonomousconnection.webserver.utils.Html;
import java.util.HashMap; import java.util.Map;
/** /**
* Landing page for the registrar ins.frontend. * Landing page for INS registrar frontend (v1.0.1).
*/ */
@Route(path = "index.html") @Route(path = "/index.html")
public final class index implements WebPage { public final class index implements WebPage {
@Override @Override
public WebResponsePacket handle(WebPageContext ctx) { public WebResourceResponsePacket handle(WebPageContext ctx) {
WebApp.init(); WebApp.init();
String html = Html.page("OAC INS Registrar", """ String html = Html.page("OAC INS Registrar", """
<div class="card"> <div class="card">
<h2>OAC INS Registrar</h2> <h2>OAC INS Registrar</h2>
<p class="muted">What you want to do?</p> <p class="muted">What you want to do?</p>
<div class="col"><a href="info.html">Info</a></div><br /> <div class="col"><a href="info.html">Info</a></div><br />
<div class="row"> <div class="row">
<div class="col"><a href="login.html">Login</a></div> <div class="col"><a href="login.html">Login</a></div>
<div class="col"><a href="register.html">Register</a></div> <div class="col"><a href="register.html">Register</a></div>
<div class="col"><a href="dashboard.html">Dashboard</a></div> <div class="col"><a href="dashboard.html">Dashboard</a></div>
</div> </div>
</div> </div>
"""); """);
return new WebResponsePacket(200, "text/html", new HashMap<>(), Html.utf8(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);
}
private 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()
);
} }
} }

View File

@@ -1,25 +1,29 @@
package ins.frontend; package ins.frontend;
import ins.frontend.utils.WebApp; import ins.frontend.utils.WebApp;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket; import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket;
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.Route;
import org.openautonomousconnection.webserver.api.WebPage; import org.openautonomousconnection.webserver.api.WebPage;
import org.openautonomousconnection.webserver.api.WebPageContext; import org.openautonomousconnection.webserver.api.WebPageContext;
import org.openautonomousconnection.webserver.utils.HeaderMaps;
import org.openautonomousconnection.webserver.utils.Html; import org.openautonomousconnection.webserver.utils.Html;
import java.util.HashMap; import java.util.Map;
/** /**
* Landing page for the registrar ins.frontend. * Info page for INS registrar frontend (v1.0.1).
*/ */
@Route(path = "info.html") @Route(path = "/info.html")
public final class info implements WebPage { public final class info implements WebPage {
@Override @Override
public WebResponsePacket handle(WebPageContext ctx) { public WebResourceResponsePacket handle(WebPageContext ctx) {
WebApp.init(); WebApp.init();
String html = Html.page("INS Info", """ String html = Html.page("INS Info", """
<section class="card"> <section class="card">
<h2>OAC Default INS Server</h2> <h2>OAC Default INS Server</h2>
<p> <p>
@@ -42,8 +46,28 @@ public final class info implements WebPage {
Note: Alternative or private INS servers may exist, but the default INS Note: Alternative or private INS servers may exist, but the default INS
server represents the official and stable reference instance. server represents the official and stable reference instance.
</p> </p>
</section> """); </section>
""");
return new WebResponsePacket(200, "text/html", new HashMap<>(), Html.utf8(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);
}
private 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()
);
} }
} }

View File

@@ -2,46 +2,132 @@ package ins.frontend;
import ins.frontend.utils.UserDao; import ins.frontend.utils.UserDao;
import ins.frontend.utils.WebApp; import ins.frontend.utils.WebApp;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket; 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.ProtocolWebServer;
import org.openautonomousconnection.protocol.side.web.managers.SessionManager; import org.openautonomousconnection.protocol.side.web.managers.SessionManager;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.WebRequestMethod; 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.Route;
import org.openautonomousconnection.webserver.api.SessionContext;
import org.openautonomousconnection.webserver.api.WebPage; import org.openautonomousconnection.webserver.api.WebPage;
import org.openautonomousconnection.webserver.api.WebPageContext; import org.openautonomousconnection.webserver.api.WebPageContext;
import org.openautonomousconnection.webserver.utils.HeaderMaps; import org.openautonomousconnection.webserver.utils.HeaderMaps;
import org.openautonomousconnection.webserver.utils.Html; import org.openautonomousconnection.webserver.utils.Html;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
/** /**
* Login page with existing-session short-circuit. * Login page (v1.0.1).
*
* <p>Username stored in DB as plain text.</p>
*/ */
@Route(path = "/login.html") @Route(path = "/login.html")
public final class login implements WebPage { public final class login implements WebPage {
private static WebResponsePacket ok(String html) { private enum ReqMethod { GET, POST, OTHER }
return new WebResponsePacket(200, "text/html; charset=utf-8", HeaderMaps.mutable(), Html.utf8(html));
}
private static WebResponsePacket text(int code, String msg) { @Override
return new WebResponsePacket(code, "text/plain; charset=utf-8", HeaderMaps.mutable(), msg.getBytes(StandardCharsets.UTF_8)); public WebResourceResponsePacket handle(WebPageContext ctx) throws Exception {
} WebApp.init();
private static WebResponsePacket redirect302(String location, String session) { // If a valid session already exists -> go dashboard (keep session)
Map<String, String> headers = HeaderMaps.mutable(); if (ctx.session != null && ctx.session.isValid() && ctx.session.getUser() != null) {
headers.put("location", location); return redirect302(ctx, "dashboard.html", ctx.session.getSessionId());
if (session != null && !session.isBlank()) {
headers.put("Location", "dashboard.html");
headers.put("Set-Cookie", "session=" + session + "; Path=/; HttpOnly; SameSite=Lax");
headers.put("session", session);
headers.put("cookie", session);
} }
return new WebResponsePacket(302, "text/plain; charset=utf-8", headers, new byte[0]);
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");
if (username == null || password == null) {
return ok(ctx, renderForm("Missing username/password."));
}
String lookupUsername = username.trim();
UserDao.UserRow user = WebApp.get().users().findByUsername(lookupUsername).orElse(null);
if (user == null) {
return ok(ctx, renderForm("Invalid credentials."));
}
boolean okPw = WebApp.get().passwordHasher().verify(password, user.passwordEncoded());
if (!okPw) {
return ok(ctx, renderForm("Invalid credentials."));
}
String ip = resolveIp(ctx);
String ua = headerIgnoreCase(ctx.request.getHeaders(), "user-agent");
if (ua == null) ua = "";
String session = SessionManager.create(
String.valueOf(user.id()),
ip,
ua,
(ProtocolWebServer) ctx.client.getServer()
);
return redirect302(ctx, "dashboard.html", session);
}
private ReqMethod detectMethod(WebPageContext ctx) {
// Preferred: request.getMethod() via reflection (avoids depending on specific enum package).
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) {
// Fall back below.
}
// Fallback: treat presence of body as POST-like.
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);
}
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);
}
private static WebResourceResponsePacket redirect302(WebPageContext ctx, String location, String session) {
Map<String, String> headers = HeaderMaps.mutable();
headers.put("Location", location);
if (session != null && !session.isBlank()) {
headers.put("Set-Cookie", "session=" + session + "; Path=/; HttpOnly; SameSite=Lax");
// optional fallback for stacks that read a direct header:
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) { private static String renderForm(String errOrOk) {
@@ -140,7 +226,7 @@ public final class login implements WebPage {
continue; continue;
} }
if (c == '%' && i + 2 < s.length()) { if (c == '%' && i + 2 < s.length()) {
int hi = hex(s.charAt(i + 1)); int hi = hex(c = s.charAt(i + 1));
int lo = hex(s.charAt(i + 2)); int lo = hex(s.charAt(i + 2));
if (hi >= 0 && lo >= 0) { if (hi >= 0 && lo >= 0) {
tmp[n++] = (byte) ((hi << 4) | lo); tmp[n++] = (byte) ((hi << 4) | lo);
@@ -149,7 +235,7 @@ public final class login implements WebPage {
} }
} }
byte[] b = String.valueOf(c).getBytes(StandardCharsets.UTF_8); byte[] b = String.valueOf(s.charAt(i)).getBytes(StandardCharsets.UTF_8);
for (byte bb : b) tmp[n++] = bb; for (byte bb : b) tmp[n++] = bb;
} }
@@ -163,69 +249,18 @@ public final class login implements WebPage {
return -1; return -1;
} }
@Override private static WebPacketHeader outHeader(WebPageContext ctx) {
public WebResponsePacket handle(WebPageContext ctx) throws Exception { WebPacketHeader in = (ctx != null && ctx.request != null) ? ctx.request.getHeader() : null;
WebApp.init(); if (in == null) {
return new WebPacketHeader(0, 0, 0, 0, WebPacketFlags.RESOURCE, System.currentTimeMillis());
// 1) If a valid session already exists -> go dashboard (keep session) }
SessionContext existing = SessionContext.from( return new WebPacketHeader(
ctx.client, in.getRequestId(),
(ProtocolWebServer) ctx.client.getServer(), in.getTabId(),
ctx.request.getHeaders() in.getPageId(),
in.getFrameId(),
in.getFlags() | WebPacketFlags.RESOURCE,
System.currentTimeMillis()
); );
if (existing.isValid() && existing.getUser() != null) {
return redirect302("dashboard.html", existing.getSessionId());
}
WebRequestMethod method = ctx.request.getMethod();
if (method == null) method = WebRequestMethod.GET;
if (method == WebRequestMethod.GET) {
return ok(renderForm(null));
}
if (method != WebRequestMethod.POST) {
return text(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(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");
if (username == null || password == null) {
return ok(renderForm("Missing username/password."));
}
String lookupUsername = username.trim();
UserDao.UserRow user = WebApp.get().users().findByUsername(lookupUsername).orElse(null);
if (user == null) {
return ok(renderForm("Invalid credentials."));
}
boolean okPw = WebApp.get().passwordHasher().verify(password, user.passwordEncoded());
if (!okPw) {
return ok(renderForm("Invalid credentials."));
}
String ip = resolveIp(ctx);
String ua = headerIgnoreCase(ctx.request.getHeaders(), "user-agent");
if (ua == null) ua = "";
String session = SessionManager.create(
String.valueOf(user.id()),
ip,
ua,
(ProtocolWebServer) ctx.client.getServer()
);
return redirect302("dashboard.html", session);
} }
} }

View File

@@ -2,44 +2,120 @@ package ins.frontend;
import ins.frontend.utils.RegistrationService; import ins.frontend.utils.RegistrationService;
import ins.frontend.utils.WebApp; import ins.frontend.utils.WebApp;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket; 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.ProtocolWebServer;
import org.openautonomousconnection.protocol.side.web.managers.SessionManager; import org.openautonomousconnection.protocol.side.web.managers.SessionManager;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.WebRequestMethod; 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.Route;
import org.openautonomousconnection.webserver.api.SessionContext;
import org.openautonomousconnection.webserver.api.WebPage; import org.openautonomousconnection.webserver.api.WebPage;
import org.openautonomousconnection.webserver.api.WebPageContext; import org.openautonomousconnection.webserver.api.WebPageContext;
import org.openautonomousconnection.webserver.utils.HeaderMaps; import org.openautonomousconnection.webserver.utils.HeaderMaps;
import org.openautonomousconnection.webserver.utils.Html; import org.openautonomousconnection.webserver.utils.Html;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
/** /**
* Register page with existing-session short-circuit. * Register page (v1.0.1).
*/ */
@Route(path = "/register.html") @Route(path = "/register.html")
public final class register implements WebPage { public final class register implements WebPage {
private static WebResponsePacket ok(String html) { private enum ReqMethod { GET, POST, OTHER }
return new WebResponsePacket(200, "text/html; charset=utf-8", HeaderMaps.mutable(), Html.utf8(html));
@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 static WebResponsePacket text(int code, String msg) { private ReqMethod detectMethod(WebPageContext ctx) {
return new WebResponsePacket(code, "text/plain; charset=utf-8", HeaderMaps.mutable(), msg.getBytes(StandardCharsets.UTF_8)); 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 WebResponsePacket redirect302(String location, String session) { private static WebResourceResponsePacket ok(WebPageContext ctx, String html) {
byte[] body = Html.utf8(html);
Map<String, String> headers = HeaderMaps.mutable(); Map<String, String> headers = HeaderMaps.mutable();
headers.put("location", location); 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<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);
}
private static WebResourceResponsePacket redirect302(WebPageContext ctx, String location, String session) {
Map<String, String> headers = HeaderMaps.mutable();
headers.put("Location", location);
if (session != null && !session.isBlank()) { if (session != null && !session.isBlank()) {
headers.put("Location", "dashboard.html");
headers.put("Set-Cookie", "session=" + session + "; Path=/; HttpOnly; SameSite=Lax"); headers.put("Set-Cookie", "session=" + session + "; Path=/; HttpOnly; SameSite=Lax");
headers.put("session", session); headers.put("session", session);
headers.put("cookie", session);
} }
return new WebResponsePacket(302, "text/plain; charset=utf-8", headers, new byte[0]); return new WebResourceResponsePacket(outHeader(ctx), 302, "text/plain; charset=utf-8", headers, new byte[0], null);
} }
private static String renderForm(String errOrOk) { private static String renderForm(String errOrOk) {
@@ -138,7 +214,7 @@ public final class register implements WebPage {
continue; continue;
} }
if (c == '%' && i + 2 < s.length()) { if (c == '%' && i + 2 < s.length()) {
int hi = hex(s.charAt(i + 1)); int hi = hex(c = s.charAt(i + 1));
int lo = hex(s.charAt(i + 2)); int lo = hex(s.charAt(i + 2));
if (hi >= 0 && lo >= 0) { if (hi >= 0 && lo >= 0) {
tmp[n++] = (byte) ((hi << 4) | lo); tmp[n++] = (byte) ((hi << 4) | lo);
@@ -147,7 +223,7 @@ public final class register implements WebPage {
} }
} }
byte[] b = String.valueOf(c).getBytes(StandardCharsets.UTF_8); byte[] b = String.valueOf(s.charAt(i)).getBytes(StandardCharsets.UTF_8);
for (byte bb : b) tmp[n++] = bb; for (byte bb : b) tmp[n++] = bb;
} }
@@ -161,61 +237,18 @@ public final class register implements WebPage {
return -1; return -1;
} }
@Override private static WebPacketHeader outHeader(WebPageContext ctx) {
public WebResponsePacket handle(WebPageContext ctx) throws Exception { WebPacketHeader in = (ctx != null && ctx.request != null) ? ctx.request.getHeader() : null;
WebApp.init(); if (in == null) {
return new WebPacketHeader(0, 0, 0, 0, WebPacketFlags.RESOURCE, System.currentTimeMillis());
// 1) If a valid session already exists -> go dashboard (keep session) }
SessionContext existing = SessionContext.from( return new WebPacketHeader(
ctx.client, in.getRequestId(),
(ProtocolWebServer) ctx.client.getServer(), in.getTabId(),
ctx.request.getHeaders() in.getPageId(),
in.getFrameId(),
in.getFlags() | WebPacketFlags.RESOURCE,
System.currentTimeMillis()
); );
if (existing.isValid() && existing.getUser() != null) {
return redirect302("dashboard.html", existing.getSessionId());
}
WebRequestMethod method = ctx.request.getMethod();
if (method == null) method = WebRequestMethod.GET;
if (method == WebRequestMethod.GET) {
return ok(renderForm(null));
}
if (method != WebRequestMethod.POST) {
return text(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(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(renderForm(r.error()));
}
// 2) 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("dashboard.html", session);
} }
} }