summaryrefslogtreecommitdiff
path: root/src/main/java/moe
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/moe')
-rw-r--r--src/main/java/moe/nea/prickly/config/Config.java1
-rw-r--r--src/main/java/moe/nea/prickly/model/AuthorizationRequest.java10
-rw-r--r--src/main/java/moe/nea/prickly/model/package-info.java4
-rw-r--r--src/main/java/moe/nea/prickly/server/Server.java23
-rw-r--r--src/main/java/moe/nea/prickly/util/JsonHelper.java14
-rw-r--r--src/main/java/moe/nea/prickly/util/OAuthUtil.java54
-rw-r--r--src/main/java/moe/nea/prickly/util/package-info.java4
7 files changed, 110 insertions, 0 deletions
diff --git a/src/main/java/moe/nea/prickly/config/Config.java b/src/main/java/moe/nea/prickly/config/Config.java
index 624b1e3..1d8e428 100644
--- a/src/main/java/moe/nea/prickly/config/Config.java
+++ b/src/main/java/moe/nea/prickly/config/Config.java
@@ -25,6 +25,7 @@ public class Config {
public final String SLUG = path.lastPart();
public final String NAME = path.join("NAME").requireString();
public final String HOMEPAGE = path.join("HOMEPAGE").requireString();
+ public final String REDIRECT_URI = path.join("REDIRECT_URI").requireString();
}
static class ConfigStruct {
diff --git a/src/main/java/moe/nea/prickly/model/AuthorizationRequest.java b/src/main/java/moe/nea/prickly/model/AuthorizationRequest.java
new file mode 100644
index 0000000..bd5c74f
--- /dev/null
+++ b/src/main/java/moe/nea/prickly/model/AuthorizationRequest.java
@@ -0,0 +1,10 @@
+/* (C) 2025 Linnea Gräf - Licensed to everyone under the BSD 3 Clause License */
+package moe.nea.prickly.model;
+
+import java.net.URI;
+import java.util.List;
+import moe.nea.prickly.util.OAuthUtil;
+import org.jspecify.annotations.Nullable;
+
+public record AuthorizationRequest(
+ OAuthUtil.ResponseType responseType, URI redirectUri, @Nullable String state, List<String> scope) {}
diff --git a/src/main/java/moe/nea/prickly/model/package-info.java b/src/main/java/moe/nea/prickly/model/package-info.java
new file mode 100644
index 0000000..7c56f0d
--- /dev/null
+++ b/src/main/java/moe/nea/prickly/model/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package moe.nea.prickly.model;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/src/main/java/moe/nea/prickly/server/Server.java b/src/main/java/moe/nea/prickly/server/Server.java
index 2ff7bc9..9bb6df1 100644
--- a/src/main/java/moe/nea/prickly/server/Server.java
+++ b/src/main/java/moe/nea/prickly/server/Server.java
@@ -1,10 +1,16 @@
/* (C) 2025 Linnea Gräf - Licensed to everyone under the BSD 3 Clause License */
package moe.nea.prickly.server;
+import com.google.common.base.Preconditions;
import io.javalin.Javalin;
import io.javalin.config.JavalinConfig;
+import io.javalin.rendering.template.JavalinJte;
+import java.util.Map;
+import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import moe.nea.prickly.config.Config;
+import moe.nea.prickly.model.AuthorizationRequest;
+import moe.nea.prickly.util.OAuthUtil;
@Slf4j
public class Server {
@@ -29,10 +35,27 @@ public class Server {
javalin.get(prefix + "/", ctx -> {
ctx.redirect(application.HOMEPAGE);
});
+ javalin.get(prefix + "/authorize", ctx -> {
+ var responseType = OAuthUtil.parseResponseType(ctx.queryParam("response_type"));
+ var redirectUri = OAuthUtil.verifyRedirectUrl(ctx.queryParam("redirect_uri"), application.REDIRECT_URI);
+ var state = ctx.queryParam("state");
+ var clientId = ctx.queryParam("client_id");
+ Preconditions.checkArgument(
+ Objects.equals(clientId, application.SLUG), "client_id does not match application slug");
+ var scope = OAuthUtil.parseScopes(ctx.queryParam("scope"));
+ ctx.render(
+ "authorize.jte",
+ Map.of(
+ "application",
+ application,
+ "authorizationRequest",
+ new AuthorizationRequest(responseType, redirectUri, state, scope)));
+ });
}
protected void configure(JavalinConfig config) {
log.info("configuring javalin");
+ config.fileRenderer(new JavalinJte());
}
public void start() {
diff --git a/src/main/java/moe/nea/prickly/util/JsonHelper.java b/src/main/java/moe/nea/prickly/util/JsonHelper.java
new file mode 100644
index 0000000..56ab175
--- /dev/null
+++ b/src/main/java/moe/nea/prickly/util/JsonHelper.java
@@ -0,0 +1,14 @@
+/* (C) 2025 Linnea Gräf - Licensed to everyone under the BSD 3 Clause License */
+package moe.nea.prickly.util;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.SneakyThrows;
+
+public class JsonHelper {
+ public static ObjectMapper mapper = new ObjectMapper();
+
+ @SneakyThrows
+ public static String encode(Object object) {
+ return mapper.writeValueAsString(object);
+ }
+}
diff --git a/src/main/java/moe/nea/prickly/util/OAuthUtil.java b/src/main/java/moe/nea/prickly/util/OAuthUtil.java
new file mode 100644
index 0000000..ec3b7fd
--- /dev/null
+++ b/src/main/java/moe/nea/prickly/util/OAuthUtil.java
@@ -0,0 +1,54 @@
+/* (C) 2025 Linnea Gräf - Licensed to everyone under the BSD 3 Clause License */
+package moe.nea.prickly.util;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import java.net.URI;
+import java.util.List;
+import java.util.Objects;
+import org.jspecify.annotations.Nullable;
+
+public class OAuthUtil {
+ private static final Splitter SCOPE_SPLITTER =
+ Splitter.on(' ').omitEmptyStrings().trimResults();
+
+ public static List<String> parseScopes(@Nullable String scope) {
+ if (scope == null) return List.of();
+ return SCOPE_SPLITTER.splitToList(scope);
+ }
+
+ public static URI verifyRedirectUrl(@Nullable String actualRedirectUri, String expectedRedirectUri) {
+ Objects.requireNonNull(expectedRedirectUri, "expected redirect uri is null");
+ if (actualRedirectUri == null) return verifyRedirectUrl(expectedRedirectUri, expectedRedirectUri);
+
+ var expected = URI.create(expectedRedirectUri);
+ var actual = URI.create(actualRedirectUri).normalize();
+ Preconditions.checkArgument(actual.isAbsolute(), "redirect URI must be absolute");
+ Preconditions.checkArgument(actual.getFragment() == null, "redirect URI must not have a fragment");
+ Preconditions.checkArgument(
+ Objects.equals(actual.getScheme(), expected.getScheme()),
+ "scheme differs from registered redirect URI");
+ Preconditions.checkArgument(
+ Objects.equals(actual.getAuthority(), expected.getAuthority()),
+ "origin differs from registered redirect URI");
+ Preconditions.checkArgument(actual.getUserInfo() == null, "redirect URI must not have a user info");
+ Preconditions.checkArgument(
+ expected.getPath() == null || actual.getPath().startsWith(expected.getPath()),
+ "redirect URI must be a subpath of registered redirect URI");
+ return actual;
+ }
+
+ public static ResponseType parseResponseType(@Nullable String responseType) {
+ return switch (responseType) {
+ case "code" -> ResponseType.CODE;
+ case "token" -> ResponseType.TOKEN;
+ case null -> throw new IllegalArgumentException("missing response_type");
+ default -> throw new IllegalArgumentException("invalid response_type " + responseType);
+ };
+ }
+
+ public enum ResponseType {
+ TOKEN,
+ CODE
+ }
+}
diff --git a/src/main/java/moe/nea/prickly/util/package-info.java b/src/main/java/moe/nea/prickly/util/package-info.java
new file mode 100644
index 0000000..ea2e468
--- /dev/null
+++ b/src/main/java/moe/nea/prickly/util/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package moe.nea.prickly.util;
+
+import org.jspecify.annotations.NullMarked;