summaryrefslogtreecommitdiff
path: root/clams-server
diff options
context:
space:
mode:
authorElias Haugsbakk <[email protected]>2026-07-20 21:10:36 +0200
committerElias Haugsbakk <[email protected]>2026-07-20 21:10:36 +0200
commitdb076ccc51220b4393044b18d315a5a691a2d6da (patch)
treebd63f725a7309b8dc47e3a21baa2041aef99ae6d /clams-server
parent778e2fedae9624fe142bff9eeceaa1bca2c1aa71 (diff)
implement authentication through an auth-token
Diffstat (limited to 'clams-server')
-rw-r--r--clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppConfig.java27
-rw-r--r--clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppContext.java4
-rw-r--r--clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppRoutes.java53
-rw-r--r--clams-server/src/main/java/no/eliashaugsbakk/clams/server/repository/SqliteManager.java24
-rw-r--r--clams-server/src/main/java/no/eliashaugsbakk/clams/server/service/AuthService.java19
5 files changed, 86 insertions, 41 deletions
diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppConfig.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppConfig.java
index 1d68166..ada9847 100644
--- a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppConfig.java
+++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppConfig.java
@@ -5,6 +5,8 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.security.SecureRandom;
+import java.util.Base64;
import java.util.Properties;
public class AppConfig {
@@ -23,7 +25,7 @@ public class AppConfig {
properties.load(reader);
ensureStorageDirectoryExists();
} catch (IOException e) {
- System.err.println("Failed to load configuration file: " + e.getMessage());
+ throw new RuntimeException("Failed to read configuration file: " + configPath, e);
}
}
@@ -43,10 +45,19 @@ public class AppConfig {
try {
Files.createDirectories(configPath.getParent());
+ byte[] tokenBytes = new byte[32];
+ new SecureRandom().nextBytes(tokenBytes);
+ String token = Base64.getUrlEncoder().withoutPadding().encodeToString(tokenBytes);
+
try (BufferedWriter writer = Files.newBufferedWriter(configPath)) {
- writer.write("storage_location=./data/\n\n");
+ writer.write(String.format("""
+ storage_location=./data/
+
+ authorization_token=%s
+ """, token));
}
- System.out.println("Generated a default configuration file at: " + configPath);
+ IO.println("Generated a default configuration file at: " + configPath);
+ IO.println("Generated authentication token: " + token);
} catch (IOException e) {
System.err.println("Could not create default config file: " + e.getMessage());
@@ -56,4 +67,14 @@ public class AppConfig {
public String getStorageLocation() {
return properties.getProperty("storage_location", "./data");
}
+
+ public String getAuthToken() {
+ String token = properties.getProperty("authorization_token");
+ if (token == null || token.isBlank()) {
+ System.err.println("CRITICAL: 'authorization_token' is missing or empty in config properties!");
+
+ throw new IllegalStateException("CRITICAL: 'authorization_token' is missing or empty in config properties!");
+ }
+ return token;
+ }
}
diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppContext.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppContext.java
index c5ef242..9f88053 100644
--- a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppContext.java
+++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppContext.java
@@ -9,6 +9,7 @@ import no.eliashaugsbakk.clams.server.repository.BlogPostRepoSqlite;
import no.eliashaugsbakk.clams.server.repository.MediaRepo;
import no.eliashaugsbakk.clams.server.repository.MediaRepoSqlite;
import no.eliashaugsbakk.clams.server.repository.SqliteManager;
+import no.eliashaugsbakk.clams.server.service.AuthService;
import no.eliashaugsbakk.clams.server.service.SlugService;
public class AppContext implements AutoCloseable {
@@ -16,6 +17,7 @@ public class AppContext implements AutoCloseable {
private final BlogController blogController;
private final MediaController mediaController;
private final PostController postController;
+ private final AuthService authService;
public AppContext() {
AppConfig appConfig = new AppConfig();
@@ -32,11 +34,13 @@ public class AppContext implements AutoCloseable {
this.blogController = new BlogController(dbManager);
this.mediaController = new MediaController(mediaRepo, appConfig);
this.postController = new PostController(blogPostRepo, slugService);
+ this.authService = new AuthService(appConfig);
}
public BlogController getBlogController() { return blogController; }
public MediaController getMediaController() { return mediaController; }
public PostController getPostController() { return postController; }
+ public AuthService getAuthService() { return authService; }
@Override
public void close() {
diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppRoutes.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppRoutes.java
index fb42dfc..fb5c936 100644
--- a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppRoutes.java
+++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/config/AppRoutes.java
@@ -1,14 +1,20 @@
package no.eliashaugsbakk.clams.server.config;
-import static io.javalin.apibuilder.ApiBuilder.*;
+import static io.javalin.apibuilder.ApiBuilder.before;
+import static io.javalin.apibuilder.ApiBuilder.delete;
+import static io.javalin.apibuilder.ApiBuilder.get;
+import static io.javalin.apibuilder.ApiBuilder.path;
+import static io.javalin.apibuilder.ApiBuilder.post;
+import static io.javalin.apibuilder.ApiBuilder.put;
+
import io.javalin.apibuilder.EndpointGroup;
import java.util.Map;
public class AppRoutes implements EndpointGroup {
- private final AppContext ctx;
+ private final AppContext appContext;
- public AppRoutes(AppContext ctx) {
- this.ctx = ctx;
+ public AppRoutes(AppContext appContext) {
+ this.appContext = appContext;
}
@Override
@@ -20,19 +26,38 @@ public class AppRoutes implements EndpointGroup {
Map.of("page_title", "Projects - Elias Haugsbakk", "page_css", "projects")));
path("blog", () -> {
- get(ctx.getBlogController()::handleBlogRequest);
- get("{slug}", ctx.getBlogController()::handleGetPost);
+ get(appContext.getBlogController()::handleBlogRequest);
+ get("{slug}", appContext.getBlogController()::handleGetPost);
});
path("api", () -> {
- post("posts", ctx.getPostController()::handlePostPost);
- put("posts/{slug}", ctx.getPostController()::handlePutPost);
- delete("posts/{slug}", ctx.getPostController()::handleDeletePost);
-
- get("media", ctx.getMediaController()::handleGetMediaIndex);
- get("media/{uuid}", ctx.getMediaController()::handleGetMedia);
- post("media", ctx.getMediaController()::handlePostMedia);
- delete("media/{uuid}", ctx.getMediaController()::handleDeleteMedia);
+ before("*", ctx -> {
+ String authHeader = ctx.header("Authorization");
+
+ if (authHeader == null || !authHeader.startsWith("Bearer ")) {
+ ctx.status(401).json(Map.of("error", "Unauthorized",
+ "message", "Missing or malformed Authorization header."));
+ ctx.skipRemainingHandlers();
+ return;
+ }
+
+ String token = authHeader.substring(7).trim();
+ if (!appContext.getAuthService().isValid(token)) {
+ ctx.status(403)
+ .json(Map.of("error", "Forbidden",
+ "message", "Invalid API validation token."));
+ ctx.skipRemainingHandlers();
+ }
+ });
+
+ post("posts", appContext.getPostController()::handlePostPost);
+ put("posts/{slug}", appContext.getPostController()::handlePutPost);
+ delete("posts/{slug}", appContext.getPostController()::handleDeletePost);
+
+ get("media", appContext.getMediaController()::handleGetMediaIndex);
+ get("media/{uuid}", appContext.getMediaController()::handleGetMedia);
+ post("media", appContext.getMediaController()::handlePostMedia);
+ delete("media/{uuid}", appContext.getMediaController()::handleDeleteMedia);
});
}
}
diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/repository/SqliteManager.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/repository/SqliteManager.java
index a192cee..06a4ee2 100644
--- a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/repository/SqliteManager.java
+++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/repository/SqliteManager.java
@@ -38,27 +38,6 @@ public class SqliteManager implements AutoCloseable {
);
""";
- String idxPublished = """
- CREATE INDEX IF NOT EXISTS idx_posts_published ON posts(published DESC);
- """;
-
- String tags = """
- CREATE TABLE IF NOT EXISTS tags (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- name TEXT NOT NULL UNIQUE
- );
- """;
-
- String post_tags = """
- CREATE TABLE IF NOT EXISTS post_tags (
- post_id INTEGER,
- tag_id INTEGER,
- PRIMARY KEY (post_id, tag_id),
- FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE,
- FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
- );
- """;
-
String images = """
CREATE TABLE IF NOT EXISTS images (
uuid TEXT PRIMARY KEY,
@@ -71,9 +50,6 @@ public class SqliteManager implements AutoCloseable {
try (Connection conn = dataSource.getConnection(); Statement stmt = conn.createStatement()) {
stmt.execute(posts);
- stmt.execute(idxPublished);
- stmt.execute(tags);
- stmt.execute(post_tags);
stmt.execute(images);
} catch (SQLException e) {
diff --git a/clams-server/src/main/java/no/eliashaugsbakk/clams/server/service/AuthService.java b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/service/AuthService.java
new file mode 100644
index 0000000..e52613b
--- /dev/null
+++ b/clams-server/src/main/java/no/eliashaugsbakk/clams/server/service/AuthService.java
@@ -0,0 +1,19 @@
+package no.eliashaugsbakk.clams.server.service;
+
+import no.eliashaugsbakk.clams.server.config.AppConfig;
+
+public class AuthService {
+ private final String tokenHash;
+
+ public AuthService(AppConfig appConfig) {
+ this.tokenHash = appConfig.getAuthToken();
+ }
+
+ public boolean isValid(String incomingToken) {
+ if (incomingToken == null || incomingToken.isBlank()) {
+ return false;
+ }
+
+ return incomingToken.equals(tokenHash);
+ }
+}