working on user login
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
@@ -4,10 +4,16 @@ package de.srsoftware.oidc.api;
|
|||||||
import com.sun.net.httpserver.HttpExchange;
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
import com.sun.net.httpserver.HttpHandler;
|
import com.sun.net.httpserver.HttpHandler;
|
||||||
import com.sun.net.httpserver.HttpServer;
|
import com.sun.net.httpserver.HttpServer;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public abstract class PathHandler implements HttpHandler {
|
public abstract class PathHandler implements HttpHandler {
|
||||||
private String path;
|
private String path;
|
||||||
|
|
||||||
|
|
||||||
public class Bond {
|
public class Bond {
|
||||||
Bond(String p) {
|
Bond(String p) {
|
||||||
path = p;
|
path = p;
|
||||||
@@ -28,4 +34,17 @@ public abstract class PathHandler implements HttpHandler {
|
|||||||
if (path.startsWith("/")) path = path.substring(1);
|
if (path.startsWith("/")) path = path.substring(1);
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Optional<String> getHeader(HttpExchange ex, String key) {
|
||||||
|
return Optional.ofNullable(ex.getRequestHeaders().get(key)).map(List::stream).map(Stream::findFirst).orElse(Optional.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<String> language(HttpExchange ex) {
|
||||||
|
return getHeader(ex, "Accept-Language").map(s -> Arrays.stream(s.split(","))).map(Stream::findFirst).orElse(Optional.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void emptyResponse(int statusCode, HttpExchange ex) throws IOException {
|
||||||
|
ex.sendResponseHeaders(statusCode, 0);
|
||||||
|
ex.getResponseBody().close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,9 @@ repositories {
|
|||||||
dependencies {
|
dependencies {
|
||||||
testImplementation platform('org.junit:junit-bom:5.10.0')
|
testImplementation platform('org.junit:junit-bom:5.10.0')
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter'
|
testImplementation 'org.junit.jupiter:junit-jupiter'
|
||||||
implementation project(':de.srsoftware.oidc.web')
|
|
||||||
implementation project(':de.srsoftware.oidc.api')
|
implementation project(':de.srsoftware.oidc.api')
|
||||||
|
implementation project(':de.srsoftware.oidc.backend')
|
||||||
|
implementation project(':de.srsoftware.oidc.web')
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
@@ -22,7 +23,7 @@ test {
|
|||||||
|
|
||||||
jar {
|
jar {
|
||||||
manifest {
|
manifest {
|
||||||
attributes "Main-Class": "de.srsoftware.oidc.server.Application"
|
attributes "Main-Class": "de.srsoftware.oidc.app.Application"
|
||||||
}
|
}
|
||||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
from {
|
from {
|
||||||
@@ -1,17 +1,23 @@
|
|||||||
/* © SRSoftware 2024 */
|
/* © SRSoftware 2024 */
|
||||||
package de.srsoftware.oidc.server;
|
package de.srsoftware.oidc.app;
|
||||||
|
|
||||||
|
|
||||||
import com.sun.net.httpserver.HttpServer;
|
import com.sun.net.httpserver.HttpServer;
|
||||||
|
import de.srsoftware.oidc.backend.Backend;
|
||||||
|
import de.srsoftware.oidc.web.Forward;
|
||||||
import de.srsoftware.oidc.web.StaticPages;
|
import de.srsoftware.oidc.web.StaticPages;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
public class Application {
|
public class Application {
|
||||||
|
public static final String STATIC_PATH = "/web";
|
||||||
|
public static final String INDEX = STATIC_PATH + "/index.html";
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
|
HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
|
||||||
new StaticPages().bindPath("/static").on(server);
|
new StaticPages().bindPath(STATIC_PATH).on(server);
|
||||||
new LanguageDirector("/static").bindPath("/").on(server);
|
new Forward(INDEX).bindPath("/").on(server);
|
||||||
|
new Backend().bindPath("/api").on(server);
|
||||||
server.setExecutor(Executors.newCachedThreadPool());
|
server.setExecutor(Executors.newCachedThreadPool());
|
||||||
server.start();
|
server.start();
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'war'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
group = 'de.srsoftware'
|
group = 'de.srsoftware'
|
||||||
@@ -11,22 +10,11 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly 'jakarta.servlet:jakarta.servlet-api:6.1.0'
|
|
||||||
implementation 'ch.qos.logback:logback-core:1.5.6'
|
|
||||||
implementation 'ch.qos.logback:logback-classic:1.5.6'
|
|
||||||
implementation 'org.slf4j:slf4j-api:2.0.13'
|
|
||||||
implementation project(':de.srsoftware.oidc.api')
|
|
||||||
testImplementation platform('org.junit:junit-bom:5.10.0')
|
testImplementation platform('org.junit:junit-bom:5.10.0')
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter'
|
testImplementation 'org.junit.jupiter:junit-jupiter'
|
||||||
|
implementation project(':de.srsoftware.oidc.api')
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
|
|
||||||
war {
|
|
||||||
archiveFileName = 'oidc.war'
|
|
||||||
webAppDirName = 'src/main/resources'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/* © SRSoftware 2024 */
|
||||||
|
package de.srsoftware.oidc.backend;
|
||||||
|
|
||||||
|
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
|
||||||
|
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
import de.srsoftware.oidc.api.PathHandler;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class Backend extends PathHandler {
|
||||||
|
@Override
|
||||||
|
public void handle(HttpExchange ex) throws IOException {
|
||||||
|
String path = relativePath(ex);
|
||||||
|
String method = ex.getRequestMethod();
|
||||||
|
System.out.printf("%s %s…", method, path);
|
||||||
|
|
||||||
|
if ("login".equals(path)) {
|
||||||
|
doLogin(ex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var token = getAuthToken(ex);
|
||||||
|
if (token.isEmpty()) {
|
||||||
|
emptyResponse(HTTP_UNAUTHORIZED, ex);
|
||||||
|
System.err.println("unauthorized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
System.err.println("not implemented");
|
||||||
|
ex.sendResponseHeaders(HTTP_NOT_FOUND, 0);
|
||||||
|
ex.getResponseBody().close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doLogin(HttpExchange ex) throws IOException {
|
||||||
|
Optional<String> user = getHeader(ex, "login-username");
|
||||||
|
Optional<String> pass = getHeader(ex, "login-password");
|
||||||
|
System.out.printf("%s : %s", user, pass);
|
||||||
|
emptyResponse(HTTP_UNAUTHORIZED, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<String> getAuthToken(HttpExchange ex) {
|
||||||
|
return getHeader(ex, "Authorization");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
/* © SRSoftware 2024 */
|
|
||||||
package de.srsoftware.oidc.light;
|
|
||||||
|
|
||||||
public class Constants {
|
|
||||||
public static final String BODY = "body";
|
|
||||||
public static final String EMAIL = "email";
|
|
||||||
public static final String ERR_PAGE_NOT_FOUND = "page_not_found";
|
|
||||||
public static final String ERR_REDIRECT_FAILED = "redirect_failed";
|
|
||||||
public static final String HEAD = "head";
|
|
||||||
public static final String MESSAGES = "messages.txt";
|
|
||||||
public static final String PAGE_LOGIN = "login";
|
|
||||||
public static final String PAGE_START = "start";
|
|
||||||
public static final String PAGE_WELCOME = "welcome";
|
|
||||||
public static final String PASSWORD = "password";
|
|
||||||
public static final String TARGET = "target";
|
|
||||||
public static final String TITLE = "title";
|
|
||||||
public static final String TITLE_LOGIN = "title_login";
|
|
||||||
public static final String TITLE_WELCOME = "title_welcom";
|
|
||||||
public static final String USER = "user";
|
|
||||||
}
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
/* © SRSoftware 2024 */
|
|
||||||
package de.srsoftware.oidc.light;
|
|
||||||
|
|
||||||
import static de.srsoftware.oidc.light.Constants.*;
|
|
||||||
import static de.srsoftware.oidc.light.Templates.braced;
|
|
||||||
|
|
||||||
import de.srsoftware.oidc.api.User;
|
|
||||||
import jakarta.servlet.ServletException;
|
|
||||||
import jakarta.servlet.annotation.WebServlet;
|
|
||||||
import jakarta.servlet.http.HttpServlet;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import jakarta.servlet.http.HttpSession;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
@WebServlet("/web")
|
|
||||||
public class LightOICD extends HttpServlet {
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(LightOICD.class);
|
|
||||||
private static final Templates templates;
|
|
||||||
|
|
||||||
static {
|
|
||||||
try {
|
|
||||||
templates = Templates.singleton();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
|
|
||||||
var path = relativePath(req);
|
|
||||||
var optUser = loadUser(req);
|
|
||||||
handleGet(path, optUser, req, resp).ifPresent(resp.getWriter()::println);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
|
||||||
var path = relativePath(req);
|
|
||||||
var optUser = loadUser(req);
|
|
||||||
handlePost(path, optUser, req, resp).ifPresent(resp.getWriter()::println);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> relativePath(HttpServletRequest req) {
|
|
||||||
var cp = req.getContextPath();
|
|
||||||
var uri = req.getRequestURI();
|
|
||||||
if (uri.startsWith(cp)) uri = uri.substring(cp.length()); // strip context path → relative path!
|
|
||||||
var path = Arrays.stream(uri.split("/")).skip(1).collect(Collectors.toList());
|
|
||||||
if (path.isEmpty()) path.add(PAGE_START);
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<String> handleGet(List<String> path, Optional<User> optUser, HttpServletRequest req, HttpServletResponse resp) {
|
|
||||||
String token = path.remove(0);
|
|
||||||
if (optUser.isPresent()) {
|
|
||||||
var user = optUser.get();
|
|
||||||
switch (token) {
|
|
||||||
case PAGE_START:
|
|
||||||
return pageStart(user, req, resp);
|
|
||||||
case PAGE_WELCOME:
|
|
||||||
return pageWelcome(user, req, resp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (token) {
|
|
||||||
case PAGE_LOGIN:
|
|
||||||
return pageLogin(req, resp);
|
|
||||||
case PAGE_START:
|
|
||||||
case PAGE_WELCOME:
|
|
||||||
return redirect(resp, PAGE_LOGIN);
|
|
||||||
}
|
|
||||||
return templates.message(ERR_PAGE_NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Optional<String> handlePost(List<String> path, Optional<User> optUser, HttpServletRequest req, HttpServletResponse resp) {
|
|
||||||
String token = path.remove(0);
|
|
||||||
if (optUser.isPresent()) {
|
|
||||||
var user = optUser.get();
|
|
||||||
switch (token) {
|
|
||||||
case PAGE_START:
|
|
||||||
return pageStart(user, req, resp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (token) {
|
|
||||||
case PAGE_LOGIN:
|
|
||||||
return postLogin(req, resp);
|
|
||||||
case PAGE_START:
|
|
||||||
return redirect(resp, PAGE_LOGIN);
|
|
||||||
}
|
|
||||||
return templates.message(ERR_PAGE_NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<User> loadUser(HttpServletRequest req) {
|
|
||||||
HttpSession session = req.getSession();
|
|
||||||
if (session.getAttribute(USER) instanceof User user) return Optional.of(user);
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Optional<String> pageLogin(HttpServletRequest req, HttpServletResponse resp) {
|
|
||||||
LOG.debug("pageLogin(…)");
|
|
||||||
try {
|
|
||||||
var title = templates.message(TITLE_LOGIN).orElse(TITLE_LOGIN);
|
|
||||||
var head = templates.get("head.snippet", Map.of(TITLE, title)).get();
|
|
||||||
var login = templates.get("login.snippet", Map.of(USER, "Darling", EMAIL, "", PASSWORD, "")).get();
|
|
||||||
var page = templates.get("scaffold.html", Map.of(BODY, login, HEAD, head)).get();
|
|
||||||
resp.setContentType("text/html");
|
|
||||||
resp.getWriter().println(page);
|
|
||||||
return Optional.empty();
|
|
||||||
} catch (Exception e) {
|
|
||||||
return Optional.of(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<String> pageStart(User user, HttpServletRequest req, HttpServletResponse resp) {
|
|
||||||
LOG.debug("pageStart(…)");
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<String> pageWelcome(User user, HttpServletRequest req, HttpServletResponse resp) {
|
|
||||||
LOG.debug("pageWelcome(…)");
|
|
||||||
try {
|
|
||||||
var title = templates.message(TITLE_WELCOME).orElse(TITLE_WELCOME);
|
|
||||||
var head = templates.get("head.snippet", Map.of(TITLE, title)).get();
|
|
||||||
var login = templates.get("welcome.snippet", Map.of(USER, "Darling", EMAIL, "", PASSWORD, "")).get();
|
|
||||||
var page = templates.get("scaffold.html", Map.of(BODY, login, HEAD, head)).get();
|
|
||||||
resp.setContentType("text/html");
|
|
||||||
resp.getWriter().println(page);
|
|
||||||
return Optional.empty();
|
|
||||||
} catch (Exception e) {
|
|
||||||
return Optional.of(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<String> postLogin(HttpServletRequest req, HttpServletResponse resp) {
|
|
||||||
LOG.debug("postLogin(…)");
|
|
||||||
var email = req.getParameter(EMAIL);
|
|
||||||
if (braced(EMAIL).equals(email)) email = "";
|
|
||||||
var pass = req.getParameter(PASSWORD);
|
|
||||||
var user = tryLogin(email, pass);
|
|
||||||
if (user.isPresent()) {
|
|
||||||
req.getSession().setAttribute(USER, user.get());
|
|
||||||
return redirect(resp, PAGE_WELCOME);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
var title = templates.message(TITLE_LOGIN).orElse(TITLE_LOGIN);
|
|
||||||
var head = templates.get("head.snippet", Map.of(TITLE, title)).get();
|
|
||||||
var login = templates.get("login.snippet", Map.of(USER, "Darling", EMAIL, email, PASSWORD, "")).get();
|
|
||||||
var page = templates.get("scaffold.html", Map.of(BODY, login, HEAD, head)).get();
|
|
||||||
resp.setContentType("text/html");
|
|
||||||
resp.getWriter().println(page);
|
|
||||||
return Optional.empty();
|
|
||||||
} catch (Exception e) {
|
|
||||||
return Optional.of(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<User> tryLogin(String email, String pass) {
|
|
||||||
if (email == null || pass == null) return Optional.empty();
|
|
||||||
if (email.equals(pass)) return Optional.of(new User());
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<String> redirect(HttpServletResponse resp, String path) {
|
|
||||||
try {
|
|
||||||
resp.sendRedirect(path);
|
|
||||||
} catch (IOException e) {
|
|
||||||
return templates.message(ERR_REDIRECT_FAILED, Map.of(TARGET, path));
|
|
||||||
}
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
/* © SRSoftware 2024 */
|
|
||||||
package de.srsoftware.oidc.light;
|
|
||||||
|
|
||||||
import static de.srsoftware.oidc.light.Constants.MESSAGES;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
public class Templates {
|
|
||||||
private static Templates singleton = null;
|
|
||||||
private static Logger LOG = LoggerFactory.getLogger(Templates.class);
|
|
||||||
private Path dir = searchTemplates();
|
|
||||||
private Map<String, String> messages = null;
|
|
||||||
|
|
||||||
public Templates() throws FileNotFoundException {
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Path searchTemplates() throws FileNotFoundException {
|
|
||||||
return searchTemplates(new File(System.getProperty("user.dir"))).map(File::toPath).orElseThrow(() -> new FileNotFoundException("Missing template directory"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Optional<File> searchTemplates(File dir) {
|
|
||||||
if (dir.isDirectory()) {
|
|
||||||
var children = dir.listFiles();
|
|
||||||
for (File child : children) {
|
|
||||||
if (child.isDirectory()) {
|
|
||||||
if (child.getName().equals("templates")) return Optional.of(child);
|
|
||||||
var inner = searchTemplates(child);
|
|
||||||
if (inner.isPresent()) return inner;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Templates singleton() throws IOException {
|
|
||||||
if (singleton == null) singleton = new Templates();
|
|
||||||
return singleton;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<String> get(String path) {
|
|
||||||
return get(path, Map.of());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<String> get(String path, Map<String, String> replacements) {
|
|
||||||
var file = dir.resolve(path);
|
|
||||||
try {
|
|
||||||
return Optional.of(Files.readString(file)).map(s -> replaceKeys(s, replacements));
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.warn("Failed to read {}", path, e);
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String replaceKeys(String text, Map<String, String> replacements) {
|
|
||||||
for (Map.Entry<String, String> replacement : replacements.entrySet()) text = text.replace(braced(replacement.getKey()), replacement.getValue());
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<String> message(String code) {
|
|
||||||
return message(code, Map.of());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<String> message(String code, Map<String, String> replacements) {
|
|
||||||
if (this.messages == null) {
|
|
||||||
get(MESSAGES).map(s -> s.split("\n")).ifPresent(this::setMessages);
|
|
||||||
}
|
|
||||||
return Optional.ofNullable(messages.get(code)).map(text -> replaceKeys(text, replacements));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setMessages(String[] lines) {
|
|
||||||
this.messages = new HashMap<>();
|
|
||||||
for (String line : lines) {
|
|
||||||
var parts = line.split(" ", 2);
|
|
||||||
if (parts.length < 2) {
|
|
||||||
LOG.warn("Invalid format in {} file, skipped {}", MESSAGES, line);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
messages.put(parts[0], parts[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String braced(String key) {
|
|
||||||
return String.join(key, "{", "}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<meta charset="UTF-8">
|
|
||||||
<title>{title}</title>
|
|
||||||
<style>
|
|
||||||
body{
|
|
||||||
background-color: #555;
|
|
||||||
color: #eeffee;
|
|
||||||
}
|
|
||||||
label{
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
fieldset{
|
|
||||||
border-radius: 10px;
|
|
||||||
display: inline;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<html>
|
|
||||||
<body>
|
|
||||||
Hallo {user}, dies ist die Index-Seite!
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<h1>Login</h1>
|
|
||||||
|
|
||||||
<form action="login" method="POST">
|
|
||||||
<fieldset>
|
|
||||||
<legend>Light OIDC Login</legend>
|
|
||||||
<label>
|
|
||||||
<input type="text" name="email" value="{email}" />
|
|
||||||
Email address
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<input type="password" name="password" value="{password}" />
|
|
||||||
Password
|
|
||||||
</label>
|
|
||||||
<button type="submit">Login</button>
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
title_login LightOIDC Login
|
|
||||||
title_welcome Willkommen bei LightOIDC!
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
{head}
|
|
||||||
<body>
|
|
||||||
{body}
|
|
||||||
</html>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Willkommen!
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
/* © SRSoftware 2024 */
|
|
||||||
package de.srsoftware.oidc.server;
|
|
||||||
|
|
||||||
import com.sun.net.httpserver.Headers;
|
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
|
||||||
import de.srsoftware.oidc.api.PathHandler;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
public class LanguageDirector extends PathHandler {
|
|
||||||
private static final String DEFAULT_LANG = "de";
|
|
||||||
private final String path;
|
|
||||||
|
|
||||||
public LanguageDirector(String pathTo) {
|
|
||||||
path = pathTo;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handle(HttpExchange t) throws IOException {
|
|
||||||
Headers headers = t.getRequestHeaders();
|
|
||||||
String lang = headers.get("Accept-Language").stream().flatMap(s -> Arrays.stream(s.split(","))).findFirst().orElse(DEFAULT_LANG);
|
|
||||||
|
|
||||||
t.getResponseHeaders().add("Location", String.join(lang, "/static/", "/de/index.html"));
|
|
||||||
t.sendResponseHeaders(301, 0);
|
|
||||||
t.getResponseBody().close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/* © SRSoftware 2024 */
|
||||||
|
package de.srsoftware.oidc.web;
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
import de.srsoftware.oidc.api.PathHandler;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class Forward extends PathHandler {
|
||||||
|
private final int CODE = 302;
|
||||||
|
private final String toPath;
|
||||||
|
|
||||||
|
public Forward(String toPath) {
|
||||||
|
this.toPath = toPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(HttpExchange ex) throws IOException {
|
||||||
|
System.out.printf("Forwarding (%d) %s to %s…\n", CODE, ex.getRequestURI(), toPath);
|
||||||
|
ex.getResponseHeaders().add("Location", toPath);
|
||||||
|
ex.sendResponseHeaders(CODE, 0);
|
||||||
|
ex.getResponseBody().close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,40 +9,42 @@ import java.io.OutputStream;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public class StaticPages extends PathHandler {
|
public class StaticPages extends PathHandler {
|
||||||
private static final String DEFAULT_LANG = "en";
|
private static final String DEFAULT_LANGUAGE = "en";
|
||||||
private ClassLoader loader;
|
private ClassLoader loader;
|
||||||
|
|
||||||
private record Response(String contentType, byte[] content) {
|
private record Response(String contentType, byte[] content) {
|
||||||
}
|
}
|
||||||
private static final String INDEX = "de/index.html";
|
private static final String INDEX = "en/index.html";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(HttpExchange ex) throws IOException {
|
public void handle(HttpExchange ex) throws IOException {
|
||||||
String path = relativePath(ex);
|
String path = relativePath(ex);
|
||||||
|
String lang = language(ex).orElse(DEFAULT_LANGUAGE);
|
||||||
|
String method = ex.getRequestMethod();
|
||||||
|
|
||||||
if (path.isBlank()) path = INDEX;
|
if (path.isBlank()) path = INDEX;
|
||||||
|
System.out.printf("%s %s: ", method, ex.getRequestURI());
|
||||||
try {
|
try {
|
||||||
var response = loadTemplate(path).orElseThrow(() -> new FileNotFoundException());
|
System.out.printf("Loading %s for lagnuage %s…", path, lang);
|
||||||
|
var response = loadTemplate(lang, path).orElseThrow(() -> new FileNotFoundException());
|
||||||
|
|
||||||
ex.getResponseHeaders().add("Content-Type", response.contentType);
|
ex.getResponseHeaders().add("Content-Type", response.contentType);
|
||||||
ex.sendResponseHeaders(200, response.content.length);
|
ex.sendResponseHeaders(200, response.content.length);
|
||||||
OutputStream os = ex.getResponseBody();
|
OutputStream os = ex.getResponseBody();
|
||||||
os.write(response.content);
|
os.write(response.content);
|
||||||
os.close();
|
os.close();
|
||||||
|
System.out.println("success.");
|
||||||
} catch (FileNotFoundException fnf) {
|
} catch (FileNotFoundException fnf) {
|
||||||
ex.sendResponseHeaders(404, 0);
|
ex.sendResponseHeaders(404, 0);
|
||||||
ex.getResponseBody().close();
|
ex.getResponseBody().close();
|
||||||
|
System.err.println("failed!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<Response> loadTemplate(String path) throws IOException {
|
private Optional<Response> loadTemplate(String language, String path) throws IOException {
|
||||||
if (loader == null) loader = getClass().getClassLoader();
|
if (loader == null) loader = getClass().getClassLoader();
|
||||||
var resource = loader.getResource(path);
|
var resource = loader.getResource(String.join("/", language, path));
|
||||||
if (resource == null) {
|
if (resource == null) resource = loader.getResource(String.join("/", DEFAULT_LANGUAGE, path));
|
||||||
var parts = path.split("/");
|
|
||||||
parts[0] = DEFAULT_LANG;
|
|
||||||
path = String.join("/", parts);
|
|
||||||
resource = loader.getResource(path);
|
|
||||||
}
|
|
||||||
if (resource == null) return Optional.empty();
|
if (resource == null) return Optional.empty();
|
||||||
var connection = resource.openConnection();
|
var connection = resource.openConnection();
|
||||||
var contentType = connection.getContentType();
|
var contentType = connection.getContentType();
|
||||||
|
|||||||
1
de.srsoftware.oidc.web/src/main/resources/en/config.js
Normal file
1
de.srsoftware.oidc.web/src/main/resources/en/config.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
var api = "/api";
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>{title}</title>
|
<title>Light OIDC</title>
|
||||||
<script src="lightoidc.js" />
|
<script src="config.js"></script>
|
||||||
|
<script src="lightoidc.js"></script>
|
||||||
|
<script>
|
||||||
|
checkUser();
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{body}
|
<h1>Welcome!</h1>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
25
de.srsoftware.oidc.web/src/main/resources/en/lightoidc.js
Normal file
25
de.srsoftware.oidc.web/src/main/resources/en/lightoidc.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
const UNAUTHORIZED = 401;
|
||||||
|
|
||||||
|
function handleCheckUser(response){
|
||||||
|
console.log(window.location.href);
|
||||||
|
if (response.status == UNAUTHORIZED){
|
||||||
|
window.location.href = "login.html";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function checkUser(){
|
||||||
|
fetch(api+"/user")
|
||||||
|
.then(handleCheckUser)
|
||||||
|
.catch((err) => console.log(err));
|
||||||
|
}
|
||||||
|
function submitForm(formId){
|
||||||
|
var data = Object.fromEntries(new FormData(document.getElementById(formId)));
|
||||||
|
fetch(api+"/login",{
|
||||||
|
headers: {
|
||||||
|
'login-username': data.user,
|
||||||
|
'login-password': data.pass, // TODO: send via body?
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
24
de.srsoftware.oidc.web/src/main/resources/en/login.html
Normal file
24
de.srsoftware.oidc.web/src/main/resources/en/login.html
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Light OIDC</title>
|
||||||
|
<script src="config.js"></script>
|
||||||
|
<script src="lightoidc.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Login</h1>
|
||||||
|
<form id="form">
|
||||||
|
<fieldset>
|
||||||
|
<legend>User credentials</legend>
|
||||||
|
<label>
|
||||||
|
Username
|
||||||
|
<input type="text" name="user" />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Password
|
||||||
|
<input type="password" name="pass" />
|
||||||
|
</label>
|
||||||
|
<button type="button" onClick="submitForm('form')">Login</button>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
rootProject.name = 'LightOIDC'
|
rootProject.name = 'LightOIDC'
|
||||||
include 'de.srsoftware.oidc.api'
|
include 'de.srsoftware.oidc.api'
|
||||||
include 'de.srsoftware.oidc.light'
|
include 'de.srsoftware.oidc.app'
|
||||||
include 'de.srsoftware.oidc.server'
|
|
||||||
include 'de.srsoftware.oidc.web'
|
include 'de.srsoftware.oidc.web'
|
||||||
|
include 'de.srsoftware.oidc.backend'
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user