From 94aeaa86500d376a1141c34692883b2540a21931 Mon Sep 17 00:00:00 2001
From: Stephan Richter <s.richter@srsoftware.de>
Date: Tue, 17 Dec 2024 00:59:45 +0100
Subject: [PATCH] moved various modules and classes to tools realm, replaced
 gradle buildscripts by gradle kotlin buildscripts

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
---
 build.gradle                                  |  36 ----
 build.gradle.kts                              |  50 +++++
 de.srsoftware.http/build.gradle               |  21 --
 .../main/java/de/srsoftware/http/Cookie.java  |  53 -----
 .../java/de/srsoftware/http/PathHandler.java  | 202 ------------------
 .../java/de/srsoftware/http/SessionToken.java |  51 -----
 de.srsoftware.logging/build.gradle            |  19 --
 .../de/srsoftware/logging/ColorLogger.java    |  65 ------
 .../srsoftware/logging/ColorLoggerFinder.java |  14 --
 .../de/srsoftware/logging/ConsoleColors.java  |  77 -------
 .../services/java.lang.System$LoggerFinder    |   1 -
 de.srsoftware.oidc.api/build.gradle           |  19 +-
 .../de/srsoftware/oidc/api/UserService.java   |   2 +-
 .../de/srsoftware/oidc/api/data/Client.java   |   2 +-
 .../srsoftware/oidc/api/AuthServiceTest.java  |   2 +-
 .../oidc/api/ClientServiceTest.java           |   2 +-
 .../de/srsoftware/oidc/api/KeyStoreTest.java  |   2 +-
 .../srsoftware/oidc/api/MailConfigTest.java   |   2 +-
 .../oidc/api/SessionServiceTest.java          |   6 +-
 .../srsoftware/oidc/api/UserServiceTest.java  |   4 +-
 de.srsoftware.oidc.app/build.gradle           |  47 ----
 de.srsoftware.oidc.app/build.gradle.kts       |  14 ++
 .../de/srsoftware/oidc/app/Application.java   |  16 +-
 de.srsoftware.oidc.backend/build.gradle       |  27 ---
 de.srsoftware.oidc.backend/build.gradle.kts   |  13 ++
 .../oidc/backend/ClientController.java        |   4 +-
 .../srsoftware/oidc/backend/Controller.java   |   4 +-
 .../oidc/backend/KeyStoreController.java      |   2 +-
 .../oidc/backend/RotatingKeyManager.java      |   4 +-
 .../oidc/backend/TokenController.java         |   6 +-
 .../oidc/backend/UserController.java          |   8 +-
 .../oidc/backend/WellKnownController.java     |   2 +-
 .../build.gradle                              |  24 ---
 .../build.gradle.kts                          |  11 +
 .../encrypted/EncryptedUserService.java       |   8 +-
 .../EncryptedClientServiceTest.java           |   6 +-
 .../encrypted}/EncryptedConfigTest.java       |   4 +-
 .../encrypted}/EncryptedKeyStoreTest.java     |   4 +-
 .../encrypted}/EncryptedMailConfigTest.java   |   4 +-
 .../encrypted}/EncryptedUserServiceTest.java  |  14 +-
 .../build.gradle                              |  26 ---
 .../build.gradle.kts                          |  13 ++
 .../oidc/datastore/file/FileStore.java        |  12 +-
 .../datastore/file/FileStoreProvider.java     |   2 +-
 .../file/FileStoreUserServiceTest.java        |   4 +-
 de.srsoftware.oidc.web/build.gradle           |  21 --
 de.srsoftware.oidc.web/build.gradle.kts       |   8 +
 .../java/de/srsoftware/oidc/web/Forward.java  |   2 +-
 .../de/srsoftware/oidc/web/StaticPages.java   |   2 +-
 de.srsoftware.utils/build.gradle              |  20 --
 .../main/java/de/srsoftware/utils/Error.java  |  40 ----
 .../java/de/srsoftware/utils/Optionals.java   |  15 --
 .../de/srsoftware/utils/PasswordHasher.java   |  11 -
 .../main/java/de/srsoftware/utils/Paths.java  |  25 ---
 .../java/de/srsoftware/utils/Payload.java     |  24 ---
 .../main/java/de/srsoftware/utils/Result.java |   6 -
 .../java/de/srsoftware/utils/Strings.java     |  10 -
 .../java/de/srsoftware/utils/UuidHasher.java  |  38 ----
 settings.gradle                               |  11 -
 settings.gradle.kts                           |   8 +
 60 files changed, 187 insertions(+), 963 deletions(-)
 delete mode 100644 build.gradle
 create mode 100644 build.gradle.kts
 delete mode 100644 de.srsoftware.http/build.gradle
 delete mode 100644 de.srsoftware.http/src/main/java/de/srsoftware/http/Cookie.java
 delete mode 100644 de.srsoftware.http/src/main/java/de/srsoftware/http/PathHandler.java
 delete mode 100644 de.srsoftware.http/src/main/java/de/srsoftware/http/SessionToken.java
 delete mode 100644 de.srsoftware.logging/build.gradle
 delete mode 100644 de.srsoftware.logging/src/main/java/de/srsoftware/logging/ColorLogger.java
 delete mode 100644 de.srsoftware.logging/src/main/java/de/srsoftware/logging/ColorLoggerFinder.java
 delete mode 100644 de.srsoftware.logging/src/main/java/de/srsoftware/logging/ConsoleColors.java
 delete mode 100644 de.srsoftware.logging/src/main/resources/META-INF/services/java.lang.System$LoggerFinder
 delete mode 100644 de.srsoftware.oidc.app/build.gradle
 create mode 100644 de.srsoftware.oidc.app/build.gradle.kts
 delete mode 100644 de.srsoftware.oidc.backend/build.gradle
 create mode 100644 de.srsoftware.oidc.backend/build.gradle.kts
 delete mode 100644 de.srsoftware.oidc.datastore.encrypted/build.gradle
 create mode 100644 de.srsoftware.oidc.datastore.encrypted/build.gradle.kts
 rename de.srsoftware.oidc.datastore.encrypted/src/test/java/{ => de/srsoftware/oidc/datastore/encrypted}/EncryptedClientServiceTest.java (88%)
 rename de.srsoftware.oidc.datastore.encrypted/src/test/java/{ => de/srsoftware/oidc/datastore/encrypted}/EncryptedConfigTest.java (81%)
 rename de.srsoftware.oidc.datastore.encrypted/src/test/java/{ => de/srsoftware/oidc/datastore/encrypted}/EncryptedKeyStoreTest.java (90%)
 rename de.srsoftware.oidc.datastore.encrypted/src/test/java/{ => de/srsoftware/oidc/datastore/encrypted}/EncryptedMailConfigTest.java (95%)
 rename de.srsoftware.oidc.datastore.encrypted/src/test/java/{ => de/srsoftware/oidc/datastore/encrypted}/EncryptedUserServiceTest.java (91%)
 delete mode 100644 de.srsoftware.oidc.datastore.file/build.gradle
 create mode 100644 de.srsoftware.oidc.datastore.file/build.gradle.kts
 delete mode 100644 de.srsoftware.oidc.web/build.gradle
 create mode 100644 de.srsoftware.oidc.web/build.gradle.kts
 delete mode 100644 de.srsoftware.utils/build.gradle
 delete mode 100644 de.srsoftware.utils/src/main/java/de/srsoftware/utils/Error.java
 delete mode 100644 de.srsoftware.utils/src/main/java/de/srsoftware/utils/Optionals.java
 delete mode 100644 de.srsoftware.utils/src/main/java/de/srsoftware/utils/PasswordHasher.java
 delete mode 100644 de.srsoftware.utils/src/main/java/de/srsoftware/utils/Paths.java
 delete mode 100644 de.srsoftware.utils/src/main/java/de/srsoftware/utils/Payload.java
 delete mode 100644 de.srsoftware.utils/src/main/java/de/srsoftware/utils/Result.java
 delete mode 100644 de.srsoftware.utils/src/main/java/de/srsoftware/utils/Strings.java
 delete mode 100644 de.srsoftware.utils/src/main/java/de/srsoftware/utils/UuidHasher.java
 delete mode 100644 settings.gradle
 create mode 100644 settings.gradle.kts

diff --git a/build.gradle b/build.gradle
deleted file mode 100644
index 7e3698a..0000000
--- a/build.gradle
+++ /dev/null
@@ -1,36 +0,0 @@
-plugins {
-    id 'java'
-    id "com.diffplug.spotless" version "6.25.0"
-}
-
-
-group = 'de.srsoftware'
-version = '1.0.1'
-
-jar.enabled = false
-build.enabled = false
-
-repositories {
-    mavenCentral()
-}
-
-dependencies {
-    testImplementation platform('org.junit:junit-bom:5.10.0')
-    testImplementation 'org.junit.jupiter:junit-jupiter'
-}
-
-test {
-    useJUnitPlatform()
-}
-
-spotless {
-    java {
-        target '*/src/*/java/**/*.java'
-        removeUnusedImports()
-        importOrder()
-        clangFormat('18.1.8').style('file:config/clang-format')
-        licenseHeader '/* © SRSoftware $YEAR */' // or licenseHeaderFile
-    }
-}
-
-compileJava.dependsOn 'spotlessApply'
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..c1d2fbb
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,50 @@
+plugins {
+    id("com.diffplug.spotless") version "latest.release"
+}
+
+repositories {
+    mavenCentral()
+}
+
+
+spotless {
+    java {
+        target("**/src/**/java/**/*.java")
+        removeUnusedImports()
+        importOrder()
+        clangFormat("18.1.8").style("file:config/clang-format")
+        licenseHeader("/* © SRSoftware 2024 */")
+    }
+}
+
+
+subprojects {
+    group = "de.srsoftware"
+    version = "1.0-SNAPSHOT"
+
+    apply(plugin = "java")
+    apply(plugin = "maven-publish")
+    apply(plugin = "com.diffplug.spotless")
+
+    repositories {
+        mavenLocal()
+        mavenCentral()
+    }
+
+
+
+    val implementation by configurations
+    val compileOnly by configurations
+    val testImplementation by configurations
+    val testRuntimeOnly by configurations
+
+
+    dependencies {
+        testImplementation(platform("org.junit:junit-bom:5.10.0"))
+        testImplementation("org.junit.jupiter:junit-jupiter")
+    }
+
+    tasks.withType<Test>() {
+        useJUnitPlatform()
+    }
+}
\ No newline at end of file
diff --git a/de.srsoftware.http/build.gradle b/de.srsoftware.http/build.gradle
deleted file mode 100644
index 64fed60..0000000
--- a/de.srsoftware.http/build.gradle
+++ /dev/null
@@ -1,21 +0,0 @@
-plugins {
-    id 'java'
-}
-
-group = 'de.srsoftware'
-version = '1.0-SNAPSHOT'
-
-repositories {
-    mavenCentral()
-}
-
-dependencies {
-    testImplementation platform('org.junit:junit-bom:5.10.0')
-    testImplementation 'org.junit.jupiter:junit-jupiter'
-    implementation 'org.json:json:20240303'
-    implementation project(':de.srsoftware.utils')
-}
-
-test {
-    useJUnitPlatform()
-}
\ No newline at end of file
diff --git a/de.srsoftware.http/src/main/java/de/srsoftware/http/Cookie.java b/de.srsoftware.http/src/main/java/de/srsoftware/http/Cookie.java
deleted file mode 100644
index 85d490f..0000000
--- a/de.srsoftware.http/src/main/java/de/srsoftware/http/Cookie.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/* © SRSoftware 2024 */
-package de.srsoftware.http;
-
-import static de.srsoftware.utils.Optionals.nullable;
-import static java.lang.System.Logger.Level.*;
-
-import com.sun.net.httpserver.Headers;
-import com.sun.net.httpserver.HttpExchange;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-
-public abstract class Cookie implements Map.Entry<String, String> {
-	static final System.Logger LOG = System.getLogger(SessionToken.class.getSimpleName());
-	private final String       key;
-	private String	           value = null;
-
-	Cookie(String key, String value) {
-		this.key = key;
-		setValue(value);
-	}
-
-	public <T extends Cookie> T addTo(Headers headers) {
-		LOG.log(INFO, "sending cookie {0}={1}", key, value);
-		headers.add("Set-Cookie", "%s=%s".formatted(key, value));
-		return (T)this;
-	}
-
-	public <T extends Cookie> T addTo(HttpExchange ex) {
-		return this.addTo(ex.getResponseHeaders());
-	}
-
-	@Override
-	public String getKey() {
-		return key;
-	}
-
-	@Override
-	public String getValue() {
-		return value;
-	}
-
-	protected static List<String> of(HttpExchange ex) {
-		return nullable(ex.getRequestHeaders().get("Cookie")).stream().flatMap(List::stream).flatMap(s -> Arrays.stream(s.split(";"))).map(String::trim).peek(cookie -> LOG.log(INFO, "received cookie {0}", cookie)).toList();
-	}
-
-	@Override
-	public String setValue(String s) {
-		var oldVal = value;
-		value      = s;
-		return oldVal;
-	}
-}
diff --git a/de.srsoftware.http/src/main/java/de/srsoftware/http/PathHandler.java b/de.srsoftware.http/src/main/java/de/srsoftware/http/PathHandler.java
deleted file mode 100644
index f1d5bb9..0000000
--- a/de.srsoftware.http/src/main/java/de/srsoftware/http/PathHandler.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/* © SRSoftware 2024 */
-package de.srsoftware.http;
-
-
-import static de.srsoftware.utils.Optionals.nullable;
-import static java.lang.System.Logger.Level.*;
-import static java.net.HttpURLConnection.*;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.sun.net.httpserver.HttpExchange;
-import com.sun.net.httpserver.HttpHandler;
-import com.sun.net.httpserver.HttpServer;
-import com.sun.net.httpserver.HttpsExchange;
-import de.srsoftware.utils.Error;
-import java.io.IOException;
-import java.util.*;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import org.json.JSONArray;
-import org.json.JSONObject;
-
-public abstract class PathHandler implements HttpHandler {
-	public static final String  AUTHORIZATION    = "Authorization";
-	public static final String  CONTENT_TYPE     = "Content-Type";
-	public static final String  DEFAULT_LANGUAGE = "en";
-	public static final String  DELETE           = "DELETE";
-	private static final String FORWARDED_HOST   = "x-forwarded-host";
-	public static final String  GET	             = "GET";
-	public static final String  HOST             = "host";
-	public static final String  JSON             = "application/json";
-	public static System.Logger LOG	             = System.getLogger(PathHandler.class.getSimpleName());
-	public static final String  POST             = "POST";
-
-	private String[] paths;
-
-	public record BasicAuth(String userId, String pass) {
-	}
-
-	public class Bond {
-		Bond(String[] paths) {
-			PathHandler.this.paths = paths;
-		}
-		public PathHandler on(HttpServer server) {
-			for (var path : paths) server.createContext(path, PathHandler.this);
-			return PathHandler.this;
-		}
-	}
-
-	public static boolean badRequest(HttpExchange ex, byte[] bytes) throws IOException {
-		return sendContent(ex, HTTP_BAD_REQUEST, bytes);
-	}
-
-	public static boolean badRequest(HttpExchange ex, Object o) throws IOException {
-		return sendContent(ex, HTTP_BAD_REQUEST, o);
-	}
-
-	public Bond bindPath(String... path) {
-		return new Bond(path);
-	}
-
-	public boolean doDelete(String path, HttpExchange ex) throws IOException {
-		return notFound(ex);
-	}
-
-	public boolean doGet(String path, HttpExchange ex) throws IOException {
-		return notFound(ex);
-	}
-
-	public boolean doPost(String path, HttpExchange ex) throws IOException {
-		return notFound(ex);
-	}
-
-	@Override
-	public void handle(HttpExchange ex) throws IOException {
-		String path   = relativePath(ex);
-		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);
-			default -> false;
-		};
- 		ex.getResponseBody().close();
-	}
-
-	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;
-		}
-
-		/******* begin of static methods *************/
-
-		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<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> 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 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 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 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 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);
-			if (o instanceof Map<?, ?> map) o = new JSONObject(map);
-			if (o instanceof Error<?> error) o = error.json();
-			if (o instanceof JSONObject) ex.getResponseHeaders().add(CONTENT_TYPE, 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, 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 String url(HttpExchange ex) {
-			return hostname(ex) + ex.getRequestURI();
-		}
-	}
diff --git a/de.srsoftware.http/src/main/java/de/srsoftware/http/SessionToken.java b/de.srsoftware.http/src/main/java/de/srsoftware/http/SessionToken.java
deleted file mode 100644
index d0de4a8..0000000
--- a/de.srsoftware.http/src/main/java/de/srsoftware/http/SessionToken.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/* © SRSoftware 2024 */
-package de.srsoftware.http;
-
-
-import com.sun.net.httpserver.Headers;
-import com.sun.net.httpserver.HttpExchange;
-import java.time.Instant;
-import java.time.ZoneOffset;
-import java.time.format.DateTimeFormatter;
-import java.util.Optional;
-
-
-public class SessionToken extends Cookie {
-	private final String	       sessionId;
-	private static final DateTimeFormatter FORMAT = DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm:ss O");
-
-	public SessionToken(String sessionId, Instant expiration, boolean trust) {
-		super("sessionToken", sessionToken(sessionId, expiration, trust));
-		this.sessionId = sessionId;
-	}
-
-	private static String sessionToken(String sessionId, Instant expiration, boolean trust) {
-		if (trust) return "%s; Path=/api; Expires=%s".formatted(sessionId, FORMAT.format(expiration.atZone(ZoneOffset.UTC)));
-		return "%s; Path=/api".formatted(sessionId);
-	}
-
-	public SessionToken(String sessionId) {
-		super("sessionToken", sessionId + "; Path=/api");
-		this.sessionId = sessionId;
-	}
-
-	@Override
-	public <T extends Cookie> T addTo(Headers headers) {
-		headers.add("session", getValue());
-		return super.addTo(headers);
-	}
-
-	public static Optional<SessionToken> from(HttpExchange ex) {
-		return Cookie.of(ex)
-		    .stream()
-		    .filter(cookie -> cookie.startsWith("sessionToken="))
-
-		    .map(cookie -> cookie.split("=", 2)[1])
-		    .map(id -> new SessionToken(id))
-		    .findAny();
-	}
-
-	public String sessionId() {
-		return sessionId;
-	}
-}
\ No newline at end of file
diff --git a/de.srsoftware.logging/build.gradle b/de.srsoftware.logging/build.gradle
deleted file mode 100644
index a55b584..0000000
--- a/de.srsoftware.logging/build.gradle
+++ /dev/null
@@ -1,19 +0,0 @@
-plugins {
-    id 'java'
-}
-
-group = 'de.srsoftware'
-version = '1.0-SNAPSHOT'
-
-repositories {
-    mavenCentral()
-}
-
-dependencies {
-    testImplementation platform('org.junit:junit-bom:5.10.0')
-    testImplementation 'org.junit.jupiter:junit-jupiter'
-}
-
-test {
-    useJUnitPlatform()
-}
\ No newline at end of file
diff --git a/de.srsoftware.logging/src/main/java/de/srsoftware/logging/ColorLogger.java b/de.srsoftware.logging/src/main/java/de/srsoftware/logging/ColorLogger.java
deleted file mode 100644
index 6e9ba85..0000000
--- a/de.srsoftware.logging/src/main/java/de/srsoftware/logging/ColorLogger.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/* © SRSoftware 2024 */
-package de.srsoftware.logging;
-
-import static de.srsoftware.logging.ConsoleColors.*;
-import static java.lang.System.Logger.Level.*;
-
-import java.text.DateFormat;
-import java.text.MessageFormat;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.ResourceBundle;
-
-public class ColorLogger implements System.Logger {
-	private final String      name;
-	private static int        rootLevel = INFO.getSeverity();
-	private static DateFormat TIME	    = new SimpleDateFormat("hh:mm:ss.SSS");
-	private static DateFormat DATE	    = new SimpleDateFormat("yyyy-MM-dd");
-	private static String     lastDate  = null;
-
-	public ColorLogger(String name) {
-		this.name = name;
-	}
-
-	@Override
-	public String getName() {
-		return name;
-	}
-
-	@Override
-	public boolean isLoggable(Level level) {
-		return level.getSeverity() >= rootLevel;
-	}
-
-	@Override
-	public void log(Level level, ResourceBundle bundle, String msg, Throwable thrown) {
-		if (isLoggable(level)) {
-			System.out.println(colorize(msg, level.getSeverity()));
-			thrown.printStackTrace();
-		}
-	}
-
-	@Override
-	public void log(Level level, ResourceBundle bundle, String format, Object... params) {
-		if (isLoggable(level)) {
-			System.out.println(colorize(MessageFormat.format(format, params), level.getSeverity()));
-		}
-	}
-
-	public ColorLogger setLogLevel(Level level) {
-		rootLevel = level.getSeverity();
-		return this;
-	}
-
-	private static String colorize(String message, int severity) {
-		var           color = severity >= ERROR.getSeverity() ? RED : severity >= WARNING.getSeverity() ? YELLOW : severity >= INFO.getSeverity() ? WHITE_BRIGHT : WHITE;
-		var           date  = new Date();
-		var           day   = DATE.format(date);
-		StringBuilder sb    = new StringBuilder();
-		if (!day.equals(lastDate)) {
-			lastDate = day;
-			sb.append(WHITE).append(day).append("\n");
-		}
-		return sb.append(WHITE).append(TIME.format(date)).append(" ").append(color).append(message).append(RESET).toString();
-	}
-}
diff --git a/de.srsoftware.logging/src/main/java/de/srsoftware/logging/ColorLoggerFinder.java b/de.srsoftware.logging/src/main/java/de/srsoftware/logging/ColorLoggerFinder.java
deleted file mode 100644
index 0c7930d..0000000
--- a/de.srsoftware.logging/src/main/java/de/srsoftware/logging/ColorLoggerFinder.java
+++ /dev/null
@@ -1,14 +0,0 @@
-/* © SRSoftware 2024 */
-package de.srsoftware.logging;
-
-import java.util.HashMap;
-import java.util.Map;
-
-public class ColorLoggerFinder extends System.LoggerFinder {
-	private static final Map<String, ColorLogger> LOGGERS = new HashMap<>();
-
-	@Override
-	public System.Logger getLogger(String name, Module module) {
-		return LOGGERS.computeIfAbsent(name, ColorLogger::new);
-	}
-}
diff --git a/de.srsoftware.logging/src/main/java/de/srsoftware/logging/ConsoleColors.java b/de.srsoftware.logging/src/main/java/de/srsoftware/logging/ConsoleColors.java
deleted file mode 100644
index e1ed800..0000000
--- a/de.srsoftware.logging/src/main/java/de/srsoftware/logging/ConsoleColors.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/* © SRSoftware 2024 */
-package de.srsoftware.logging;
-
-public class ConsoleColors {
-	// Reset
-	public static final String RESET = "\033[0m";  // Text Reset
-
-	// Regular Colors
-	public static final String BLACK  = "\033[0;30m";  // BLACK
-	public static final String RED	  = "\033[0;31m";  // RED
-	public static final String GREEN  = "\033[0;32m";  // GREEN
-	public static final String YELLOW = "\033[0;33m";  // YELLOW
-	public static final String BLUE	  = "\033[0;34m";  // BLUE
-	public static final String PURPLE = "\033[0;35m";  // PURPLE
-	public static final String CYAN	  = "\033[0;36m";  // CYAN
-	public static final String WHITE  = "\033[0;37m";  // WHITE
-
-	// Bold
-	public static final String BLACK_BOLD  = "\033[1;30m";  // BLACK
-	public static final String RED_BOLD    = "\033[1;31m";  // RED
-	public static final String GREEN_BOLD  = "\033[1;32m";  // GREEN
-	public static final String YELLOW_BOLD = "\033[1;33m";  // YELLOW
-	public static final String BLUE_BOLD   = "\033[1;34m";  // BLUE
-	public static final String PURPLE_BOLD = "\033[1;35m";  // PURPLE
-	public static final String CYAN_BOLD   = "\033[1;36m";  // CYAN
-	public static final String WHITE_BOLD  = "\033[1;37m";  // WHITE
-
-	// Underline
-	public static final String BLACK_UNDERLINED  = "\033[4;30m";  // BLACK
-	public static final String RED_UNDERLINED    = "\033[4;31m";  // RED
-	public static final String GREEN_UNDERLINED  = "\033[4;32m";  // GREEN
-	public static final String YELLOW_UNDERLINED = "\033[4;33m";  // YELLOW
-	public static final String BLUE_UNDERLINED   = "\033[4;34m";  // BLUE
-	public static final String PURPLE_UNDERLINED = "\033[4;35m";  // PURPLE
-	public static final String CYAN_UNDERLINED   = "\033[4;36m";  // CYAN
-	public static final String WHITE_UNDERLINED  = "\033[4;37m";  // WHITE
-
-	// Background
-	public static final String BLACK_BACKGROUND  = "\033[40m";  // BLACK
-	public static final String RED_BACKGROUND    = "\033[41m";  // RED
-	public static final String GREEN_BACKGROUND  = "\033[42m";  // GREEN
-	public static final String YELLOW_BACKGROUND = "\033[43m";  // YELLOW
-	public static final String BLUE_BACKGROUND   = "\033[44m";  // BLUE
-	public static final String PURPLE_BACKGROUND = "\033[45m";  // PURPLE
-	public static final String CYAN_BACKGROUND   = "\033[46m";  // CYAN
-	public static final String WHITE_BACKGROUND  = "\033[47m";  // WHITE
-
-	// High Intensity
-	public static final String BLACK_BRIGHT  = "\033[0;90m";  // BLACK
-	public static final String RED_BRIGHT    = "\033[0;91m";  // RED
-	public static final String GREEN_BRIGHT  = "\033[0;92m";  // GREEN
-	public static final String YELLOW_BRIGHT = "\033[0;93m";  // YELLOW
-	public static final String BLUE_BRIGHT   = "\033[0;94m";  // BLUE
-	public static final String PURPLE_BRIGHT = "\033[0;95m";  // PURPLE
-	public static final String CYAN_BRIGHT   = "\033[0;96m";  // CYAN
-	public static final String WHITE_BRIGHT  = "\033[0;97m";  // WHITE
-
-	// Bold High Intensity
-	public static final String BLACK_BOLD_BRIGHT  = "\033[1;90m";  // BLACK
-	public static final String RED_BOLD_BRIGHT    = "\033[1;91m";  // RED
-	public static final String GREEN_BOLD_BRIGHT  = "\033[1;92m";  // GREEN
-	public static final String YELLOW_BOLD_BRIGHT = "\033[1;93m";  // YELLOW
-	public static final String BLUE_BOLD_BRIGHT   = "\033[1;94m";  // BLUE
-	public static final String PURPLE_BOLD_BRIGHT = "\033[1;95m";  // PURPLE
-	public static final String CYAN_BOLD_BRIGHT   = "\033[1;96m";  // CYAN
-	public static final String WHITE_BOLD_BRIGHT  = "\033[1;97m";  // WHITE
-
-	// High Intensity backgrounds
-	public static final String BLACK_BACKGROUND_BRIGHT  = "\033[0;100m";  // BLACK
-	public static final String RED_BACKGROUND_BRIGHT    = "\033[0;101m";  // RED
-	public static final String GREEN_BACKGROUND_BRIGHT  = "\033[0;102m";  // GREEN
-	public static final String YELLOW_BACKGROUND_BRIGHT = "\033[0;103m";  // YELLOW
-	public static final String BLUE_BACKGROUND_BRIGHT   = "\033[0;104m";  // BLUE
-	public static final String PURPLE_BACKGROUND_BRIGHT = "\033[0;105m";  // PURPLE
-	public static final String CYAN_BACKGROUND_BRIGHT   = "\033[0;106m";  // CYAN
-	public static final String WHITE_BACKGROUND_BRIGHT  = "\033[0;107m";  // WHITE
-}
\ No newline at end of file
diff --git a/de.srsoftware.logging/src/main/resources/META-INF/services/java.lang.System$LoggerFinder b/de.srsoftware.logging/src/main/resources/META-INF/services/java.lang.System$LoggerFinder
deleted file mode 100644
index 29b80f4..0000000
--- a/de.srsoftware.logging/src/main/resources/META-INF/services/java.lang.System$LoggerFinder
+++ /dev/null
@@ -1 +0,0 @@
-de.srsoftware.logging.ColorLoggerFinder
\ No newline at end of file
diff --git a/de.srsoftware.oidc.api/build.gradle b/de.srsoftware.oidc.api/build.gradle
index 62628d3..1a11b90 100644
--- a/de.srsoftware.oidc.api/build.gradle
+++ b/de.srsoftware.oidc.api/build.gradle
@@ -1,26 +1,17 @@
-plugins {
-    id 'java'
-}
-
-group = 'de.srsoftware'
 
-repositories {
-    mavenCentral()
-}
+description = 'SRSoftware OIDC: api'
 
 dependencies {
     testImplementation platform('org.junit:junit-bom:5.10.0')
     testImplementation 'org.junit.jupiter:junit-jupiter'
-    implementation project(':de.srsoftware.utils')
-    implementation 'org.json:json:20240303'
+
+    implementation 'de.srsoftware:tools.optionals:1.0.0'
+    implementation 'de.srsoftware:tools.util:1.0.2'
     implementation 'org.bitbucket.b_c:jose4j:0.9.6'
+    implementation 'org.json:json:20240303'
     implementation 'com.sun.mail:jakarta.mail:2.0.1'
 }
 
-test {
-    useJUnitPlatform()
-}
-
 task jarTests (type: Jar) {
     from sourceSets.test.output
     archiveClassifier = 'test'
diff --git a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/UserService.java b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/UserService.java
index 0ad91a1..8acb2c2 100644
--- a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/UserService.java
+++ b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/UserService.java
@@ -6,7 +6,7 @@ import static java.util.Optional.empty;
 import de.srsoftware.oidc.api.data.AccessToken;
 import de.srsoftware.oidc.api.data.Lock;
 import de.srsoftware.oidc.api.data.User;
-import de.srsoftware.utils.Result;
+import de.srsoftware.tools.Result;
 import java.time.Instant;
 import java.util.*;
 
diff --git a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/Client.java b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/Client.java
index db09475..9243700 100644
--- a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/Client.java
+++ b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/Client.java
@@ -3,7 +3,7 @@ package de.srsoftware.oidc.api.data;
 
 
 import static de.srsoftware.oidc.api.Constants.*;
-import static de.srsoftware.utils.Optionals.nullable;
+import static de.srsoftware.tools.Optionals.nullable;
 
 import java.time.Duration;
 import java.util.*;
diff --git a/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/AuthServiceTest.java b/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/AuthServiceTest.java
index d70ab47..8aab9c2 100644
--- a/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/AuthServiceTest.java
+++ b/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/AuthServiceTest.java
@@ -2,7 +2,7 @@
 package de.srsoftware.oidc.api;
 
 import static de.srsoftware.oidc.api.Constants.OPENID;
-import static de.srsoftware.utils.Strings.uuid;
+import static de.srsoftware.tools.Strings.uuid;
 import static java.time.temporal.ChronoUnit.SECONDS;
 import static org.junit.jupiter.api.Assertions.*;
 
diff --git a/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/ClientServiceTest.java b/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/ClientServiceTest.java
index b1ef86c..8b38a98 100644
--- a/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/ClientServiceTest.java
+++ b/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/ClientServiceTest.java
@@ -1,7 +1,7 @@
 /* © SRSoftware 2024 */
 package de.srsoftware.oidc.api;
 
-import static de.srsoftware.utils.Strings.uuid;
+import static de.srsoftware.tools.Strings.uuid;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
diff --git a/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/KeyStoreTest.java b/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/KeyStoreTest.java
index b5ccf93..765516e 100644
--- a/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/KeyStoreTest.java
+++ b/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/KeyStoreTest.java
@@ -2,7 +2,7 @@
 package de.srsoftware.oidc.api;
 
 import static de.srsoftware.oidc.api.Constants.EXPIRATION;
-import static de.srsoftware.utils.Strings.uuid;
+import static de.srsoftware.tools.Strings.uuid;
 import static org.jose4j.jws.AlgorithmIdentifiers.RSA_USING_SHA256;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
diff --git a/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/MailConfigTest.java b/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/MailConfigTest.java
index a6ebcaa..2ae50a6 100644
--- a/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/MailConfigTest.java
+++ b/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/MailConfigTest.java
@@ -2,7 +2,7 @@
 package de.srsoftware.oidc.api;
 
 import static de.srsoftware.oidc.api.Constants.*;
-import static de.srsoftware.utils.Strings.uuid;
+import static de.srsoftware.tools.Strings.uuid;
 import static org.junit.jupiter.api.Assertions.*;
 
 import jakarta.mail.Authenticator;
diff --git a/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/SessionServiceTest.java b/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/SessionServiceTest.java
index 53d160d..1958e5e 100644
--- a/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/SessionServiceTest.java
+++ b/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/SessionServiceTest.java
@@ -1,13 +1,13 @@
 /* © SRSoftware 2024 */
 package de.srsoftware.oidc.api;
 
-import static de.srsoftware.utils.Strings.uuid;
+import static de.srsoftware.tools.Strings.uuid;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import de.srsoftware.oidc.api.data.User;
-import de.srsoftware.utils.PasswordHasher;
-import de.srsoftware.utils.UuidHasher;
+import de.srsoftware.tools.PasswordHasher;
+import de.srsoftware.tools.UuidHasher;
 import java.security.NoSuchAlgorithmException;
 import java.time.Duration;
 import java.time.Instant;
diff --git a/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/UserServiceTest.java b/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/UserServiceTest.java
index 8b7c2e5..1086ae6 100644
--- a/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/UserServiceTest.java
+++ b/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/UserServiceTest.java
@@ -6,8 +6,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import de.srsoftware.oidc.api.data.Permission;
 import de.srsoftware.oidc.api.data.User;
-import de.srsoftware.utils.PasswordHasher;
-import de.srsoftware.utils.UuidHasher;
+import de.srsoftware.tools.PasswordHasher;
+import de.srsoftware.tools.UuidHasher;
 import java.security.NoSuchAlgorithmException;
 import java.util.UUID;
 import org.junit.jupiter.api.Assertions;
diff --git a/de.srsoftware.oidc.app/build.gradle b/de.srsoftware.oidc.app/build.gradle
deleted file mode 100644
index cebaba4..0000000
--- a/de.srsoftware.oidc.app/build.gradle
+++ /dev/null
@@ -1,47 +0,0 @@
-plugins {
-    id 'java'
-}
-
-group = 'de.srsoftware'
-version = '1.0-SNAPSHOT'
-
-repositories {
-    mavenCentral()
-}
-
-dependencies {
-    testImplementation platform('org.junit:junit-bom:5.10.0')
-    testImplementation 'org.junit.jupiter:junit-jupiter'
-    implementation project(':de.srsoftware.http')
-    implementation project(':de.srsoftware.logging')
-    implementation project(':de.srsoftware.oidc.api')
-    implementation project(':de.srsoftware.oidc.backend')
-    implementation project(':de.srsoftware.oidc.datastore.encrypted')
-    implementation project(':de.srsoftware.oidc.datastore.file')
-    implementation project(':de.srsoftware.oidc.web')
-    implementation project(':de.srsoftware.utils')
-    implementation 'org.json:json:20240303'
-
-}
-
-test {
-    useJUnitPlatform()
-}
-
-task run(type: JavaExec) {
-    group = "application"
-    description = "Run the main class with JavaExecTask"
-    classpath = sourceSets.main.runtimeClasspath
-    mainClass = 'de.srsoftware.oidc.app.Application'
-    args = ['--base','/home/srichter/workspace/LightOIDC/de.srsoftware.oidc.web/src/main/resources']
-}
-
-jar {
-    manifest {
-        attributes "Main-Class": "de.srsoftware.oidc.app.Application"
-    }
-    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
-    from {
-        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
-    }
-}
diff --git a/de.srsoftware.oidc.app/build.gradle.kts b/de.srsoftware.oidc.app/build.gradle.kts
new file mode 100644
index 0000000..6baa74b
--- /dev/null
+++ b/de.srsoftware.oidc.app/build.gradle.kts
@@ -0,0 +1,14 @@
+description = "SRSoftware OIDC: app"
+
+dependencies{
+    implementation("org.json:json:20240303")
+    implementation("de.srsoftware:tools.http:1.0.0")
+    implementation("de.srsoftware:tools.logging:1.0.0")
+    implementation("de.srsoftware:tools.optionals:1.0.0")
+    implementation("de.srsoftware:tools.util:1.0.2")
+    implementation(project(":de.srsoftware.oidc.api"))
+    implementation(project(":de.srsoftware.oidc.backend"))
+    implementation(project(":de.srsoftware.oidc.datastore.encrypted"))
+    implementation(project(":de.srsoftware.oidc.datastore.file"))
+    implementation(project(":de.srsoftware.oidc.web"))
+}
\ No newline at end of file
diff --git a/de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java b/de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java
index 7462ae0..740beb3 100644
--- a/de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java
+++ b/de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java
@@ -4,17 +4,16 @@ package de.srsoftware.oidc.app;
 
 import static de.srsoftware.oidc.api.Constants.*;
 import static de.srsoftware.oidc.api.data.Permission.*;
-import static de.srsoftware.utils.Optionals.emptyIfBlank;
-import static de.srsoftware.utils.Optionals.nullable;
-import static de.srsoftware.utils.Paths.configDir;
-import static de.srsoftware.utils.Strings.uuid;
+import static de.srsoftware.tools.Optionals.absentIfBlank;
+import static de.srsoftware.tools.Optionals.nullable;
+import static de.srsoftware.tools.Paths.configDir;
+import static de.srsoftware.tools.Strings.uuid;
 import static java.lang.System.Logger.Level.DEBUG;
 import static java.lang.System.Logger.Level.ERROR;
 import static java.lang.System.getenv;
 import static java.util.Optional.empty;
 
 import com.sun.net.httpserver.HttpServer;
-import de.srsoftware.logging.ColorLogger;
 import de.srsoftware.oidc.api.*;
 import de.srsoftware.oidc.api.data.User;
 import de.srsoftware.oidc.backend.*;
@@ -26,7 +25,8 @@ import de.srsoftware.oidc.datastore.file.FileStoreProvider;
 import de.srsoftware.oidc.datastore.file.PlaintextKeyStore;
 import de.srsoftware.oidc.web.Forward;
 import de.srsoftware.oidc.web.StaticPages;
-import de.srsoftware.utils.UuidHasher;
+import de.srsoftware.tools.ColorLogger;
+import de.srsoftware.tools.UuidHasher;
 import java.io.File;
 import java.net.InetSocketAddress;
 import java.nio.file.Path;
@@ -146,8 +146,8 @@ public class Application {
 		var tokens = new ArrayList<>(List.of(args));
 		var map    = new HashMap<String, Object>();
 
-		emptyIfBlank(getenv(BASE_PATH)).map(Path::of).ifPresent(path -> map.put(BASE_PATH, path));
-		emptyIfBlank(getenv(CONFIG_PATH)).map(Path::of).ifPresent(path -> map.put(CONFIG_PATH, path));
+		absentIfBlank(getenv(BASE_PATH)).map(Path::of).ifPresent(path -> map.put(BASE_PATH, path));
+		absentIfBlank(getenv(CONFIG_PATH)).map(Path::of).ifPresent(path -> map.put(CONFIG_PATH, path));
 
 		// Command line arguments override environment
 		while (!tokens.isEmpty()) {
diff --git a/de.srsoftware.oidc.backend/build.gradle b/de.srsoftware.oidc.backend/build.gradle
deleted file mode 100644
index 87f9a51..0000000
--- a/de.srsoftware.oidc.backend/build.gradle
+++ /dev/null
@@ -1,27 +0,0 @@
-plugins {
-    id 'java'
-}
-
-group = 'de.srsoftware'
-version = '1.0-SNAPSHOT'
-
-repositories {
-    mavenCentral()
-}
-
-dependencies {
-    testImplementation platform('org.junit:junit-bom:5.10.0')
-    testImplementation 'org.junit.jupiter:junit-jupiter'
-    implementation project(':de.srsoftware.http')
-    implementation project(':de.srsoftware.logging')
-    implementation project(':de.srsoftware.oidc.api')
-    implementation project(':de.srsoftware.utils')
-    implementation 'org.json:json:20240303'
-    implementation 'org.bitbucket.b_c:jose4j:0.9.6'
-    implementation 'com.sun.mail:jakarta.mail:2.0.1'
-
-}
-
-test {
-    useJUnitPlatform()
-}
diff --git a/de.srsoftware.oidc.backend/build.gradle.kts b/de.srsoftware.oidc.backend/build.gradle.kts
new file mode 100644
index 0000000..aad3421
--- /dev/null
+++ b/de.srsoftware.oidc.backend/build.gradle.kts
@@ -0,0 +1,13 @@
+description = "SRSoftware OIDC: backend"
+
+dependencies{
+    implementation("com.sun.mail:jakarta.mail:2.0.1")
+    implementation("de.srsoftware:tools.http:1.0.0")
+    implementation("de.srsoftware:tools.optionals:1.0.0")
+    implementation("de.srsoftware:tools.util:1.0.2")
+    implementation("org.bitbucket.b_c:jose4j:0.9.6")
+    implementation("org.json:json:20240303")
+
+    implementation(project(":de.srsoftware.oidc.api"))
+}
+
diff --git a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/ClientController.java b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/ClientController.java
index 97e1315..465472e 100644
--- a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/ClientController.java
+++ b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/ClientController.java
@@ -11,8 +11,8 @@ import de.srsoftware.oidc.api.data.AuthorizedScopes;
 import de.srsoftware.oidc.api.data.Client;
 import de.srsoftware.oidc.api.data.Session;
 import de.srsoftware.oidc.api.data.User;
-import de.srsoftware.utils.Error;
-import de.srsoftware.utils.Optionals;
+import de.srsoftware.tools.Error;
+import de.srsoftware.tools.Optionals;
 import java.io.IOException;
 import java.time.Duration;
 import java.time.Instant;
diff --git a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/Controller.java b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/Controller.java
index 604c8d3..db4be78 100644
--- a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/Controller.java
+++ b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/Controller.java
@@ -2,10 +2,10 @@
 package de.srsoftware.oidc.backend;
 
 import com.sun.net.httpserver.HttpExchange;
-import de.srsoftware.http.PathHandler;
-import de.srsoftware.http.SessionToken;
 import de.srsoftware.oidc.api.SessionService;
 import de.srsoftware.oidc.api.data.Session;
+import de.srsoftware.tools.PathHandler;
+import de.srsoftware.tools.SessionToken;
 import java.io.IOException;
 import java.util.Optional;
 
diff --git a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/KeyStoreController.java b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/KeyStoreController.java
index 4e25f20..c27a449 100644
--- a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/KeyStoreController.java
+++ b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/KeyStoreController.java
@@ -2,8 +2,8 @@
 package de.srsoftware.oidc.backend;
 
 import com.sun.net.httpserver.HttpExchange;
-import de.srsoftware.http.PathHandler;
 import de.srsoftware.oidc.api.KeyStorage;
+import de.srsoftware.tools.PathHandler;
 import java.io.IOException;
 import org.jose4j.jwk.JsonWebKey;
 import org.jose4j.jwk.PublicJsonWebKey;
diff --git a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/RotatingKeyManager.java b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/RotatingKeyManager.java
index 588fbec..98838a8 100644
--- a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/RotatingKeyManager.java
+++ b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/RotatingKeyManager.java
@@ -2,8 +2,8 @@
 package de.srsoftware.oidc.backend;
 
 import static de.srsoftware.oidc.api.Constants.EXPIRATION;
-import static de.srsoftware.utils.Optionals.nullable;
-import static de.srsoftware.utils.Strings.uuid;
+import static de.srsoftware.tools.Optionals.nullable;
+import static de.srsoftware.tools.Strings.uuid;
 import static org.jose4j.jws.AlgorithmIdentifiers.RSA_USING_SHA256;
 
 import de.srsoftware.oidc.api.KeyManager;
diff --git a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/TokenController.java b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/TokenController.java
index abd2056..51b122d 100644
--- a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/TokenController.java
+++ b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/TokenController.java
@@ -3,16 +3,16 @@ package de.srsoftware.oidc.backend;
 
 import static de.srsoftware.oidc.api.Constants.*;
 import static de.srsoftware.oidc.api.Constants.ERROR;
-import static de.srsoftware.utils.Optionals.emptyIfBlank;
+import static de.srsoftware.tools.Optionals.absentIfBlank;
 import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
 import static java.nio.charset.StandardCharsets.US_ASCII;
 
 import com.sun.net.httpserver.HttpExchange;
-import de.srsoftware.http.PathHandler;
 import de.srsoftware.oidc.api.*;
 import de.srsoftware.oidc.api.data.AccessToken;
 import de.srsoftware.oidc.api.data.Client;
 import de.srsoftware.oidc.api.data.User;
+import de.srsoftware.tools.PathHandler;
 import java.io.IOException;
 import java.net.URLDecoder;
 import java.nio.charset.StandardCharsets;
@@ -68,7 +68,7 @@ public class TokenController extends PathHandler {
 	private HashMap<String, String> tokenResponse(String errorCode, String description) throws IOException {
 		var map = new HashMap<String, String>();
 		map.put(ERROR, errorCode);
-		emptyIfBlank(description).ifPresent(d -> map.put(ERROR_DESCRIPTION, d));
+		absentIfBlank(description).ifPresent(d -> map.put(ERROR_DESCRIPTION, d));
 		return map;
 	}
 
diff --git a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/UserController.java b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/UserController.java
index 52e945a..611b32e 100644
--- a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/UserController.java
+++ b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/UserController.java
@@ -4,19 +4,19 @@ package de.srsoftware.oidc.backend;
 import static de.srsoftware.oidc.api.Constants.*;
 import static de.srsoftware.oidc.api.data.Permission.MANAGE_USERS;
 import static de.srsoftware.oidc.api.data.User.*;
-import static de.srsoftware.utils.Strings.uuid;
+import static de.srsoftware.tools.Strings.uuid;
 import static java.lang.System.Logger.Level.WARNING;
 import static java.net.HttpURLConnection.*;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.sun.net.httpserver.HttpExchange;
-import de.srsoftware.http.SessionToken;
 import de.srsoftware.oidc.api.*;
 import de.srsoftware.oidc.api.data.Permission;
 import de.srsoftware.oidc.api.data.Session;
 import de.srsoftware.oidc.api.data.User;
-import de.srsoftware.utils.Payload;
-import de.srsoftware.utils.Result;
+import de.srsoftware.tools.Payload;
+import de.srsoftware.tools.Result;
+import de.srsoftware.tools.SessionToken;
 import jakarta.mail.*;
 import jakarta.mail.internet.*;
 import java.io.IOException;
diff --git a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/WellKnownController.java b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/WellKnownController.java
index 079dfae..3ac67cc 100644
--- a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/WellKnownController.java
+++ b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/WellKnownController.java
@@ -3,7 +3,7 @@ package de.srsoftware.oidc.backend;
 
 
 import com.sun.net.httpserver.HttpExchange;
-import de.srsoftware.http.PathHandler;
+import de.srsoftware.tools.PathHandler;
 import java.io.IOException;
 import java.util.List;
 import java.util.Map;
diff --git a/de.srsoftware.oidc.datastore.encrypted/build.gradle b/de.srsoftware.oidc.datastore.encrypted/build.gradle
deleted file mode 100644
index e94d2df..0000000
--- a/de.srsoftware.oidc.datastore.encrypted/build.gradle
+++ /dev/null
@@ -1,24 +0,0 @@
-plugins {
-    id 'java'
-}
-
-group = 'de.srsoftware'
-version = '1.0-SNAPSHOT'
-
-repositories {
-    mavenCentral()
-}
-
-dependencies {
-    testImplementation platform('org.junit:junit-bom:5.10.0')
-    testImplementation 'org.junit.jupiter:junit-jupiter'
-    testImplementation project(path: ':de.srsoftware.oidc.api', configuration: "testBundle")
-    implementation project(':de.srsoftware.oidc.api')
-    implementation project(':de.srsoftware.utils')
-    implementation 'com.sun.mail:jakarta.mail:2.0.1'
-    implementation 'org.bitbucket.b_c:jose4j:0.9.6'
-}
-
-test {
-    useJUnitPlatform()
-}
\ No newline at end of file
diff --git a/de.srsoftware.oidc.datastore.encrypted/build.gradle.kts b/de.srsoftware.oidc.datastore.encrypted/build.gradle.kts
new file mode 100644
index 0000000..78332cf
--- /dev/null
+++ b/de.srsoftware.oidc.datastore.encrypted/build.gradle.kts
@@ -0,0 +1,11 @@
+description = "SRSoftware OIDC: encrypted datastore module"
+
+dependencies{
+    implementation("com.sun.mail:jakarta.mail:2.0.1")
+    implementation("de.srsoftware:tools.optionals:1.0.0")
+    implementation("de.srsoftware:tools.util:1.0.2")
+
+    implementation(project(":de.srsoftware.oidc.api"))
+    testImplementation(project(":de.srsoftware.oidc.api","testBundle"))
+}
+
diff --git a/de.srsoftware.oidc.datastore.encrypted/src/main/java/de/srsoftware/oidc/datastore/encrypted/EncryptedUserService.java b/de.srsoftware.oidc.datastore.encrypted/src/main/java/de/srsoftware/oidc/datastore/encrypted/EncryptedUserService.java
index e45d678..e7a0c34 100644
--- a/de.srsoftware.oidc.datastore.encrypted/src/main/java/de/srsoftware/oidc/datastore/encrypted/EncryptedUserService.java
+++ b/de.srsoftware.oidc.datastore.encrypted/src/main/java/de/srsoftware/oidc/datastore/encrypted/EncryptedUserService.java
@@ -8,10 +8,10 @@ import static java.util.Optional.empty;
 import de.srsoftware.oidc.api.UserService;
 import de.srsoftware.oidc.api.data.AccessToken;
 import de.srsoftware.oidc.api.data.User;
-import de.srsoftware.utils.Error;
-import de.srsoftware.utils.PasswordHasher;
-import de.srsoftware.utils.Payload;
-import de.srsoftware.utils.Result;
+import de.srsoftware.tools.Error;
+import de.srsoftware.tools.PasswordHasher;
+import de.srsoftware.tools.Payload;
+import de.srsoftware.tools.Result;
 import java.util.*;
 
 public class EncryptedUserService extends EncryptedConfig implements UserService {
diff --git a/de.srsoftware.oidc.datastore.encrypted/src/test/java/EncryptedClientServiceTest.java b/de.srsoftware.oidc.datastore.encrypted/src/test/java/de/srsoftware/oidc/datastore/encrypted/EncryptedClientServiceTest.java
similarity index 88%
rename from de.srsoftware.oidc.datastore.encrypted/src/test/java/EncryptedClientServiceTest.java
rename to de.srsoftware.oidc.datastore.encrypted/src/test/java/de/srsoftware/oidc/datastore/encrypted/EncryptedClientServiceTest.java
index 1ae533c..cd5bcca 100644
--- a/de.srsoftware.oidc.datastore.encrypted/src/test/java/EncryptedClientServiceTest.java
+++ b/de.srsoftware.oidc.datastore.encrypted/src/test/java/de/srsoftware/oidc/datastore/encrypted/EncryptedClientServiceTest.java
@@ -1,11 +1,11 @@
 /* © SRSoftware 2024 */
-import static de.srsoftware.utils.Optionals.nullable;
-import static de.srsoftware.utils.Strings.uuid;
+package de.srsoftware.oidc.datastore.encrypted; /* © SRSoftware 2024 */
+import static de.srsoftware.tools.Optionals.nullable;
+import static de.srsoftware.tools.Strings.uuid;
 
 import de.srsoftware.oidc.api.ClientService;
 import de.srsoftware.oidc.api.ClientServiceTest;
 import de.srsoftware.oidc.api.data.Client;
-import de.srsoftware.oidc.datastore.encrypted.EncryptedClientService;
 import java.sql.SQLException;
 import java.util.HashMap;
 import java.util.List;
diff --git a/de.srsoftware.oidc.datastore.encrypted/src/test/java/EncryptedConfigTest.java b/de.srsoftware.oidc.datastore.encrypted/src/test/java/de/srsoftware/oidc/datastore/encrypted/EncryptedConfigTest.java
similarity index 81%
rename from de.srsoftware.oidc.datastore.encrypted/src/test/java/EncryptedConfigTest.java
rename to de.srsoftware.oidc.datastore.encrypted/src/test/java/de/srsoftware/oidc/datastore/encrypted/EncryptedConfigTest.java
index 8acc0c6..34e518c 100644
--- a/de.srsoftware.oidc.datastore.encrypted/src/test/java/EncryptedConfigTest.java
+++ b/de.srsoftware.oidc.datastore.encrypted/src/test/java/de/srsoftware/oidc/datastore/encrypted/EncryptedConfigTest.java
@@ -1,8 +1,8 @@
 /* © SRSoftware 2024 */
-import static de.srsoftware.utils.Strings.uuid;
+package de.srsoftware.oidc.datastore.encrypted; /* © SRSoftware 2024 */
+import static de.srsoftware.tools.Strings.uuid;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
-import de.srsoftware.oidc.datastore.encrypted.EncryptedConfig;
 import org.junit.jupiter.api.Test;
 
 public class EncryptedConfigTest {
diff --git a/de.srsoftware.oidc.datastore.encrypted/src/test/java/EncryptedKeyStoreTest.java b/de.srsoftware.oidc.datastore.encrypted/src/test/java/de/srsoftware/oidc/datastore/encrypted/EncryptedKeyStoreTest.java
similarity index 90%
rename from de.srsoftware.oidc.datastore.encrypted/src/test/java/EncryptedKeyStoreTest.java
rename to de.srsoftware.oidc.datastore.encrypted/src/test/java/de/srsoftware/oidc/datastore/encrypted/EncryptedKeyStoreTest.java
index ca15001..9740728 100644
--- a/de.srsoftware.oidc.datastore.encrypted/src/test/java/EncryptedKeyStoreTest.java
+++ b/de.srsoftware.oidc.datastore.encrypted/src/test/java/de/srsoftware/oidc/datastore/encrypted/EncryptedKeyStoreTest.java
@@ -1,9 +1,9 @@
 /* © SRSoftware 2024 */
-import static de.srsoftware.utils.Strings.uuid;
+package de.srsoftware.oidc.datastore.encrypted; /* © SRSoftware 2024 */
+import static de.srsoftware.tools.Strings.uuid;
 
 import de.srsoftware.oidc.api.KeyStorage;
 import de.srsoftware.oidc.api.KeyStoreTest;
-import de.srsoftware.oidc.datastore.encrypted.EncryptedKeyStore;
 import java.io.IOException;
 import java.sql.SQLException;
 import java.util.HashMap;
diff --git a/de.srsoftware.oidc.datastore.encrypted/src/test/java/EncryptedMailConfigTest.java b/de.srsoftware.oidc.datastore.encrypted/src/test/java/de/srsoftware/oidc/datastore/encrypted/EncryptedMailConfigTest.java
similarity index 95%
rename from de.srsoftware.oidc.datastore.encrypted/src/test/java/EncryptedMailConfigTest.java
rename to de.srsoftware.oidc.datastore.encrypted/src/test/java/de/srsoftware/oidc/datastore/encrypted/EncryptedMailConfigTest.java
index e7c3f36..47b6d2c 100644
--- a/de.srsoftware.oidc.datastore.encrypted/src/test/java/EncryptedMailConfigTest.java
+++ b/de.srsoftware.oidc.datastore.encrypted/src/test/java/de/srsoftware/oidc/datastore/encrypted/EncryptedMailConfigTest.java
@@ -1,9 +1,9 @@
 /* © SRSoftware 2024 */
-import static de.srsoftware.utils.Strings.uuid;
+package de.srsoftware.oidc.datastore.encrypted; /* © SRSoftware 2024 */
+import static de.srsoftware.tools.Strings.uuid;
 import static org.junit.jupiter.api.Assertions.*;
 
 import de.srsoftware.oidc.api.MailConfig;
-import de.srsoftware.oidc.datastore.encrypted.EncryptedMailConfig;
 import jakarta.mail.Authenticator;
 import java.util.Map;
 import java.util.Properties;
diff --git a/de.srsoftware.oidc.datastore.encrypted/src/test/java/EncryptedUserServiceTest.java b/de.srsoftware.oidc.datastore.encrypted/src/test/java/de/srsoftware/oidc/datastore/encrypted/EncryptedUserServiceTest.java
similarity index 91%
rename from de.srsoftware.oidc.datastore.encrypted/src/test/java/EncryptedUserServiceTest.java
rename to de.srsoftware.oidc.datastore.encrypted/src/test/java/de/srsoftware/oidc/datastore/encrypted/EncryptedUserServiceTest.java
index 0dcfeb8..bf2f011 100644
--- a/de.srsoftware.oidc.datastore.encrypted/src/test/java/EncryptedUserServiceTest.java
+++ b/de.srsoftware.oidc.datastore.encrypted/src/test/java/de/srsoftware/oidc/datastore/encrypted/EncryptedUserServiceTest.java
@@ -1,17 +1,17 @@
 /* © SRSoftware 2024 */
+package de.srsoftware.oidc.datastore.encrypted; /* © SRSoftware 2024 */
 import static de.srsoftware.oidc.api.Constants.*;
-import static de.srsoftware.utils.Optionals.nullable;
-import static de.srsoftware.utils.Strings.uuid;
+import static de.srsoftware.tools.Optionals.nullable;
+import static de.srsoftware.tools.Strings.uuid;
 import static java.lang.System.Logger.Level.WARNING;
 
 import de.srsoftware.oidc.api.*;
 import de.srsoftware.oidc.api.data.AccessToken;
 import de.srsoftware.oidc.api.data.User;
-import de.srsoftware.oidc.datastore.encrypted.EncryptedUserService;
-import de.srsoftware.utils.Error;
-import de.srsoftware.utils.PasswordHasher;
-import de.srsoftware.utils.Payload;
-import de.srsoftware.utils.Result;
+import de.srsoftware.tools.Error;
+import de.srsoftware.tools.PasswordHasher;
+import de.srsoftware.tools.Payload;
+import de.srsoftware.tools.Result;
 import java.io.File;
 import java.util.*;
 import java.util.stream.Collectors;
diff --git a/de.srsoftware.oidc.datastore.file/build.gradle b/de.srsoftware.oidc.datastore.file/build.gradle
deleted file mode 100644
index b4d682c..0000000
--- a/de.srsoftware.oidc.datastore.file/build.gradle
+++ /dev/null
@@ -1,26 +0,0 @@
-plugins {
-    id 'java'
-}
-
-group = 'de.srsoftware'
-version = '1.0-SNAPSHOT'
-
-repositories {
-    mavenCentral()
-}
-
-dependencies {
-    testImplementation platform('org.junit:junit-bom:5.10.0')
-    testImplementation 'org.junit.jupiter:junit-jupiter'
-    testImplementation project(path: ':de.srsoftware.oidc.api', configuration: "testBundle")
-    implementation project(':de.srsoftware.oidc.api')
-    implementation project(':de.srsoftware.utils')
-    implementation 'org.json:json:20240303'
-    implementation 'org.bitbucket.b_c:jose4j:0.9.6'
-    implementation 'com.sun.mail:jakarta.mail:2.0.1'
-
-}
-
-test {
-    useJUnitPlatform()
-}
\ No newline at end of file
diff --git a/de.srsoftware.oidc.datastore.file/build.gradle.kts b/de.srsoftware.oidc.datastore.file/build.gradle.kts
new file mode 100644
index 0000000..79d50dc
--- /dev/null
+++ b/de.srsoftware.oidc.datastore.file/build.gradle.kts
@@ -0,0 +1,13 @@
+description = "SRSoftware OIDC: file datastore module"
+
+dependencies{
+    implementation("com.sun.mail:jakarta.mail:2.0.1")
+    implementation("de.srsoftware:tools.optionals:1.0.0")
+    implementation("de.srsoftware:tools.util:1.0.2")
+    implementation("org.json:json:20240303")
+
+    implementation(project(":de.srsoftware.oidc.api"))
+    implementation(project(":de.srsoftware.oidc.web"))
+    testImplementation(project(":de.srsoftware.oidc.api","testBundle"))
+}
+
diff --git a/de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStore.java b/de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStore.java
index c475a20..91727c6 100644
--- a/de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStore.java
+++ b/de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStore.java
@@ -2,18 +2,18 @@
 package de.srsoftware.oidc.datastore.file; /* © SRSoftware 2024 */
 import static de.srsoftware.oidc.api.Constants.*;
 import static de.srsoftware.oidc.api.data.User.*;
-import static de.srsoftware.utils.Optionals.nullable;
-import static de.srsoftware.utils.Strings.uuid;
+import static de.srsoftware.tools.Optionals.nullable;
+import static de.srsoftware.tools.Strings.uuid;
 import static java.lang.System.Logger.Level.*;
 import static java.time.temporal.ChronoUnit.SECONDS;
 import static java.util.Optional.empty;
 
 import de.srsoftware.oidc.api.*;
 import de.srsoftware.oidc.api.data.*;
-import de.srsoftware.utils.Error;
-import de.srsoftware.utils.PasswordHasher;
-import de.srsoftware.utils.Payload;
-import de.srsoftware.utils.Result;
+import de.srsoftware.tools.Error;
+import de.srsoftware.tools.PasswordHasher;
+import de.srsoftware.tools.Payload;
+import de.srsoftware.tools.Result;
 import jakarta.mail.Authenticator;
 import jakarta.mail.PasswordAuthentication;
 import java.io.File;
diff --git a/de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStoreProvider.java b/de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStoreProvider.java
index b6bd4d8..0dee0ef 100644
--- a/de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStoreProvider.java
+++ b/de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStoreProvider.java
@@ -1,7 +1,7 @@
 /* © SRSoftware 2024 */
 package de.srsoftware.oidc.datastore.file;
 
-import de.srsoftware.utils.UuidHasher;
+import de.srsoftware.tools.UuidHasher;
 import java.io.File;
 import java.io.IOException;
 import java.util.HashMap;
diff --git a/de.srsoftware.oidc.datastore.file/src/test/java/de/srsoftware/oidc/datastore/file/FileStoreUserServiceTest.java b/de.srsoftware.oidc.datastore.file/src/test/java/de/srsoftware/oidc/datastore/file/FileStoreUserServiceTest.java
index 0050445..aa8660b 100644
--- a/de.srsoftware.oidc.datastore.file/src/test/java/de/srsoftware/oidc/datastore/file/FileStoreUserServiceTest.java
+++ b/de.srsoftware.oidc.datastore.file/src/test/java/de/srsoftware/oidc/datastore/file/FileStoreUserServiceTest.java
@@ -3,8 +3,8 @@ package de.srsoftware.oidc.datastore.file;
 
 import de.srsoftware.oidc.api.UserService;
 import de.srsoftware.oidc.api.UserServiceTest;
-import de.srsoftware.utils.PasswordHasher;
-import de.srsoftware.utils.UuidHasher;
+import de.srsoftware.tools.PasswordHasher;
+import de.srsoftware.tools.UuidHasher;
 import java.io.File;
 import java.io.IOException;
 import java.security.NoSuchAlgorithmException;
diff --git a/de.srsoftware.oidc.web/build.gradle b/de.srsoftware.oidc.web/build.gradle
deleted file mode 100644
index 8bd2c6a..0000000
--- a/de.srsoftware.oidc.web/build.gradle
+++ /dev/null
@@ -1,21 +0,0 @@
-plugins {
-    id 'java'
-}
-
-group = 'de.srsoftware'
-version = '1.0-SNAPSHOT'
-
-repositories {
-    mavenCentral()
-}
-
-dependencies {
-    testImplementation platform('org.junit:junit-bom:5.10.0')
-    testImplementation 'org.junit.jupiter:junit-jupiter'
-    implementation project(':de.srsoftware.oidc.api')
-    implementation project(':de.srsoftware.http')
-}
-
-test {
-    useJUnitPlatform()
-}
\ No newline at end of file
diff --git a/de.srsoftware.oidc.web/build.gradle.kts b/de.srsoftware.oidc.web/build.gradle.kts
new file mode 100644
index 0000000..d0131c6
--- /dev/null
+++ b/de.srsoftware.oidc.web/build.gradle.kts
@@ -0,0 +1,8 @@
+description = "SRSoftware OIDC: web module"
+
+dependencies{
+    implementation("de.srsoftware:tools.http:1.0.0")
+
+    implementation(project(":de.srsoftware.oidc.api"))
+}
+
diff --git a/de.srsoftware.oidc.web/src/main/java/de/srsoftware/oidc/web/Forward.java b/de.srsoftware.oidc.web/src/main/java/de/srsoftware/oidc/web/Forward.java
index 779461b..ac91658 100644
--- a/de.srsoftware.oidc.web/src/main/java/de/srsoftware/oidc/web/Forward.java
+++ b/de.srsoftware.oidc.web/src/main/java/de/srsoftware/oidc/web/Forward.java
@@ -4,7 +4,7 @@ package de.srsoftware.oidc.web;
 import static java.lang.System.Logger.Level.INFO;
 
 import com.sun.net.httpserver.HttpExchange;
-import de.srsoftware.http.PathHandler;
+import de.srsoftware.tools.PathHandler;
 import java.io.IOException;
 
 public class Forward extends PathHandler {
diff --git a/de.srsoftware.oidc.web/src/main/java/de/srsoftware/oidc/web/StaticPages.java b/de.srsoftware.oidc.web/src/main/java/de/srsoftware/oidc/web/StaticPages.java
index 15bcbe8..70a20ba 100644
--- a/de.srsoftware.oidc.web/src/main/java/de/srsoftware/oidc/web/StaticPages.java
+++ b/de.srsoftware.oidc.web/src/main/java/de/srsoftware/oidc/web/StaticPages.java
@@ -5,8 +5,8 @@ import static java.lang.System.Logger.Level.*;
 import static java.util.Optional.empty;
 
 import com.sun.net.httpserver.HttpExchange;
-import de.srsoftware.http.PathHandler;
 import de.srsoftware.oidc.api.ResourceLoader;
+import de.srsoftware.tools.PathHandler;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.net.MalformedURLException;
diff --git a/de.srsoftware.utils/build.gradle b/de.srsoftware.utils/build.gradle
deleted file mode 100644
index 7088e92..0000000
--- a/de.srsoftware.utils/build.gradle
+++ /dev/null
@@ -1,20 +0,0 @@
-plugins {
-    id 'java'
-}
-
-group = 'de.srsoftware'
-version = '1.0-SNAPSHOT'
-
-repositories {
-    mavenCentral()
-}
-
-dependencies {
-    testImplementation platform('org.junit:junit-bom:5.10.0')
-    testImplementation 'org.junit.jupiter:junit-jupiter'
-    implementation 'org.json:json:20240303'
-}
-
-test {
-    useJUnitPlatform()
-}
\ No newline at end of file
diff --git a/de.srsoftware.utils/src/main/java/de/srsoftware/utils/Error.java b/de.srsoftware.utils/src/main/java/de/srsoftware/utils/Error.java
deleted file mode 100644
index 458cdc2..0000000
--- a/de.srsoftware.utils/src/main/java/de/srsoftware/utils/Error.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/* © SRSoftware 2024 */
-package de.srsoftware.utils;
-
-
-import java.util.HashMap;
-import java.util.Map;
-import org.json.JSONObject;
-
-public class Error<T> implements Result<T> {
-	private final String        cause;
-	private Map<String, Object> metadata;
-
-	public Error(String cause) {
-		this.cause = cause;
-	}
-
-	public String cause() {
-		return cause;
-	}
-
-	@Override
-	public boolean isError() {
-		return true;
-	}
-
-	public static <T> Error<T> message(String cause, Object... tokens) {
-		var err      = new Error<T>(cause);
-		err.metadata = new HashMap<>();
-		for (int i = 0; i < tokens.length - 1; i += 2) {
-			err.metadata.put(tokens[i].toString(), tokens[i + 1]);
-		}
-		return err;
-	}
-
-	public JSONObject json() {
-		var json = new JSONObject(Map.of("error", cause));
-		if (metadata != null) json.put("metadata", metadata);
-		return json;
-	}
-}
diff --git a/de.srsoftware.utils/src/main/java/de/srsoftware/utils/Optionals.java b/de.srsoftware.utils/src/main/java/de/srsoftware/utils/Optionals.java
deleted file mode 100644
index 2a239af..0000000
--- a/de.srsoftware.utils/src/main/java/de/srsoftware/utils/Optionals.java
+++ /dev/null
@@ -1,15 +0,0 @@
-/* © SRSoftware 2024 */
-package de.srsoftware.utils;
-import static java.util.Optional.empty;
-
-import java.util.Optional;
-
-public class Optionals {
-	public static <T> Optional<T> nullable(T val) {
-		return Optional.ofNullable(val);
-	}
-
-	public static Optional<String> emptyIfBlank(String text) {
-		return text == null || text.isBlank() ? empty() : nullable(text.trim());
-	}
-}
diff --git a/de.srsoftware.utils/src/main/java/de/srsoftware/utils/PasswordHasher.java b/de.srsoftware.utils/src/main/java/de/srsoftware/utils/PasswordHasher.java
deleted file mode 100644
index 3bb60b1..0000000
--- a/de.srsoftware.utils/src/main/java/de/srsoftware/utils/PasswordHasher.java
+++ /dev/null
@@ -1,11 +0,0 @@
-/* © SRSoftware 2024 */
-package de.srsoftware.utils;
-
-public interface PasswordHasher<T> {
-	public String hash(String password, String salt);
-	public String salt(String hashedPassword);
-
-	public default boolean matches(String plaintextPassword, String hashedPassword) {
-		return hash(plaintextPassword, salt(hashedPassword)).equals(hashedPassword);
-	}
-}
diff --git a/de.srsoftware.utils/src/main/java/de/srsoftware/utils/Paths.java b/de.srsoftware.utils/src/main/java/de/srsoftware/utils/Paths.java
deleted file mode 100644
index 34b3946..0000000
--- a/de.srsoftware.utils/src/main/java/de/srsoftware/utils/Paths.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/* © SRSoftware 2024 */
-package de.srsoftware.utils;
-
-import java.io.File;
-import java.nio.file.Path;
-
-public class Paths {
-	public static Path configDir(String applicationName) {
-		String home = System.getProperty("user.home");
-		return Path.of(home, ".config", applicationName);
-	}
-
-	public static Path configDir(Class clazz) {
-		return configDir(clazz.getSimpleName());
-	}
-
-	public static Path configDir(Object clazz) {
-		return configDir(clazz.getClass());
-	}
-
-	public static String extension(File file) {
-		var parts = file.getName().split("\\.");
-		return parts.length == 1 ? "" : parts[parts.length - 1];
-	}
-}
diff --git a/de.srsoftware.utils/src/main/java/de/srsoftware/utils/Payload.java b/de.srsoftware.utils/src/main/java/de/srsoftware/utils/Payload.java
deleted file mode 100644
index df012bd..0000000
--- a/de.srsoftware.utils/src/main/java/de/srsoftware/utils/Payload.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/* © SRSoftware 2024 */
-package de.srsoftware.utils;
-
-
-public class Payload<T> implements Result<T> {
-	private final T object;
-
-	public Payload(T object) {
-		this.object = object;
-	}
-
-	public static <T> Payload<T> of(T object) {
-		return new Payload<>(object);
-	}
-
-	@Override
-	public boolean isError() {
-		return false;
-	}
-
-	public T get() {
-		return object;
-	}
-}
diff --git a/de.srsoftware.utils/src/main/java/de/srsoftware/utils/Result.java b/de.srsoftware.utils/src/main/java/de/srsoftware/utils/Result.java
deleted file mode 100644
index 7a72b24..0000000
--- a/de.srsoftware.utils/src/main/java/de/srsoftware/utils/Result.java
+++ /dev/null
@@ -1,6 +0,0 @@
-/* © SRSoftware 2024 */
-package de.srsoftware.utils;
-
-public interface Result<T> {
-	public boolean isError();
-}
diff --git a/de.srsoftware.utils/src/main/java/de/srsoftware/utils/Strings.java b/de.srsoftware.utils/src/main/java/de/srsoftware/utils/Strings.java
deleted file mode 100644
index 83fa8ac..0000000
--- a/de.srsoftware.utils/src/main/java/de/srsoftware/utils/Strings.java
+++ /dev/null
@@ -1,10 +0,0 @@
-/* © SRSoftware 2024 */
-package de.srsoftware.utils;
-
-import java.util.UUID;
-
-public class Strings {
-	public static String uuid() {
-		return UUID.randomUUID().toString();
-	}
-}
diff --git a/de.srsoftware.utils/src/main/java/de/srsoftware/utils/UuidHasher.java b/de.srsoftware.utils/src/main/java/de/srsoftware/utils/UuidHasher.java
deleted file mode 100644
index ba7e2fa..0000000
--- a/de.srsoftware.utils/src/main/java/de/srsoftware/utils/UuidHasher.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/* © SRSoftware 2024 */
-package de.srsoftware.utils;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-
-
-public class UuidHasher implements PasswordHasher<String> {
-	private static final String SHA256 = "SHA-256";
-
-	private final MessageDigest digest;
-
-	public UuidHasher() throws NoSuchAlgorithmException {
-		digest = MessageDigest.getInstance(SHA256);
-	}
-
-	@Override
-	public String hash(String password, String uuid) {
-		var salt       = uuid;
-		var saltedPass = "%s %s".formatted(salt, password);
-		var bytes      = digest.digest(saltedPass.getBytes(UTF_8));
-
-		return "%s@%s".formatted(hex(bytes), salt);
-	}
-
-	@Override
-	public String salt(String hashedPassword) {
-		return hashedPassword.split("@", 2)[1];
-	}
-
-	public static String hex(byte[] bytes) {
-		StringBuilder sb = new StringBuilder(bytes.length * 2);
-		for (byte b : bytes) sb.append(String.format("%02x", b));
-		return sb.toString();
-	}
-}
diff --git a/settings.gradle b/settings.gradle
deleted file mode 100644
index 6fd4b19..0000000
--- a/settings.gradle
+++ /dev/null
@@ -1,11 +0,0 @@
-rootProject.name = 'LightOIDC'
-include 'de.srsoftware.http'
-include 'de.srsoftware.logging'
-include 'de.srsoftware.oidc.api'
-include 'de.srsoftware.oidc.app'
-include 'de.srsoftware.oidc.backend'
-include 'de.srsoftware.oidc.datastore.encrypted'
-include 'de.srsoftware.oidc.datastore.file'
-include 'de.srsoftware.oidc.web'
-include 'de.srsoftware.utils'
-
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..590fb8a
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,8 @@
+rootProject.name = "LightOIDC"
+include("de.srsoftware.oidc.api")
+include("de.srsoftware.oidc.app")
+include("de.srsoftware.oidc.backend")
+include("de.srsoftware.oidc.datastore.encrypted")
+include("de.srsoftware.oidc.datastore.file")
+include("de.srsoftware.oidc.web")
+