diff options
Diffstat (limited to 'clams-server/src')
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); + } +} |
