From c471503ede9aad91193ff6f93966196e6aff15d6 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Wed, 4 Jan 2023 03:38:19 +0300 Subject: OAuth authentication for Mastodon and ActivityPub C2S --- src/main/java/com/juick/www/api/Mastodon.java | 118 ++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 src/main/java/com/juick/www/api/Mastodon.java (limited to 'src/main/java/com/juick/www/api/Mastodon.java') 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 . + */ + +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 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) + ); + } +} -- cgit v1.2.3