This commit is contained in:
UnlegitDqrk
2026-02-11 23:22:20 +01:00
parent 9483c36a66
commit 64ce55ea7b
39 changed files with 2033 additions and 3089 deletions

231
frontend/login.java Normal file
View File

@@ -0,0 +1,231 @@
package ins.frontend;
import ins.frontend.utils.UserDao;
import ins.frontend.utils.WebApp;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket;
import org.openautonomousconnection.protocol.side.web.ProtocolWebServer;
import org.openautonomousconnection.protocol.side.web.managers.SessionManager;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.WebRequestMethod;
import org.openautonomousconnection.webserver.api.Route;
import org.openautonomousconnection.webserver.api.SessionContext;
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.nio.charset.StandardCharsets;
import java.util.*;
/**
* Login page with existing-session short-circuit.
*
* <p>Username stored in DB as plain text.</p>
*/
@Route(path = "/login.html")
public final class login implements WebPage {
private static WebResponsePacket ok(String html) {
return new WebResponsePacket(200, "text/html; charset=utf-8", HeaderMaps.mutable(), Html.utf8(html));
}
private static WebResponsePacket text(int code, String msg) {
return new WebResponsePacket(code, "text/plain; charset=utf-8", HeaderMaps.mutable(), msg.getBytes(StandardCharsets.UTF_8));
}
private static WebResponsePacket redirect302(String location, String session) {
Map<String, String> headers = HeaderMaps.mutable();
headers.put("location", location);
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]);
}
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>Login</h2>
%s
<form method="post" action="login.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="current-password" required></label>
<button type="submit">Login</button>
</form>
<div class="row" style="margin-top: 12px;">
<div class="col"><a href="register.html">Register</a></div>
<div class="col"><a href="index.html">Home</a></div>
</div>
</div>
""".formatted(line);
return Html.page("Login", 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()) {
int hi = hex(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(c).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;
}
@Override
public WebResponsePacket handle(WebPageContext ctx) throws Exception {
WebApp.init();
// 1) If a valid session already exists -> go dashboard (keep session)
SessionContext existing = SessionContext.from(
ctx.client,
(ProtocolWebServer) ctx.client.getServer(),
ctx.request.getHeaders()
);
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);
}
}