Browse Source

improved UI

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
Stephan Richter 4 months ago
parent
commit
8cccd8b4e8
  1. 184
      de.srsoftware.http/src/main/java/de/srsoftware/http/PathHandler.java
  2. 46
      de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java
  3. 2
      de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/ClientController.java
  4. 179
      de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteMailConfig.java
  5. 4
      de.srsoftware.oidc.web/src/main/resources/de/reset.html
  6. 5
      de.srsoftware.oidc.web/src/main/resources/en/reset.html
  7. 14
      de.srsoftware.oidc.web/src/main/resources/en/scripts/index.js
  8. 12
      de.srsoftware.oidc.web/src/main/resources/en/scripts/reset.js
  9. 1
      settings.gradle

184
de.srsoftware.http/src/main/java/de/srsoftware/http/PathHandler.java

@ -76,9 +76,9 @@ public abstract class PathHandler implements HttpHandler { @@ -76,9 +76,9 @@ public abstract class PathHandler implements HttpHandler {
String method = ex.getRequestMethod();
LOG.log(INFO, "{0} {1}", method, path);
boolean ignored = switch (method) {
case DELETE -> doDelete(path,ex);
case GET -> doGet(path,ex);
case POST -> doPost(path,ex);
case DELETE -> doDelete(path, ex);
case GET -> doGet(path, ex);
case POST -> doPost(path, ex);
default -> false;
};
ex.getResponseBody().close();
@ -86,116 +86,116 @@ public abstract class PathHandler implements HttpHandler { @@ -86,116 +86,116 @@ public abstract class PathHandler implements HttpHandler {
public String relativePath(HttpExchange ex) {
var requestPath = ex.getRequestURI().toString();
for (var path : paths){
if (requestPath.startsWith(path)) {
requestPath = requestPath.substring(path.length());
break;
}
}
if (!requestPath.startsWith("/")) requestPath = "/" + requestPath;
var pos = requestPath.indexOf("?");
if (pos >= 0) requestPath = requestPath.substring(0, pos);
return requestPath;
}
for (var path : paths) {
if (requestPath.startsWith(path)) {
requestPath = requestPath.substring(path.length());
break;
}
}
if (!requestPath.startsWith("/")) requestPath = "/" + requestPath;
var pos = requestPath.indexOf("?");
if (pos >= 0) requestPath = requestPath.substring(0, pos);
return requestPath;
}
/******* begin of static methods *************/
/******* begin of static methods *************/
public static String body(HttpExchange ex) throws IOException {
return new String(ex.getRequestBody().readAllBytes(), UTF_8);
}
public static String body(HttpExchange ex) throws IOException {
return new String(ex.getRequestBody().readAllBytes(), UTF_8);
}
public static Optional<String> getAuthToken(HttpExchange ex) {
return getHeader(ex, AUTHORIZATION);
}
public static Optional<String> getAuthToken(HttpExchange ex) {
return getHeader(ex, AUTHORIZATION);
}
public static Optional<BasicAuth> getBasicAuth(HttpExchange ex) {
return getAuthToken(ex)
.filter(token -> token.startsWith("Basic ")) //
.map(token -> token.substring(6))
.map(Base64.getDecoder()::decode)
.map(bytes -> new String(bytes, UTF_8))
.map(token -> token.split(":", 2))
.map(arr -> new BasicAuth(arr[0], arr[1]));
}
public static Optional<BasicAuth> getBasicAuth(HttpExchange ex) {
return getAuthToken(ex)
.filter(token -> token.startsWith("Basic ")) //
.map(token -> token.substring(6))
.map(Base64.getDecoder()::decode)
.map(bytes -> new String(bytes, UTF_8))
.map(token -> token.split(":", 2))
.map(arr -> new BasicAuth(arr[0], arr[1]));
}
public static Optional<String> getBearer(HttpExchange ex) {
return getAuthToken(ex).filter(token -> token.startsWith("Bearer ")).map(token -> token.substring(7));
}
public static Optional<String> getBearer(HttpExchange ex) {
return getAuthToken(ex).filter(token -> token.startsWith("Bearer ")).map(token -> token.substring(7));
}
public static Optional<String> getHeader(HttpExchange ex, String key) {
return nullable(ex.getRequestHeaders().get(key)).map(List::stream).flatMap(Stream::findFirst);
}
public static Optional<String> getHeader(HttpExchange ex, String key) {
return nullable(ex.getRequestHeaders().get(key)).map(List::stream).flatMap(Stream::findFirst);
}
public static String hostname(HttpExchange ex) {
var headers = ex.getRequestHeaders();
var host = headers.getFirst(FORWARDED_HOST);
if (host == null) host = headers.getFirst(HOST);
var proto = nullable(headers.getFirst("X-forwarded-proto")).orElseGet(() -> ex instanceof HttpsExchange ? "https" : "http");
return host == null ? null : proto + "://" + host;
}
public static String hostname(HttpExchange ex) {
var headers = ex.getRequestHeaders();
var host = headers.getFirst(FORWARDED_HOST);
if (host == null) host = headers.getFirst(HOST);
var proto = nullable(headers.getFirst("X-forwarded-proto")).orElseGet(() -> ex instanceof HttpsExchange ? "https" : "http");
return host == null ? null : proto + "://" + host;
}
public static JSONObject json(HttpExchange ex) throws IOException {
return new JSONObject(body(ex));
}
public static JSONObject json(HttpExchange ex) throws IOException {
return new JSONObject(body(ex));
}
public static String language(HttpExchange ex) {
return getHeader(ex, "Accept-Language") //
.map(s -> Arrays.stream(s.split(",")))
.flatMap(Stream::findFirst)
.orElse(DEFAULT_LANGUAGE);
}
public static String language(HttpExchange ex) {
return getHeader(ex, "Accept-Language") //
.map(s -> Arrays.stream(s.split(",")))
.flatMap(Stream::findFirst)
.orElse(DEFAULT_LANGUAGE);
}
public static boolean notFound(HttpExchange ex) throws IOException {
LOG.log(ERROR, "not implemented");
return sendEmptyResponse(HTTP_NOT_FOUND, ex);
}
public static boolean notFound(HttpExchange ex) throws IOException {
LOG.log(ERROR, "not implemented");
return sendEmptyResponse(HTTP_NOT_FOUND, ex);
}
public Map<String, String> queryParam(HttpExchange ex) {
return Arrays
.stream(ex.getRequestURI().getQuery().split("&")) //
.map(s -> s.split("=", 2))
.collect(Collectors.toMap(arr -> arr[0], arr -> arr[1]));
}
public Map<String, String> queryParam(HttpExchange ex) {
return Arrays
.stream(ex.getRequestURI().getQuery().split("&")) //
.map(s -> s.split("=", 2))
.collect(Collectors.toMap(arr -> arr[0], arr -> arr[1]));
}
public static boolean sendEmptyResponse(int statusCode, HttpExchange ex) throws IOException {
ex.sendResponseHeaders(statusCode, 0);
return false;
}
public static boolean sendEmptyResponse(int statusCode, HttpExchange ex) throws IOException {
ex.sendResponseHeaders(statusCode, 0);
return false;
}
public static boolean sendRedirect(HttpExchange ex, String url) throws IOException {
ex.getResponseHeaders().add("Location", url);
return sendEmptyResponse(HTTP_MOVED_TEMP, ex);
}
public static boolean sendRedirect(HttpExchange ex, String url) throws IOException {
ex.getResponseHeaders().add("Location", url);
return sendEmptyResponse(HTTP_MOVED_TEMP, ex);
}
public static boolean sendContent(HttpExchange ex, int status, byte[] bytes) throws IOException {
LOG.log(DEBUG, "sending {0} response…", status);
ex.sendResponseHeaders(status, bytes.length);
ex.getResponseBody().write(bytes);
return true;
}
public static boolean sendContent(HttpExchange ex, int status, byte[] bytes) throws IOException {
LOG.log(DEBUG, "sending {0} response…", status);
ex.sendResponseHeaders(status, bytes.length);
ex.getResponseBody().write(bytes);
return true;
}
public static boolean sendContent(HttpExchange ex, int status, Object o) throws IOException {
if (o instanceof List<?> list) o = new JSONArray(list);
public static boolean sendContent(HttpExchange ex, int status, Object o) throws IOException {
if (o instanceof List<?> list) o = new JSONArray(list);
if (o instanceof Map<?, ?> map) o = new JSONObject(map);
if (o instanceof Error<?> error) o = error.json();
return sendContent(ex, status, o.toString().getBytes(UTF_8));
}
}
public static boolean sendContent(HttpExchange ex, byte[] bytes) throws IOException {
return sendContent(ex, HTTP_OK, bytes);
}
public static boolean sendContent(HttpExchange ex, byte[] bytes) throws IOException {
return sendContent(ex, HTTP_OK, bytes);
}
public static boolean sendContent(HttpExchange ex, Object o) throws IOException {
return sendContent(ex, HTTP_OK, o);
}
public static boolean sendContent(HttpExchange ex, Object o) throws IOException {
return sendContent(ex, HTTP_OK, o);
}
public static boolean serverError(HttpExchange ex, Object o) throws IOException {
sendContent(ex, HTTP_INTERNAL_ERROR, o);
return false;
}
public static boolean serverError(HttpExchange ex, Object o) throws IOException {
sendContent(ex, HTTP_INTERNAL_ERROR, o);
return false;
}
public static String url(HttpExchange ex) {
return hostname(ex) + ex.getRequestURI();
}
public static String url(HttpExchange ex) {
return hostname(ex) + ex.getRequestURI();
}
}

46
de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java

@ -106,7 +106,7 @@ public class Application { @@ -106,7 +106,7 @@ public class Application {
private static AuthorizationService setupAuthService(Configuration config, Path defaultFile, FileStoreProvider fileStoreProvider) throws SQLException {
var authServiceLocation = new File(config.getOrDefault("auth_store", defaultFile));
return switch (extension(authServiceLocation)){
return switch (extension(authServiceLocation)) {
case "db", "sqlite", "sqlite3" -> new SqliteAuthService(connectionProvider.get(authServiceLocation));
default -> fileStoreProvider.get(authServiceLocation);
};
@ -114,17 +114,17 @@ public class Application { @@ -114,17 +114,17 @@ public class Application {
private static SessionService setupSessionService(Configuration config, Path defaultFile, FileStoreProvider fileStoreProvider) throws SQLException {
var sessionStore = new File(config.getOrDefault("session_storage", defaultFile));
return switch (extension(sessionStore)){
return switch (extension(sessionStore)) {
case "db", "sqlite", "sqlite3" -> new SqliteSessionService(connectionProvider.get(sessionStore));
default -> fileStoreProvider.get(sessionStore);
};
}
private static MailConfig setupMailConfig(Configuration config, Path defaultFile, FileStoreProvider fileStoreProvider) throws SQLException {
var mailConfigLocation = new File(config.getOrDefault("mail_config_storage", defaultFile));
var mailConfig = switch (extension(mailConfigLocation)){
case "db", "sqlite", "sqlite3" -> new SqliteMailConfig(connectionProvider.get(mailConfigLocation));
default -> fileStoreProvider.get(mailConfigLocation);
var mailConfigLocation = new File(config.getOrDefault("mail_config_storage", defaultFile));
var mailConfig = switch (extension(mailConfigLocation)) {
case "db", "sqlite", "sqlite3" -> new SqliteMailConfig(connectionProvider.get(mailConfigLocation));
default -> fileStoreProvider.get(mailConfigLocation);
};
Optional<String> encryptionKey = config.get(ENCRYPTION_KEY);
@ -137,10 +137,10 @@ public class Application { @@ -137,10 +137,10 @@ public class Application {
}
private static UserService setupUserService(Configuration config, Path defaultFile, FileStoreProvider fileStoreProvider, UuidHasher passHasher) throws SQLException {
var userStorageLocation = new File(config.getOrDefault("user_storage", defaultFile));
var userService = switch (extension(userStorageLocation).toLowerCase()){
case "db", "sqlite", "sqlite3" -> new SqliteUserService(connectionProvider.get(userStorageLocation),passHasher);
default -> fileStoreProvider.get(userStorageLocation);
var userStorageLocation = new File(config.getOrDefault("user_storage", defaultFile));
var userService = switch (extension(userStorageLocation).toLowerCase()) {
case "db", "sqlite", "sqlite3" -> new SqliteUserService(connectionProvider.get(userStorageLocation), passHasher);
default -> fileStoreProvider.get(userStorageLocation);
};
Optional<String> encryptionKey = config.get(ENCRYPTION_KEY);
@ -154,7 +154,7 @@ public class Application { @@ -154,7 +154,7 @@ public class Application {
private static KeyStorage setupKeyStore(Configuration config, Path defaultConfigDir) throws SQLException {
var keyStorageLocation = new File(config.getOrDefault("key_storage", defaultConfigDir.resolve("keys")));
KeyStorage keyStore = null;
KeyStorage keyStore = null;
if ((keyStorageLocation.exists() && keyStorageLocation.isDirectory()) || !keyStorageLocation.getName().contains(".")) {
keyStore = new PlaintextKeyStore(keyStorageLocation.toPath());
} else { // SQLite
@ -183,18 +183,18 @@ public class Application { @@ -183,18 +183,18 @@ public class Application {
var token = tokens.remove(0);
switch (token) {
case "--base":
if (tokens.isEmpty()) throw new IllegalArgumentException("--base option requires second argument!");
map.put(BASE_PATH, Path.of(tokens.remove(0)));
break;
case "--config":
if (tokens.isEmpty()) throw new IllegalArgumentException("--config option requires second argument!");
map.put(CONFIG_PATH, Path.of(tokens.remove(0)));
break;
default:
LOG.log(ERROR, "Unknown option: {0}", token);
if (tokens.isEmpty()) throw new IllegalArgumentException("--base option requires second argument!");
map.put(BASE_PATH, Path.of(tokens.remove(0)));
break;
case "--config":
if (tokens.isEmpty()) throw new IllegalArgumentException("--config option requires second argument!");
map.put(CONFIG_PATH, Path.of(tokens.remove(0)));
break;
default:
LOG.log(ERROR, "Unknown option: {0}", token);
}
}
}
return map;
}
return map;
}
}

2
de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/ClientController.java

@ -158,7 +158,7 @@ public class ClientController extends Controller { @@ -158,7 +158,7 @@ public class ClientController extends Controller {
.stream()
.map(clients::getClient)
.flatMap(Optional::stream)
.sorted(Comparator.comparing(Client::name))
.sorted(Comparator.comparing(Client::name, String.CASE_INSENSITIVE_ORDER))
.map(Client::safeMap)
.toList();
return sendContent(ex, Map.of(AUTHORZED, authorizedClients, NAME, user.realName()));

179
de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteMailConfig.java

@ -70,114 +70,113 @@ public class SqliteMailConfig extends SqliteStore implements MailConfig { @@ -70,114 +70,113 @@ public class SqliteMailConfig extends SqliteStore implements MailConfig {
try {
switch (currentVersion) {
case 0:
createStoreTables();
break;
createStoreTables();
break;
}
stmt.setInt(1, ++currentVersion);
stmt.execute();
conn.commit();
} catch (Exception e) {
conn.rollback();
LOG.log(System.Logger.Level.ERROR, "Failed to update at {} = {}", STORE_VERSION, currentVersion);
break;
}
stmt.setInt(1, ++currentVersion);
stmt.execute();
conn.commit();
}
catch (Exception e) {
conn.rollback();
LOG.log(System.Logger.Level.ERROR, "Failed to update at {} = {}", STORE_VERSION, currentVersion);
break;
}
conn.setAutoCommit(true);
}
conn.setAutoCommit(true);
}
@Override
public String smtpHost() {
return smtpHost;
}
@Override
public String smtpHost() {
return smtpHost;
}
@Override
public MailConfig smtpHost(String newValue) {
smtpHost = newValue;
return this;
}
@Override
public MailConfig smtpHost(String newValue) {
smtpHost = newValue;
return this;
}
@Override
public int smtpPort() {
return smtpPort;
}
@Override
public int smtpPort() {
return smtpPort;
}
@Override
public MailConfig smtpPort(int newValue) {
smtpPort = newValue;
return this;
}
@Override
public MailConfig smtpPort(int newValue) {
smtpPort = newValue;
return this;
}
@Override
public String senderAddress() {
return senderAddress;
}
@Override
public String senderAddress() {
return senderAddress;
}
@Override
public MailConfig senderAddress(String newValue) {
senderAddress = newValue;
return this;
}
@Override
public MailConfig senderAddress(String newValue) {
senderAddress = newValue;
return this;
}
@Override
public String senderPassword() {
return password;
}
@Override
public String senderPassword() {
return password;
}
@Override
public MailConfig senderPassword(String newValue) {
password = newValue;
return this;
}
@Override
public MailConfig senderPassword(String newValue) {
password = newValue;
return this;
}
@Override
public boolean startTls() {
return startTls;
}
@Override
public boolean startTls() {
return startTls;
}
@Override
public boolean smtpAuth() {
return smtpAuth;
}
@Override
public boolean smtpAuth() {
return smtpAuth;
}
@Override
public MailConfig startTls(boolean newValue) {
startTls = newValue;
return this;
}
@Override
public MailConfig startTls(boolean newValue) {
startTls = newValue;
return this;
}
@Override
public MailConfig smtpAuth(boolean newValue) {
smtpAuth = newValue;
return this;
}
@Override
public MailConfig smtpAuth(boolean newValue) {
smtpAuth = newValue;
return this;
}
@Override
public Authenticator authenticator() {
if (auth == null) {
auth = new Authenticator() {
// override the getPasswordAuthentication method
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(senderAddress(), senderPassword());
}
};
@Override
public Authenticator authenticator() {
if (auth == null) {
auth = new Authenticator() {
// override the getPasswordAuthentication method
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(senderAddress(), senderPassword());
}
};
}
return auth;
}
return auth;
}
@Override
public MailConfig save() {
try {
var stmt = conn.prepareStatement(SAVE_MAILCONFIG);
for (var entry : map().entrySet()) {
stmt.setString(1, entry.getKey());
stmt.setObject(2, entry.getValue());
stmt.setObject(3, entry.getValue());
stmt.execute();
@Override
public MailConfig save() {
try {
var stmt = conn.prepareStatement(SAVE_MAILCONFIG);
for (var entry : map().entrySet()) {
stmt.setString(1, entry.getKey());
stmt.setObject(2, entry.getValue());
stmt.setObject(3, entry.getValue());
stmt.execute();
}
return this;
} catch (SQLException e) {
throw new RuntimeException(e);
}
return this;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}

4
de.srsoftware.oidc.web/src/main/resources/de/reset.html

@ -42,6 +42,10 @@ @@ -42,6 +42,10 @@
<th>Error</th>
<td class="warning">Zugriffs-Token gefunden, ist aber ungültig!</td>
</tr>
<tr id="other_error" style="display: none">
<th>Error</th>
<td class="warning" id="other_error_text">Unbekannter Fehler!</td>
</tr>
<tr>
<td></td>
<td><button id="passBtn" type="button" onClick="updatePass()">Aktualisieren</button></td>

5
de.srsoftware.oidc.web/src/main/resources/en/reset.html

@ -42,6 +42,11 @@ @@ -42,6 +42,11 @@
<th>Error</th>
<td class="warning">I received an access token, but it is invalid!</td>
</tr>
<tr id="other_error" style="display: none">
<th>Error</th>
<td class="warning" id="other_error_text">Unknown error!</td>
</tr>
<tr>
<td></td>
<td><button id="passBtn" type="button" onClick="updatePass()">Update</button></td>

14
de.srsoftware.oidc.web/src/main/resources/en/scripts/index.js

@ -11,12 +11,18 @@ function handleDash(response){ @@ -11,12 +11,18 @@ function handleDash(response){
var clients = data.authorized;
var content = document.getElementById('content');
var any = false;
var lastLetter = null;
for (let id in clients){
var client = clients[id];
if (client.landing_page){
var div = document.createElement("div");
div.innerHTML = `<button onclick="window.location.href='${client.landing_page}';">${client.name}</button>`;
content.append(div);
if (client.landing_page){
var initialLetter = client.name.charAt(0).toUpperCase();
if (initialLetter != lastLetter) {
if (lastLetter) content.append(document.createElement("br"));
lastLetter = initialLetter;
}
var span = document.createElement("span");
span.innerHTML = `<button onclick="window.location.href='${client.landing_page}';">${client.name}</button>`;
content.append(span);
any = true;
}
}

12
de.srsoftware.oidc.web/src/main/resources/en/scripts/reset.js

@ -10,10 +10,14 @@ function handlePasswordResponse(response){ @@ -10,10 +10,14 @@ function handlePasswordResponse(response){
} else {
setText('passBtn', 'Update failed!');
response.text().then(text => {
if (text == 'invalid token') show('invalid_token');
if (text == 'token missing') show('missing_token');
if (text == 'password mismatch') show('password_mismatch');
if (text == 'weak password') show('weak_password');
if (text == 'invalid token') show('invalid_token'); else
if (text == 'token missing') show('missing_token'); else
if (text == 'password mismatch') show('password_mismatch'); else
if (text == 'weak password') show('weak_password'); else {
setText('other_error_text',text);
show('other_error');
}
});
}
enable('passBtn');

1
settings.gradle

@ -9,4 +9,5 @@ include 'de.srsoftware.oidc.datastore.encrypted' @@ -9,4 +9,5 @@ include 'de.srsoftware.oidc.datastore.encrypted'
include 'de.srsoftware.oidc.datastore.file'
include 'de.srsoftware.oidc.web'
include 'de.srsoftware.utils'
include 'de.srsoftware.slf4j2syslog'

Loading…
Cancel
Save