diff --git a/src/main/kotlin/dev/usbharu/hideout/config/SecurityConfig.kt b/src/main/kotlin/dev/usbharu/hideout/config/SecurityConfig.kt index 589dc701..f98420b3 100644 --- a/src/main/kotlin/dev/usbharu/hideout/config/SecurityConfig.kt +++ b/src/main/kotlin/dev/usbharu/hideout/config/SecurityConfig.kt @@ -12,7 +12,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.core.annotation.Order -import org.springframework.http.MediaType import org.springframework.security.config.Customizer import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity @@ -28,7 +27,6 @@ import org.springframework.security.oauth2.server.authorization.token.OAuth2Toke import org.springframework.security.web.SecurityFilterChain import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher -import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher import org.springframework.web.servlet.handler.HandlerMappingIntrospector import java.security.KeyPairGenerator import java.security.interfaces.RSAPrivateKey @@ -47,9 +45,8 @@ class SecurityConfig { OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http) http .exceptionHandling { - it.defaultAuthenticationEntryPointFor( - LoginUrlAuthenticationEntryPoint("/login"), - MediaTypeRequestMatcher(MediaType.TEXT_HTML) + it.authenticationEntryPoint( + LoginUrlAuthenticationEntryPoint("/login") ) } .oauth2ResourceServer { @@ -58,34 +55,33 @@ class SecurityConfig { return http.build() } + @Bean @Order(2) fun defaultSecurityFilterChain(http: HttpSecurity, introspector: HandlerMappingIntrospector): SecurityFilterChain { val builder = MvcRequestMatcher.Builder(introspector) - http.authorizeHttpRequests { - it.requestMatchers(builder.pattern("/api/v1/**")).hasAnyAuthority("SCOPE_read", "SCOPE_read:accounts") - } - http - .authorizeHttpRequests { - it.requestMatchers( - builder.pattern("/inbox"), - builder.pattern("/api/v1/apps"), - builder.pattern("/api/v1/instance/**") - ).permitAll() - } http .authorizeHttpRequests { it.requestMatchers(PathRequest.toH2Console()).permitAll() - } - http - .authorizeHttpRequests { - it.anyRequest().authenticated() + it.requestMatchers( + builder.pattern("/inbox"), + builder.pattern("/api/v1/apps"), + builder.pattern("/api/v1/instance/**"), + builder.pattern("/.well-known/**"), + builder.pattern("/error"), + builder.pattern("/nodeinfo/2.0") + ).permitAll() + it.requestMatchers(builder.pattern("/change-password")).authenticated() + it.requestMatchers(builder.pattern("/api/v1/accounts/verify_credentials")) + .hasAnyAuthority("SCOPE_read", "SCOPE_read:accounts") + it.anyRequest().permitAll() } http .oauth2ResourceServer { it.jwt(Customizer.withDefaults()) } + .passwordManagement { } .formLogin(Customizer.withDefaults()) .csrf { it.ignoringRequestMatchers(builder.pattern("/api/**")) diff --git a/src/main/kotlin/dev/usbharu/hideout/controller/wellknown/HostMetaController.kt b/src/main/kotlin/dev/usbharu/hideout/controller/wellknown/HostMetaController.kt new file mode 100644 index 00000000..1c92dc73 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/controller/wellknown/HostMetaController.kt @@ -0,0 +1,42 @@ +package dev.usbharu.hideout.controller.wellknown + +import dev.usbharu.hideout.config.ApplicationConfig +import org.intellij.lang.annotations.Language +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +class HostMetaController(private val applicationConfig: ApplicationConfig) { + + val xml = //language=XML + """ + + +""" + + @Language("JSON") + val json = """{ + "links": [ + { + "rel": "lrdd", + "type": "application/jrd+json", + "template": "${applicationConfig.url}/.well-known/webfinger?resource={uri}" + } + ] +}""" + + @GetMapping("/.well-known/host-meta", produces = ["application/xml"]) + fun hostmeta(): ResponseEntity { + return ResponseEntity(xml, HttpStatus.OK) + } + + @GetMapping("/.well-known/host-meta.json", produces = ["application/json"]) + fun hostmetJson(): ResponseEntity { + return ResponseEntity(json, HttpStatus.OK) + } + + +} diff --git a/src/main/kotlin/dev/usbharu/hideout/controller/wellknown/NodeinfoController.kt b/src/main/kotlin/dev/usbharu/hideout/controller/wellknown/NodeinfoController.kt new file mode 100644 index 00000000..a6630fe7 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/controller/wellknown/NodeinfoController.kt @@ -0,0 +1,64 @@ +package dev.usbharu.hideout.controller.wellknown + +import dev.usbharu.hideout.config.ApplicationConfig +import dev.usbharu.hideout.domain.model.wellknown.Nodeinfo +import dev.usbharu.hideout.domain.model.wellknown.Nodeinfo2_0 +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +class NodeinfoController(private val applicationConfig: ApplicationConfig) { + @GetMapping("/.well-known/nodeinfo") + fun nodeinfo(): ResponseEntity { + return ResponseEntity( + Nodeinfo( + listOf( + Nodeinfo.Links( + "http://nodeinfo.diaspora.software/ns/schema/2.0", + "${applicationConfig.url}/nodeinfo/2.0" + ) + ) + ), HttpStatus.OK + ) + } + + @GetMapping("/nodeinfo/2.0") + fun nodeinfo2_0(): ResponseEntity { + return ResponseEntity( + Nodeinfo2_0( + version = "2.0", + software = Nodeinfo2_0.Software( + name = "hideout", + version = "0.0.1" + ), + protocols = listOf("activitypub"), + services = Nodeinfo2_0.Services( + inbound = emptyList(), + outbound = emptyList() + ), + openRegistrations = false, + usage = Nodeinfo2_0.Usage( + users = Nodeinfo2_0.Usage.Users( + total = 1, + activeHalfYear = 1, + activeMonth = 1 + ), + localPosts = 1, + localComments = 0 + ), + metadata = Nodeinfo2_0.Metadata( + nodeName = "hideout", + nodeDescription = "hideout test server", + maintainer = Nodeinfo2_0.Metadata.Maintainer("usbharu", "i@usbharu.dev"), + langs = emptyList(), + tosUrl = "", + repositoryUrl = "https://github.com/usbharu/Hideout", + feedbackUrl = "https://github.com/usbharu/Hideout/issues/new/choose", + ) + ), + HttpStatus.OK + ) + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/controller/wellknown/WebFingerController.kt b/src/main/kotlin/dev/usbharu/hideout/controller/wellknown/WebFingerController.kt new file mode 100644 index 00000000..3ea7fffd --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/controller/wellknown/WebFingerController.kt @@ -0,0 +1,37 @@ +package dev.usbharu.hideout.controller.wellknown + +import dev.usbharu.hideout.config.ApplicationConfig +import dev.usbharu.hideout.domain.model.wellknown.WebFinger +import dev.usbharu.hideout.service.api.WebFingerApiService +import dev.usbharu.hideout.util.AcctUtil +import kotlinx.coroutines.runBlocking +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.stereotype.Controller +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestParam +import java.net.URL + +@Controller +class WebFingerController( + private val webFingerApiService: WebFingerApiService, + private val applicationConfig: ApplicationConfig +) { + @GetMapping("/.well-known/webfinger") + fun webfinger(@RequestParam("resource") resource: String): ResponseEntity = runBlocking { + val acct = AcctUtil.parse(resource.replace("acct:", "")) + val user = + webFingerApiService.findByNameAndDomain(acct.username, acct.domain ?: URL(applicationConfig.url).host) + val webFinger = WebFinger( + "acct:${user.name}@${user.domain}", + listOf( + WebFinger.Link( + "self", + "application/activity+json", + applicationConfig.url + "/users/" + user.id + ) + ) + ) + ResponseEntity(webFinger, HttpStatus.OK) + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/wellknown/Nodeinfo.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/wellknown/Nodeinfo.kt new file mode 100644 index 00000000..46adc8b8 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/wellknown/Nodeinfo.kt @@ -0,0 +1,11 @@ +package dev.usbharu.hideout.domain.model.wellknown + + +data class Nodeinfo( + val links: List +) { + data class Links( + val rel: String, + val href: String + ) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/wellknown/Nodeinfo2_0.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/wellknown/Nodeinfo2_0.kt new file mode 100644 index 00000000..fbce0298 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/wellknown/Nodeinfo2_0.kt @@ -0,0 +1,48 @@ +package dev.usbharu.hideout.domain.model.wellknown + +data class Nodeinfo2_0( + val version: String, + val software: Software, + val protocols: List, + val services: Services, + val openRegistrations: Boolean, + val usage: Usage, + val metadata: Metadata +) { + data class Software( + val name: String, + val version: String + ) + + data class Services( + val inbound: List, + val outbound: List + ) + + data class Usage( + val users: Users, + val localPosts: Int, + val localComments: Int + ) { + data class Users( + val total: Int, + val activeHalfYear: Int, + val activeMonth: Int + ) + } + + data class Metadata( + val nodeName: String, + val nodeDescription: String, + val maintainer: Maintainer, + val langs: List, + val tosUrl: String, + val repositoryUrl: String, + val feedbackUrl: String, + ) { + data class Maintainer( + val name: String, + val email: String + ) + } +}