aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/juick/www/api/Mastodon.java
diff options
context:
space:
mode:
authorGravatar Vitaly Takmazov2023-01-04 03:38:19 +0300
committerGravatar Vitaly Takmazov2023-01-04 05:46:16 +0300
commitc471503ede9aad91193ff6f93966196e6aff15d6 (patch)
tree8c70c8f58b140465be651cd019f26eadd476711f /src/main/java/com/juick/www/api/Mastodon.java
parent086d9a7625bfc5a386f5b1028d364fb546c2fa9d (diff)
OAuth authentication for Mastodon and ActivityPub C2S
Diffstat (limited to 'src/main/java/com/juick/www/api/Mastodon.java')
-rw-r--r--src/main/java/com/juick/www/api/Mastodon.java118
1 files changed, 118 insertions, 0 deletions
diff --git a/src/main/java/com/juick/www/api/Mastodon.java b/src/main/java/com/juick/www/api/Mastodon.java
new file mode 100644
index 00000000..69f0f4f6
--- /dev/null
+++ b/src/main/java/com/juick/www/api/Mastodon.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2008-2023, Juick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.juick.www.api;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.juick.model.User;
+import com.juick.service.UserService;
+import com.juick.www.WebApp;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.MediaType;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
+import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
+import org.springframework.web.bind.annotation.*;
+
+import javax.inject.Inject;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.UUID;
+
+@RestController
+public class Mastodon {
+ @Value("${web_domain:localhost}")
+ private String domain;
+ @Inject
+ WebApp webApp;
+ @Inject
+ UserService userService;
+ @Inject
+ RegisteredClientRepository registeredClientRepository;
+
+ public record ApplicationRequest(@JsonProperty("client_name") String clientName,
+ @JsonProperty("redirect_uris") String redirectUris,
+ @JsonProperty("scopes") String scopes) {
+ }
+
+ public record ApplicationResponse(String id,
+ @JsonProperty("client_name") String name,
+ @JsonProperty("redirect_uri") String redirectUri,
+ @JsonProperty("client_id") String clientId,
+ @JsonProperty("client_secret") String clientSecret) {
+ }
+
+ public record CredentialAccount(String id, String username, String acct,
+ @JsonProperty("display_name") String displayName,
+ @JsonProperty("followers_count") Integer followersCount,
+ @JsonProperty("following_count") Integer followingCount,
+ String avatar) {
+
+ }
+ private Collection<String> parseScopes(String s) {
+ return s != null ? Arrays.asList(s.split(" ")) : Collections.emptyList();
+ }
+
+ @PostMapping(value = "/api/v1/apps", consumes = { MediaType.APPLICATION_JSON_VALUE })
+ public ApplicationResponse apps(@RequestBody ApplicationRequest application) {
+ return apps(application.clientName(), application.redirectUris(), application.scopes());
+ }
+
+ @PostMapping(value = "/api/v1/apps", consumes = { MediaType.APPLICATION_FORM_URLENCODED_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE })
+ public ApplicationResponse apps(@RequestParam("client_name") String clientName,
+ @RequestParam("redirect_uris") String redirectUris,
+ @RequestParam("scopes") String scopes) {
+ var secret = UUID.randomUUID().toString();
+ RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
+ .clientId(UUID.randomUUID().toString())
+ .clientSecret("{noop}" + secret)
+ .clientName(clientName)
+ .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
+ .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
+ .redirectUri(redirectUris)
+ .scopes((coll) -> coll.addAll(parseScopes(scopes)))
+ .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build();
+ registeredClientRepository.save(registeredClient);
+ return new ApplicationResponse(
+ registeredClient.getId(),
+ registeredClient.getClientName(),
+ String.join(",", registeredClient.getRedirectUris()),
+ registeredClient.getClientId(),
+ secret
+ );
+ }
+ public record Instance(String domain) {}
+ @GetMapping("/api/v1/instance")
+ public Instance getInstance() {
+ return new Instance(domain);
+ }
+ @GetMapping("/api/v1/accounts/verify_credentials")
+ public CredentialAccount account(@ModelAttribute User visitor) {
+ return new CredentialAccount(
+ String.valueOf(visitor.getUid()),
+ visitor.getName(),
+ visitor.getName(),
+ visitor.getFullName(),
+ userService.getUserReaders(visitor.getUid()).size(),
+ userService.getUserFriends(visitor.getUid()).size(),
+ webApp.getAvatarUrl(visitor)
+ );
+ }
+}