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);
@@ -206,7 +200,7 @@ public final class dashboard implements WebPage {
<h2>INS Registrar</h2> <h2>INS Registrar</h2>
%s %s
%s %s
<h3>Create TLN</h3> <h3>Create TLN</h3>
<form method="post" action="dashboard.html" class="form"> <form method="post" action="dashboard.html" class="form">
<input type="hidden" name="action" value="create_tln"> <input type="hidden" name="action" value="create_tln">
@@ -216,10 +210,10 @@ public final class dashboard implements WebPage {
<label><span class="muted">allow_subdomains</span><input type="text" name="allow_subdomains" value="1" required></label> <label><span class="muted">allow_subdomains</span><input type="text" name="allow_subdomains" value="1" required></label>
<button type="submit">Create TLN</button> <button type="submit">Create TLN</button>
</form> </form>
<h3>TLNs (public + owned)</h3> <h3>TLNs (public + owned)</h3>
%s %s
<h3>Create InfoName</h3> <h3>Create InfoName</h3>
<p class="muted">Allowed if TLN is public or owned by you.</p> <p class="muted">Allowed if TLN is public or owned by you.</p>
<form method="post" action="dashboard.html" class="form"> <form method="post" action="dashboard.html" class="form">
@@ -228,7 +222,7 @@ public final class dashboard implements WebPage {
<label><span class="muted">info</span><input type="text" name="info" placeholder="example" required></label> <label><span class="muted">info</span><input type="text" name="info" placeholder="example" required></label>
<button type="submit">Create InfoName</button> <button type="submit">Create InfoName</button>
</form> </form>
<h3>Add Record</h3> <h3>Add Record</h3>
<p class="muted">Subname requires allow_subdomains=1 unless you own the TLN. Root (no sub) always allowed.</p> <p class="muted">Subname requires allow_subdomains=1 unless you own the TLN. Root (no sub) always allowed.</p>
<form method="post" action="dashboard.html" class="form"> <form method="post" action="dashboard.html" class="form">
@@ -243,10 +237,10 @@ public final class dashboard implements WebPage {
<label><span class="muted">weight (SRV)</span><input type="number" name="weight"></label> <label><span class="muted">weight (SRV)</span><input type="number" name="weight"></label>
<button type="submit">Add Record</button> <button type="submit">Add Record</button>
</form> </form>
<h3>Your InfoNames</h3> <h3>Your InfoNames</h3>
%s %s
<div class="row"> <div class="row">
<div class="col"><a href="index.html">Home</a></div> <div class="col"><a href="index.html">Home</a></div>
</div> </div>
@@ -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) {
@@ -300,7 +294,7 @@ public final class dashboard implements WebPage {
</label> </label>
<button type="submit">Update</button> <button type="submit">Update</button>
</form> </form>
<form method="post" action="dashboard.html" style="margin:0;"> <form method="post" action="dashboard.html" style="margin:0;">
<input type="hidden" name="action" value="delete_tln"> <input type="hidden" name="action" value="delete_tln">
<input type="hidden" name="id" value="%d"> <input type="hidden" name="id" value="%d">
@@ -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,49 +1,73 @@
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>
The <strong>Default INS Server</strong> is the official, project-operated The <strong>Default INS Server</strong> is the official, project-operated
name service of the Open Autonomous Connection (OAC) network. name service of the Open Autonomous Connection (OAC) network.
</p> </p>
<p> <p>
It provides a trusted reference point for resolving InfoNames It provides a trusted reference point for resolving InfoNames
and enables initial client connections and enables initial client connections
to the OAC ecosystem. to the OAC ecosystem.
</p> </p>
<p> <p>
This server is maintained by the OAC project and is intended This server is maintained by the OAC project and is intended
as the recommended entry point for public services and new clients. as the recommended entry point for public services and new clients.
</p> </p>
<p class="muted"> <p class="muted">
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);
} }
} }