diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 66a5c04a..de183e3e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -21,10 +21,10 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'temurin' - name: Build with Gradle uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index df48f282..d8840f11 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,10 +21,10 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'temurin' - name: Build with Gradle uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1 diff --git a/build.gradle.kts b/build.gradle.kts index 8cc4ad0e..7e8bd200 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,6 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import org.openapitools.generator.gradle.plugin.tasks.GenerateTask val ktor_version: String by project val kotlin_version: String by project @@ -11,15 +13,22 @@ plugins { kotlin("jvm") version "1.8.21" id("io.ktor.plugin") version "2.3.0" id("org.graalvm.buildtools.native") version "0.9.21" - id("io.gitlab.arturbosch.detekt") version "1.22.0" + id("io.gitlab.arturbosch.detekt") version "1.23.1" id("com.google.devtools.ksp") version "1.8.21-1.0.11" + id("org.springframework.boot") version "3.1.2" + kotlin("plugin.spring") version "1.8.21" + id("org.openapi.generator") version "6.6.0" // id("org.jetbrains.kotlin.plugin.serialization") version "1.8.10" } +apply { + plugin("io.spring.dependency-management") +} + group = "dev.usbharu" version = "0.0.1" application { - mainClass.set("io.ktor.server.cio.EngineMain") + mainClass.set("dev.usbharu.hideout.SpringApplicationKt") val isDevelopment: Boolean = project.ext.has("development") applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") @@ -34,6 +43,14 @@ tasks.withType>().con compilerOptions.apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_8) } +tasks.withType { + kotlinOptions { + freeCompilerArgs += "-Xjsr305=strict" + } + dependsOn("openApiGenerateServer") + mustRunAfter("openApiGenerateServer") +} + tasks.withType { manifest { attributes( @@ -46,6 +63,35 @@ tasks.clean { delete += listOf("$rootDir/src/main/resources/static") } +tasks.create("openApiGenerateServer", GenerateTask::class) { + generatorName.set("kotlin-spring") + inputSpec.set("$rootDir/src/main/resources/openapi/api.yaml") + outputDir.set("$buildDir/generated/sources/openapi") + apiPackage.set("dev.usbharu.hideout.controller.generated") + modelPackage.set("dev.usbharu.hideout.domain.model.generated") + configOptions.put("interfaceOnly", "true") + configOptions.put("useSpringBoot3", "true") + additionalProperties.put("useTags", "true") + schemaMappings.putAll( + mapOf( + "ReactionResponse" to "dev.usbharu.hideout.domain.model.hideout.dto.ReactionResponse", + "Account" to "dev.usbharu.hideout.domain.model.hideout.dto.Account", + "JwtToken" to "dev.usbharu.hideout.domain.model.hideout.dto.JwtToken", + "PostRequest" to "dev.usbharu.hideout.domain.model.hideout.form.Post", + "PostResponse" to "dev.usbharu.hideout.domain.model.hideout.dto.PostResponse", + "Reaction" to "dev.usbharu.hideout.domain.model.hideout.form.Reaction", + "RefreshToken" to "dev.usbharu.hideout.domain.model.hideout.form.RefreshToken", + "UserLogin" to "dev.usbharu.hideout.domain.model.hideout.form.UserLogin", + "UserResponse" to "dev.usbharu.hideout.domain.model.hideout.dto.UserResponse", + "UserCreate" to "dev.usbharu.hideout.domain.model.hideout.form.UserCreate", + "Visibility" to "dev.usbharu.hideout.domain.model.hideout.entity.Visibility", + ) + ) + +// importMappings.putAll(mapOf("ReactionResponse" to "ReactionResponse")) +// typeMappings.putAll(mapOf("ReactionResponse" to "ReactionResponse")) +} + repositories { mavenCentral() } @@ -53,13 +99,13 @@ repositories { kotlin { target { compilations.all { - kotlinOptions.jvmTarget = JavaVersion.VERSION_11.toString() + kotlinOptions.jvmTarget = JavaVersion.VERSION_17.toString() } } } sourceSets.main { - kotlin.srcDirs("$buildDir/generated/ksp/main") + kotlin.srcDirs("$buildDir/generated/ksp/main", "$buildDir/generated/sources/openapi/src/main/kotlin") } dependencies { @@ -90,6 +136,23 @@ dependencies { implementation("io.ktor:ktor-server-compression-jvm:2.3.0") ksp("io.insert-koin:koin-ksp-compiler:1.2.0") + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter-security") + implementation("org.springframework.boot:spring-boot-starter-oauth2-authorization-server") + implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server") + implementation("jakarta.validation:jakarta.validation-api") + implementation("jakarta.annotation:jakarta.annotation-api:2.1.0") + compileOnly("io.swagger.core.v3:swagger-annotations:2.2.6") + implementation("io.swagger.core.v3:swagger-models:2.2.6") + implementation("org.jetbrains.exposed:exposed-java-time:$exposed_version") + implementation("org.jetbrains.exposed:spring-transaction:$exposed_version") + implementation("org.springframework.data:spring-data-commons") + implementation("org.springframework.boot:spring-boot-starter-jdbc") + implementation("org.springframework.boot:spring-boot-starter-data-jdbc") + implementation("org.springframework.boot:spring-boot-starter-webflux") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") + testImplementation("org.springframework.boot:spring-boot-test-autoconfigure") + testImplementation("org.springframework.boot:spring-boot-starter-test") implementation("io.ktor:ktor-client-logging-jvm:$ktor_version") implementation("io.ktor:ktor-server-host-common-jvm:$ktor_version") @@ -97,7 +160,7 @@ dependencies { testImplementation("io.ktor:ktor-server-tests-jvm:$ktor_version") testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") - testImplementation ("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4") + testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4") implementation("io.ktor:ktor-client-core:$ktor_version") implementation("io.ktor:ktor-client-cio:$ktor_version") @@ -137,7 +200,7 @@ graalvmNative { named("main") { fallback.set(false) verbose.set(true) - agent{ + agent { enabled.set(false) } @@ -167,9 +230,17 @@ detekt { } tasks.withType().configureEach { - exclude("**/org/koin/ksp/generated/**") + exclude("**/org/koin/ksp/generated/**", "**/generated/**") } tasks.withType().configureEach { - exclude("**/org/koin/ksp/generated/**") + exclude("**/org/koin/ksp/generated/**", "**/generated/**") +} + +configurations.matching { it.name == "detekt" }.all { + resolutionStrategy.eachDependency { + if (requested.group == "org.jetbrains.kotlin") { + useVersion("1.9.0") + } + } } diff --git a/gradle.properties b/gradle.properties index 9ee92fa5..31697ea3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ logback_version=1.4.6 kotlin.code.style=official exposed_version=0.41.1 h2_version=2.1.214 -koin_version=3.3.1 +koin_version=3.4.3 org.gradle.parallel=true org.gradle.configureondemand=true org.gradle.caching=true diff --git a/src/main/kotlin/dev/usbharu/hideout/Application.kt b/src/main/kotlin/dev/usbharu/hideout/Application.kt index 6a745122..5b1530a4 100644 --- a/src/main/kotlin/dev/usbharu/hideout/Application.kt +++ b/src/main/kotlin/dev/usbharu/hideout/Application.kt @@ -39,6 +39,7 @@ import org.koin.ksp.generated.module import org.koin.ktor.ext.inject import java.util.concurrent.TimeUnit +@Deprecated("Ktor is deprecated") fun main(args: Array): Unit = io.ktor.server.cio.EngineMain.main(args) val Application.property: Application.(propertyName: String) -> String @@ -52,6 +53,7 @@ val Application.propertyOrNull: Application.(propertyName: String) -> String? } // application.conf references the main function. This annotation prevents the IDE from marking it as unused. +@Deprecated("Ktor is deprecated") @Suppress("unused", "LongMethod") fun Application.parent() { Config.configData = ConfigData( @@ -136,6 +138,7 @@ fun Application.parent() { ) } +@Deprecated("Ktor is deprecated") @Suppress("unused") fun Application.worker() { val kJob = kjob(ExposedKJob) { diff --git a/src/main/kotlin/dev/usbharu/hideout/SpringApplication.kt b/src/main/kotlin/dev/usbharu/hideout/SpringApplication.kt new file mode 100644 index 00000000..0cc32e48 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/SpringApplication.kt @@ -0,0 +1,13 @@ +package dev.usbharu.hideout + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.context.properties.ConfigurationPropertiesScan +import org.springframework.boot.runApplication + +@SpringBootApplication +@ConfigurationPropertiesScan +class SpringApplication + +fun main(args: Array) { + runApplication(*args) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/config/Config.kt b/src/main/kotlin/dev/usbharu/hideout/config/Config.kt index 8acf1113..1623ff25 100644 --- a/src/main/kotlin/dev/usbharu/hideout/config/Config.kt +++ b/src/main/kotlin/dev/usbharu/hideout/config/Config.kt @@ -3,10 +3,12 @@ package dev.usbharu.hideout.config import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +@Deprecated("Config is deprecated") object Config { var configData: ConfigData = ConfigData() } +@Deprecated("Config is deprecated") data class ConfigData( val url: String = "", val domain: String = url.substringAfter("://").substringBeforeLast(":"), @@ -14,12 +16,14 @@ data class ConfigData( val characterLimit: CharacterLimit = CharacterLimit() ) +@Deprecated("Config is deprecated") data class CharacterLimit( val general: General = General.of(), val post: Post = Post(), val account: Account = Account(), val instance: Instance = Instance() ) { + @Deprecated("Config is deprecated") data class General private constructor( val url: Int, val domain: Int, @@ -39,17 +43,20 @@ data class CharacterLimit( } } + @Deprecated("Config is deprecated") data class Post( val text: Int = 3000, val overview: Int = 3000 ) + @Deprecated("Config is deprecated") data class Account( val id: Int = 300, val name: Int = 300, val description: Int = 10000 ) + @Deprecated("Config is deprecated") data class Instance( val name: Int = 600, val description: Int = 10000 diff --git a/src/main/kotlin/dev/usbharu/hideout/config/DatabaseConfig.kt b/src/main/kotlin/dev/usbharu/hideout/config/DatabaseConfig.kt new file mode 100644 index 00000000..03064946 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/config/DatabaseConfig.kt @@ -0,0 +1,32 @@ +package dev.usbharu.hideout.config + +import org.jetbrains.exposed.sql.Database +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class DatabaseConfig { + + @Autowired + lateinit var dbConfig: DatabaseConnectConfig + + @Bean + fun database(): Database { + return Database.connect( + url = dbConfig.url, + driver = dbConfig.driver, + user = dbConfig.user, + password = dbConfig.password + ) + } +} + +@ConfigurationProperties("hideout.database") +data class DatabaseConnectConfig( + val url: String, + val driver: String, + val user: String, + val password: String +) diff --git a/src/main/kotlin/dev/usbharu/hideout/config/HttpClientConfig.kt b/src/main/kotlin/dev/usbharu/hideout/config/HttpClientConfig.kt new file mode 100644 index 00000000..85483577 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/config/HttpClientConfig.kt @@ -0,0 +1,12 @@ +package dev.usbharu.hideout.config + +import io.ktor.client.* +import io.ktor.client.engine.cio.* +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class HttpClientConfig { + @Bean + fun httpClient(): HttpClient = HttpClient(CIO) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/config/SecurityConfig.kt b/src/main/kotlin/dev/usbharu/hideout/config/SecurityConfig.kt new file mode 100644 index 00000000..75505da3 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/config/SecurityConfig.kt @@ -0,0 +1,130 @@ +package dev.usbharu.hideout.config + +import com.nimbusds.jose.jwk.JWKSet +import com.nimbusds.jose.jwk.RSAKey +import com.nimbusds.jose.jwk.source.ImmutableJWKSet +import com.nimbusds.jose.jwk.source.JWKSource +import com.nimbusds.jose.proc.SecurityContext +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +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 +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder +import org.springframework.security.crypto.password.PasswordEncoder +import org.springframework.security.oauth2.jwt.JwtDecoder +import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration +import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings +import org.springframework.security.web.SecurityFilterChain +import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint +import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher +import java.security.KeyPairGenerator +import java.security.interfaces.RSAPrivateKey +import java.security.interfaces.RSAPublicKey +import java.util.* + +@EnableWebSecurity +@Configuration +class SecurityConfig { + + @Bean + @Order(1) + fun oauth2SecurityFilterChain(http: HttpSecurity): SecurityFilterChain { + OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http) + http + .exceptionHandling { + it.defaultAuthenticationEntryPointFor( + LoginUrlAuthenticationEntryPoint("/login"), + MediaTypeRequestMatcher(MediaType.TEXT_HTML) + ) + } + .oauth2ResourceServer { + it.jwt(Customizer.withDefaults()) + } + .csrf { + it.disable() + } + return http.build() + } + + @Bean + @Order(2) + fun defaultSecurityFilterChain(http: HttpSecurity): SecurityFilterChain { + http + .authorizeHttpRequests { + it.requestMatchers( + "/inbox", + "/users/*/inbox", + "/outbox", + "/users/*/outbox" + ) + .permitAll() + } + .authorizeHttpRequests { + it.anyRequest().authenticated() + } + .formLogin(Customizer.withDefaults()) + .csrf { + it.disable() + } + return http.build() + } + + @Bean + fun passwordEncoder(): PasswordEncoder { + return BCryptPasswordEncoder() + } + + @Bean + fun genJwkSource(): JWKSource { + val keyPairGenerator = KeyPairGenerator.getInstance("RSA") + keyPairGenerator.initialize(2048) + val generateKeyPair = keyPairGenerator.generateKeyPair() + val rsaPublicKey = generateKeyPair.public as RSAPublicKey + val rsaPrivateKey = generateKeyPair.private as RSAPrivateKey + val rsaKey = RSAKey + .Builder(rsaPublicKey) + .privateKey(rsaPrivateKey) + .keyID(UUID.randomUUID().toString()) + .build() + + val jwkSet = JWKSet(rsaKey) + return ImmutableJWKSet(jwkSet) + } + + @Bean + @ConditionalOnProperty(name = ["hideout.security.jwt.generate"], havingValue = "") + fun loadJwkSource(jwkConfig: JwkConfig): JWKSource { + val rsaKey = RSAKey.Builder(jwkConfig.publicKey) + .privateKey(jwkConfig.privateKey) + .keyID(jwkConfig.keyId) + .build() + return ImmutableJWKSet(JWKSet(rsaKey)) + } + + @Bean + fun jwtDecoder(jwkSource: JWKSource): JwtDecoder { + return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource) + } + + @Bean + fun authorizationServerSettings(): AuthorizationServerSettings { + return AuthorizationServerSettings.builder() + .authorizationEndpoint("/oauth/authorize") + .tokenEndpoint("/oauth/token") + .tokenRevocationEndpoint("/oauth/revoke") + .build() + } +} + +@ConfigurationProperties("hideout.security.jwt") +@ConditionalOnProperty(name = ["hideout.security.jwt.generate"], havingValue = "") +data class JwkConfig( + val keyId: String, + val publicKey: RSAPublicKey, + val privateKey: RSAPrivateKey +) diff --git a/src/main/kotlin/dev/usbharu/hideout/config/SpringTransactionConfig.kt b/src/main/kotlin/dev/usbharu/hideout/config/SpringTransactionConfig.kt new file mode 100644 index 00000000..92caf4f1 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/config/SpringTransactionConfig.kt @@ -0,0 +1,18 @@ +package dev.usbharu.hideout.config + +import org.jetbrains.exposed.spring.SpringTransactionManager +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.transaction.PlatformTransactionManager +import org.springframework.transaction.annotation.EnableTransactionManagement +import org.springframework.transaction.annotation.TransactionManagementConfigurer +import javax.sql.DataSource + +@Configuration +@EnableTransactionManagement +class SpringTransactionConfig(val datasource: DataSource) : TransactionManagementConfigurer { + @Bean + override fun annotationDrivenTransactionManager(): PlatformTransactionManager { + return SpringTransactionManager(datasource) + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/controller/DefaultApiImpl.kt b/src/main/kotlin/dev/usbharu/hideout/controller/DefaultApiImpl.kt new file mode 100644 index 00000000..b6253251 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/controller/DefaultApiImpl.kt @@ -0,0 +1,15 @@ +package dev.usbharu.hideout.controller + +import dev.usbharu.hideout.controller.generated.DefaultApi +import dev.usbharu.hideout.domain.model.hideout.dto.JwtToken +import dev.usbharu.hideout.service.api.UserAuthApiService +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.stereotype.Controller + +@Controller +class DefaultApiImpl(private val userAuthApiService: UserAuthApiService) : DefaultApi { + override fun refreshTokenPost(): ResponseEntity { + return ResponseEntity(HttpStatus.OK) + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/controller/InboxController.kt b/src/main/kotlin/dev/usbharu/hideout/controller/InboxController.kt new file mode 100644 index 00000000..0cba64e3 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/controller/InboxController.kt @@ -0,0 +1,21 @@ +package dev.usbharu.hideout.controller + +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestMethod +import org.springframework.web.bind.annotation.RestController + +@RestController +interface InboxController { + @RequestMapping( + "/inbox", + "/users/{username}/inbox", + produces = ["application/activity+json", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""], + method = [RequestMethod.GET, RequestMethod.POST] + ) + suspend fun inbox(@RequestBody string: String): ResponseEntity { + return ResponseEntity(HttpStatus.ACCEPTED) + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/controller/InboxControllerImpl.kt b/src/main/kotlin/dev/usbharu/hideout/controller/InboxControllerImpl.kt new file mode 100644 index 00000000..fb47a3f0 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/controller/InboxControllerImpl.kt @@ -0,0 +1,16 @@ +package dev.usbharu.hideout.controller + +import dev.usbharu.hideout.service.ap.APService +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RestController + +@RestController +class InboxControllerImpl(private val apService: APService) : InboxController { + override suspend fun inbox(@RequestBody string: String): ResponseEntity { + val parseActivity = apService.parseActivity(string) + apService.processActivity(string, parseActivity) + return ResponseEntity(HttpStatus.ACCEPTED) + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/controller/OutboxController.kt b/src/main/kotlin/dev/usbharu/hideout/controller/OutboxController.kt new file mode 100644 index 00000000..ee003682 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/controller/OutboxController.kt @@ -0,0 +1,16 @@ +package dev.usbharu.hideout.controller + +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestMethod +import org.springframework.web.bind.annotation.RestController + +@RestController +interface OutboxController { + @RequestMapping("/outbox", "/users/{username}/outbox", method = [RequestMethod.POST, RequestMethod.GET]) + fun outbox(@RequestBody string: String): ResponseEntity { + return ResponseEntity(HttpStatus.ACCEPTED) + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/controller/OutboxControllerImpl.kt b/src/main/kotlin/dev/usbharu/hideout/controller/OutboxControllerImpl.kt new file mode 100644 index 00000000..04dc227f --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/controller/OutboxControllerImpl.kt @@ -0,0 +1,13 @@ +package dev.usbharu.hideout.controller + +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RestController + +@RestController +class OutboxControllerImpl : OutboxController { + override fun outbox(@RequestBody string: String): ResponseEntity { + return ResponseEntity(HttpStatus.NOT_IMPLEMENTED) + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/controller/UserAPController.kt b/src/main/kotlin/dev/usbharu/hideout/controller/UserAPController.kt new file mode 100644 index 00000000..5390fff2 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/controller/UserAPController.kt @@ -0,0 +1,13 @@ +package dev.usbharu.hideout.controller + +import dev.usbharu.hideout.domain.model.ap.Person +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RestController + +@RestController +interface UserAPController { + @GetMapping("/users/{username}") + suspend fun userAp(@PathVariable("username") username: String): ResponseEntity +} diff --git a/src/main/kotlin/dev/usbharu/hideout/controller/UserAPControllerImpl.kt b/src/main/kotlin/dev/usbharu/hideout/controller/UserAPControllerImpl.kt new file mode 100644 index 00000000..90fb6155 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/controller/UserAPControllerImpl.kt @@ -0,0 +1,15 @@ +package dev.usbharu.hideout.controller + +import dev.usbharu.hideout.domain.model.ap.Person +import dev.usbharu.hideout.service.ap.APUserService +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.RestController + +@RestController +class UserAPControllerImpl(private val apUserService: APUserService) : UserAPController { + override suspend fun userAp(username: String): ResponseEntity { + val person = apUserService.getPersonByName(username) + return ResponseEntity(person, HttpStatus.OK) + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/Compression.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/Compression.kt index e13fa4c9..d1387d28 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/Compression.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/Compression.kt @@ -4,6 +4,7 @@ import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.plugins.compression.* +@Deprecated("Ktor is deprecated") fun Application.configureCompression() { install(Compression) { gzip { diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/HTTP.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/HTTP.kt index 98e5e259..44f88bbd 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/HTTP.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/HTTP.kt @@ -4,6 +4,7 @@ import io.ktor.server.application.* import io.ktor.server.plugins.defaultheaders.* import io.ktor.server.plugins.forwardedheaders.* +@Deprecated("Ktor is deprecated") fun Application.configureHTTP() { // install(CORS) { // allowMethod(HttpMethod.Options) diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/Koin.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/Koin.kt index e7a9e249..a7b1ed16 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/Koin.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/Koin.kt @@ -5,6 +5,7 @@ import org.koin.core.module.Module import org.koin.ktor.plugin.Koin import org.koin.logger.slf4jLogger +@Deprecated("Ktor is deprecated") fun Application.configureKoin(vararg module: Module) { install(Koin) { slf4jLogger() diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/Monitoring.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/Monitoring.kt index 2f33df7a..a2345312 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/Monitoring.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/Monitoring.kt @@ -4,6 +4,7 @@ import io.ktor.server.application.* import io.ktor.server.plugins.callloging.* import org.slf4j.event.Level +@Deprecated("Ktor is deprecated") fun Application.configureMonitoring() { install(CallLogging) { level = Level.INFO diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt index 178127b5..7a11e3be 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt @@ -22,6 +22,7 @@ import io.ktor.server.application.* import io.ktor.server.plugins.autohead.* import io.ktor.server.routing.* +@Deprecated("Ktor is deprecated") @Suppress("LongParameterList") fun Application.configureRouting( httpSignatureVerifyService: HttpSignatureVerifyService, diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/Security.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/Security.kt index a4d7d34d..104c5844 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/Security.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/Security.kt @@ -13,6 +13,7 @@ import io.ktor.server.routing.* const val TOKEN_AUTH = "jwt-auth" +@Deprecated("Ktor is deprecated") @Suppress("MagicNumber") fun Application.configureSecurity( jwkProvider: JwkProvider, diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/Serialization.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/Serialization.kt index 09edfe72..1e81e2e7 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/Serialization.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/Serialization.kt @@ -8,6 +8,7 @@ import io.ktor.serialization.jackson.* import io.ktor.server.application.* import io.ktor.server.plugins.contentnegotiation.* +@Deprecated("Ktor is deprecated") fun Application.configureSerialization() { install(ContentNegotiation) { jackson { diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/StaticRouting.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/StaticRouting.kt index 58888b99..c4cc37d6 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/StaticRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/StaticRouting.kt @@ -6,6 +6,7 @@ import io.ktor.server.http.content.* import io.ktor.server.response.* import io.ktor.server.routing.* +@Deprecated("Ktor is deprecated") fun Application.configureStaticRouting() { routing { get("/") { diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/StatusPages.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/StatusPages.kt index e0305692..9a9ea5ac 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/StatusPages.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/StatusPages.kt @@ -6,6 +6,7 @@ import io.ktor.server.application.* import io.ktor.server.plugins.statuspages.* import io.ktor.server.response.* +@Deprecated("Ktor is deprecated") fun Application.configureStatusPages() { install(StatusPages) { exception { call, cause -> diff --git a/src/main/kotlin/dev/usbharu/hideout/query/FollowerQueryService.kt b/src/main/kotlin/dev/usbharu/hideout/query/FollowerQueryService.kt index 4922b268..4cc73ef1 100644 --- a/src/main/kotlin/dev/usbharu/hideout/query/FollowerQueryService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/query/FollowerQueryService.kt @@ -1,7 +1,9 @@ package dev.usbharu.hideout.query import dev.usbharu.hideout.domain.model.hideout.entity.User +import org.springframework.stereotype.Repository +@Repository interface FollowerQueryService { suspend fun findFollowersById(id: Long): List suspend fun findFollowersByNameAndDomain(name: String, domain: String): List diff --git a/src/main/kotlin/dev/usbharu/hideout/query/FollowerQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/query/FollowerQueryServiceImpl.kt index 3a938620..831c715c 100644 --- a/src/main/kotlin/dev/usbharu/hideout/query/FollowerQueryServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/query/FollowerQueryServiceImpl.kt @@ -6,9 +6,11 @@ import dev.usbharu.hideout.repository.UsersFollowers import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.koin.core.annotation.Single +import org.springframework.stereotype.Repository import java.time.Instant @Single +@Repository class FollowerQueryServiceImpl : FollowerQueryService { override suspend fun findFollowersById(id: Long): List { val followers = Users.alias("FOLLOWERS") diff --git a/src/main/kotlin/dev/usbharu/hideout/query/JwtRefreshTokenQueryService.kt b/src/main/kotlin/dev/usbharu/hideout/query/JwtRefreshTokenQueryService.kt index 9ce1ad57..44c5675c 100644 --- a/src/main/kotlin/dev/usbharu/hideout/query/JwtRefreshTokenQueryService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/query/JwtRefreshTokenQueryService.kt @@ -1,7 +1,9 @@ package dev.usbharu.hideout.query import dev.usbharu.hideout.domain.model.hideout.entity.JwtRefreshToken +import org.springframework.stereotype.Repository +@Repository interface JwtRefreshTokenQueryService { suspend fun findById(id: Long): JwtRefreshToken suspend fun findByToken(token: String): JwtRefreshToken diff --git a/src/main/kotlin/dev/usbharu/hideout/query/JwtRefreshTokenQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/query/JwtRefreshTokenQueryServiceImpl.kt index 7b6affff..ce217c42 100644 --- a/src/main/kotlin/dev/usbharu/hideout/query/JwtRefreshTokenQueryServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/query/JwtRefreshTokenQueryServiceImpl.kt @@ -10,8 +10,10 @@ import org.jetbrains.exposed.sql.deleteAll import org.jetbrains.exposed.sql.deleteWhere import org.jetbrains.exposed.sql.select import org.koin.core.annotation.Single +import org.springframework.stereotype.Repository @Single +@Repository class JwtRefreshTokenQueryServiceImpl : JwtRefreshTokenQueryService { override suspend fun findById(id: Long): JwtRefreshToken = JwtRefreshTokens.select { JwtRefreshTokens.id.eq(id) } diff --git a/src/main/kotlin/dev/usbharu/hideout/query/PostQueryService.kt b/src/main/kotlin/dev/usbharu/hideout/query/PostQueryService.kt index 8ad102ab..2a67b1fe 100644 --- a/src/main/kotlin/dev/usbharu/hideout/query/PostQueryService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/query/PostQueryService.kt @@ -1,7 +1,9 @@ package dev.usbharu.hideout.query import dev.usbharu.hideout.domain.model.hideout.entity.Post +import org.springframework.stereotype.Repository +@Repository interface PostQueryService { suspend fun findById(id: Long): Post suspend fun findByUrl(url: String): Post diff --git a/src/main/kotlin/dev/usbharu/hideout/query/PostQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/query/PostQueryServiceImpl.kt index 4f98710b..a3293b06 100644 --- a/src/main/kotlin/dev/usbharu/hideout/query/PostQueryServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/query/PostQueryServiceImpl.kt @@ -7,8 +7,10 @@ import dev.usbharu.hideout.repository.toPost import dev.usbharu.hideout.util.singleOr import org.jetbrains.exposed.sql.select import org.koin.core.annotation.Single +import org.springframework.stereotype.Repository @Single +@Repository class PostQueryServiceImpl : PostQueryService { override suspend fun findById(id: Long): Post = Posts.select { Posts.id eq id } diff --git a/src/main/kotlin/dev/usbharu/hideout/query/PostResponseQueryService.kt b/src/main/kotlin/dev/usbharu/hideout/query/PostResponseQueryService.kt index 65441189..9c5263b8 100644 --- a/src/main/kotlin/dev/usbharu/hideout/query/PostResponseQueryService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/query/PostResponseQueryService.kt @@ -1,8 +1,10 @@ package dev.usbharu.hideout.query import dev.usbharu.hideout.domain.model.hideout.dto.PostResponse +import org.springframework.stereotype.Repository @Suppress("LongParameterList") +@Repository interface PostResponseQueryService { suspend fun findById(id: Long, userId: Long?): PostResponse suspend fun findAll( diff --git a/src/main/kotlin/dev/usbharu/hideout/query/PostResponseQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/query/PostResponseQueryServiceImpl.kt index a8ef3930..ce350fc1 100644 --- a/src/main/kotlin/dev/usbharu/hideout/query/PostResponseQueryServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/query/PostResponseQueryServiceImpl.kt @@ -12,8 +12,10 @@ import org.jetbrains.exposed.sql.innerJoin import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.selectAll import org.koin.core.annotation.Single +import org.springframework.stereotype.Repository @Single +@Repository class PostResponseQueryServiceImpl : PostResponseQueryService { override suspend fun findById(id: Long, userId: Long?): PostResponse { return Posts diff --git a/src/main/kotlin/dev/usbharu/hideout/query/ReactionQueryService.kt b/src/main/kotlin/dev/usbharu/hideout/query/ReactionQueryService.kt index 72e0c316..fa81a62c 100644 --- a/src/main/kotlin/dev/usbharu/hideout/query/ReactionQueryService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/query/ReactionQueryService.kt @@ -2,7 +2,9 @@ package dev.usbharu.hideout.query import dev.usbharu.hideout.domain.model.hideout.dto.ReactionResponse import dev.usbharu.hideout.domain.model.hideout.entity.Reaction +import org.springframework.stereotype.Repository +@Repository interface ReactionQueryService { suspend fun findByPostId(postId: Long, userId: Long? = null): List diff --git a/src/main/kotlin/dev/usbharu/hideout/query/ReactionQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/query/ReactionQueryServiceImpl.kt index de41d1cb..9536ca61 100644 --- a/src/main/kotlin/dev/usbharu/hideout/query/ReactionQueryServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/query/ReactionQueryServiceImpl.kt @@ -11,8 +11,10 @@ import dev.usbharu.hideout.util.singleOr import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.koin.core.annotation.Single +import org.springframework.stereotype.Repository @Single +@Repository class ReactionQueryServiceImpl : ReactionQueryService { override suspend fun findByPostId(postId: Long, userId: Long?): List { return Reactions.select { diff --git a/src/main/kotlin/dev/usbharu/hideout/query/UserQueryService.kt b/src/main/kotlin/dev/usbharu/hideout/query/UserQueryService.kt index 6b7f7db6..09e5972a 100644 --- a/src/main/kotlin/dev/usbharu/hideout/query/UserQueryService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/query/UserQueryService.kt @@ -1,7 +1,9 @@ package dev.usbharu.hideout.query import dev.usbharu.hideout.domain.model.hideout.entity.User +import org.springframework.stereotype.Repository +@Repository interface UserQueryService { suspend fun findAll(limit: Int, offset: Long): List suspend fun findById(id: Long): User diff --git a/src/main/kotlin/dev/usbharu/hideout/query/UserQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/query/UserQueryServiceImpl.kt index 647b8d5f..fc2bc9b3 100644 --- a/src/main/kotlin/dev/usbharu/hideout/query/UserQueryServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/query/UserQueryServiceImpl.kt @@ -10,8 +10,10 @@ import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.selectAll import org.koin.core.annotation.Single import org.slf4j.LoggerFactory +import org.springframework.stereotype.Repository @Single +@Repository class UserQueryServiceImpl : UserQueryService { private val logger = LoggerFactory.getLogger(UserQueryServiceImpl::class.java) diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/JwtRefreshTokenRepository.kt b/src/main/kotlin/dev/usbharu/hideout/repository/JwtRefreshTokenRepository.kt index d6bc5638..81c6aa35 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/JwtRefreshTokenRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/JwtRefreshTokenRepository.kt @@ -1,7 +1,9 @@ package dev.usbharu.hideout.repository import dev.usbharu.hideout.domain.model.hideout.entity.JwtRefreshToken +import org.springframework.stereotype.Repository +@Repository interface JwtRefreshTokenRepository { suspend fun generateId(): Long diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/JwtRefreshTokenRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/repository/JwtRefreshTokenRepositoryImpl.kt index 0fdc79dd..c1bf8ad3 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/JwtRefreshTokenRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/JwtRefreshTokenRepositoryImpl.kt @@ -6,9 +6,11 @@ import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.transactions.transaction import org.koin.core.annotation.Single +import org.springframework.stereotype.Repository import java.time.Instant @Single +@Repository class JwtRefreshTokenRepositoryImpl( private val database: Database, private val idGenerateService: IdGenerateService diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/MetaRepository.kt b/src/main/kotlin/dev/usbharu/hideout/repository/MetaRepository.kt index 5fda5200..af4eb65f 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/MetaRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/MetaRepository.kt @@ -1,7 +1,9 @@ package dev.usbharu.hideout.repository import dev.usbharu.hideout.domain.model.hideout.entity.Meta +import org.springframework.stereotype.Repository +@Repository interface MetaRepository { suspend fun save(meta: Meta) diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/MetaRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/repository/MetaRepositoryImpl.kt index d0de63a1..543bb1fb 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/MetaRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/MetaRepositoryImpl.kt @@ -4,9 +4,11 @@ import dev.usbharu.hideout.domain.model.hideout.entity.Jwt import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.transactions.transaction import org.koin.core.annotation.Single +import org.springframework.stereotype.Repository import java.util.* @Single +@Repository class MetaRepositoryImpl(private val database: Database) : MetaRepository { init { diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/PostRepository.kt b/src/main/kotlin/dev/usbharu/hideout/repository/PostRepository.kt index 21a9bec8..8011f282 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/PostRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/PostRepository.kt @@ -1,8 +1,10 @@ package dev.usbharu.hideout.repository import dev.usbharu.hideout.domain.model.hideout.entity.Post +import org.springframework.stereotype.Repository @Suppress("LongParameterList") +@Repository interface PostRepository { suspend fun generateId(): Long suspend fun save(post: Post): Post diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/PostRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/repository/PostRepositoryImpl.kt index 79698826..08998c15 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/PostRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/PostRepositoryImpl.kt @@ -8,8 +8,10 @@ import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.transactions.transaction import org.koin.core.annotation.Single +import org.springframework.stereotype.Repository @Single +@Repository class PostRepositoryImpl(database: Database, private val idGenerateService: IdGenerateService) : PostRepository { init { diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/ReactionRepository.kt b/src/main/kotlin/dev/usbharu/hideout/repository/ReactionRepository.kt index f2aad560..d98a0c84 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/ReactionRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/ReactionRepository.kt @@ -1,7 +1,9 @@ package dev.usbharu.hideout.repository import dev.usbharu.hideout.domain.model.hideout.entity.Reaction +import org.springframework.stereotype.Repository +@Repository interface ReactionRepository { suspend fun generateId(): Long suspend fun save(reaction: Reaction): Reaction diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/ReactionRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/repository/ReactionRepositoryImpl.kt index 628a995e..00f74d01 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/ReactionRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/ReactionRepositoryImpl.kt @@ -7,8 +7,10 @@ import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.transactions.transaction import org.koin.core.annotation.Single +import org.springframework.stereotype.Repository @Single +@Repository class ReactionRepositoryImpl( private val database: Database, private val idGenerateService: IdGenerateService diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/RegisteredClientRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/repository/RegisteredClientRepositoryImpl.kt new file mode 100644 index 00000000..7199a949 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/repository/RegisteredClientRepositoryImpl.kt @@ -0,0 +1,169 @@ +package dev.usbharu.hideout.repository + +import dev.usbharu.hideout.repository.RegisteredClient.clientId +import dev.usbharu.hideout.repository.RegisteredClient.clientSettings +import dev.usbharu.hideout.repository.RegisteredClient.tokenSettings +import dev.usbharu.hideout.util.JsonUtil +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.javatime.CurrentTimestamp +import org.jetbrains.exposed.sql.javatime.timestamp +import org.jetbrains.exposed.sql.transactions.transaction +import org.springframework.security.oauth2.core.AuthorizationGrantType +import org.springframework.security.oauth2.core.ClientAuthenticationMethod +import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository +import org.springframework.security.oauth2.server.authorization.settings.ClientSettings +import org.springframework.security.oauth2.server.authorization.settings.ConfigurationSettingNames +import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat +import org.springframework.security.oauth2.server.authorization.settings.TokenSettings +import org.springframework.stereotype.Repository +import org.springframework.transaction.annotation.Transactional +import java.time.Instant +import org.springframework.security.oauth2.server.authorization.client.RegisteredClient as SpringRegisteredClient + +@Repository +class RegisteredClientRepositoryImpl(private val database: Database) : RegisteredClientRepository { + + init { + transaction(database) { + SchemaUtils.create(RegisteredClient) + SchemaUtils.createMissingTablesAndColumns(RegisteredClient) + } + } + + override fun save(registeredClient: SpringRegisteredClient?) { + requireNotNull(registeredClient) + val singleOrNull = RegisteredClient.select { RegisteredClient.id eq registeredClient.id }.singleOrNull() + if (singleOrNull == null) { + RegisteredClient.insert { + it[id] = registeredClient.id + it[clientId] = registeredClient.clientId + it[clientIdIssuedAt] = registeredClient.clientIdIssuedAt ?: Instant.now() + it[clientSecret] = registeredClient.clientSecret + it[clientSecretExpiresAt] = registeredClient.clientSecretExpiresAt + it[clientName] = registeredClient.clientName + it[clientAuthenticationMethods] = registeredClient.clientAuthenticationMethods.joinToString(",") + it[authorizationGrantTypes] = registeredClient.authorizationGrantTypes.joinToString(",") + it[redirectUris] = registeredClient.redirectUris.joinToString(",") + it[postLogoutRedirectUris] = registeredClient.postLogoutRedirectUris.joinToString(",") + it[scopes] = registeredClient.scopes.joinToString(",") + it[clientSettings] = JsonUtil.mapToJson(registeredClient.clientSettings.settings) + it[tokenSettings] = JsonUtil.mapToJson(registeredClient.tokenSettings.settings) + } + } else { + RegisteredClient.update({ RegisteredClient.id eq registeredClient.id }) { + it[clientId] = registeredClient.clientId + it[clientIdIssuedAt] = registeredClient.clientIdIssuedAt ?: Instant.now() + it[clientSecret] = registeredClient.clientSecret + it[clientSecretExpiresAt] = registeredClient.clientSecretExpiresAt + it[clientName] = registeredClient.clientName + it[clientAuthenticationMethods] = registeredClient.clientAuthenticationMethods.joinToString(",") + it[authorizationGrantTypes] = registeredClient.authorizationGrantTypes.joinToString(",") + it[redirectUris] = registeredClient.redirectUris.joinToString(",") + it[postLogoutRedirectUris] = registeredClient.postLogoutRedirectUris.joinToString(",") + it[scopes] = registeredClient.scopes.joinToString(",") + it[clientSettings] = JsonUtil.mapToJson(registeredClient.clientSettings.settings) + it[tokenSettings] = JsonUtil.mapToJson(registeredClient.tokenSettings.settings) + } + } + } + + override fun findById(id: String?): SpringRegisteredClient? { + if (id == null) { + return null + } + return RegisteredClient.select { + RegisteredClient.id eq id + }.singleOrNull()?.toRegisteredClient() + } + + @Transactional + override fun findByClientId(clientId: String?): SpringRegisteredClient? { + if (clientId == null) { + return null + } + return RegisteredClient.select { + RegisteredClient.clientId eq clientId + }.singleOrNull()?.toRegisteredClient() + } +} + +// org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql +object RegisteredClient : Table("registered_client") { + val id = varchar("id", 100) + val clientId = varchar("client_id", 100) + val clientIdIssuedAt = timestamp("client_id_issued_at").defaultExpression(CurrentTimestamp()) + val clientSecret = varchar("client_secret", 200).nullable().default(null) + val clientSecretExpiresAt = timestamp("client_secret_expires_at").nullable().default(null) + val clientName = varchar("client_name", 200) + val clientAuthenticationMethods = varchar("client_authentication_methods", 1000) + val authorizationGrantTypes = varchar("authorization_grant_types", 1000) + val redirectUris = varchar("redirect_uris", 1000).nullable().default(null) + val postLogoutRedirectUris = varchar("post_logout_redirect_uris", 1000).nullable().default(null) + val scopes = varchar("scopes", 1000) + val clientSettings = varchar("client_settings", 2000) + val tokenSettings = varchar("token_settings", 2000) + + override val primaryKey = PrimaryKey(id) +} + +fun ResultRow.toRegisteredClient(): SpringRegisteredClient { + fun resolveClientAuthenticationMethods(string: String): ClientAuthenticationMethod { + return when (string) { + ClientAuthenticationMethod.CLIENT_SECRET_BASIC.value -> ClientAuthenticationMethod.CLIENT_SECRET_BASIC + ClientAuthenticationMethod.CLIENT_SECRET_JWT.value -> ClientAuthenticationMethod.CLIENT_SECRET_JWT + ClientAuthenticationMethod.CLIENT_SECRET_POST.value -> ClientAuthenticationMethod.CLIENT_SECRET_POST + ClientAuthenticationMethod.NONE.value -> ClientAuthenticationMethod.NONE + else -> { + ClientAuthenticationMethod(string) + } + } + } + + fun resolveAuthorizationGrantType(string: String): AuthorizationGrantType { + return when (string) { + AuthorizationGrantType.AUTHORIZATION_CODE.value -> AuthorizationGrantType.AUTHORIZATION_CODE + AuthorizationGrantType.CLIENT_CREDENTIALS.value -> AuthorizationGrantType.CLIENT_CREDENTIALS + AuthorizationGrantType.REFRESH_TOKEN.value -> AuthorizationGrantType.REFRESH_TOKEN + else -> { + AuthorizationGrantType(string) + } + } + } + + val clientAuthenticationMethods = this[RegisteredClient.clientAuthenticationMethods].split(",").toSet() + val authorizationGrantTypes = this[RegisteredClient.authorizationGrantTypes].split(",").toSet() + val redirectUris = this[RegisteredClient.redirectUris]?.split(",").orEmpty().toSet() + val postLogoutRedirectUris = this[RegisteredClient.postLogoutRedirectUris]?.split(",").orEmpty().toSet() + val clientScopes = this[RegisteredClient.scopes].split(",").toSet() + + val builder = SpringRegisteredClient + .withId(this[RegisteredClient.id]) + .clientId(this[clientId]) + .clientIdIssuedAt(this[RegisteredClient.clientIdIssuedAt]) + .clientSecret(this[RegisteredClient.clientSecret]) + .clientSecretExpiresAt(this[RegisteredClient.clientSecretExpiresAt]) + .clientName(this[RegisteredClient.clientName]) + .clientAuthenticationMethods { + clientAuthenticationMethods.forEach { s -> + it.add(resolveClientAuthenticationMethods(s)) + } + } + .authorizationGrantTypes { + authorizationGrantTypes.forEach { s -> + it.add(resolveAuthorizationGrantType(s)) + } + } + .redirectUris { it.addAll(redirectUris) } + .postLogoutRedirectUris { it.addAll(postLogoutRedirectUris) } + .scopes { it.addAll(clientScopes) } + .clientSettings(ClientSettings.withSettings(JsonUtil.jsonToMap(this[clientSettings])).build()) + + val tokenSettingsMap = JsonUtil.jsonToMap(this[tokenSettings]) + val withSettings = TokenSettings.withSettings(tokenSettingsMap) + if (tokenSettingsMap.containsKey(ConfigurationSettingNames.Token.ACCESS_TOKEN_FORMAT)) { + withSettings.accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED) + } + builder.tokenSettings(withSettings.build()) + + return builder.build() +} diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/UserRepository.kt b/src/main/kotlin/dev/usbharu/hideout/repository/UserRepository.kt index 17344d24..b00d66c4 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/UserRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/UserRepository.kt @@ -1,8 +1,10 @@ package dev.usbharu.hideout.repository import dev.usbharu.hideout.domain.model.hideout.entity.User +import org.springframework.stereotype.Repository @Suppress("TooManyFunctions") +@Repository interface UserRepository { suspend fun save(user: User): User diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/UserRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/repository/UserRepositoryImpl.kt index 961a089c..c57a7ab7 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/UserRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/UserRepositoryImpl.kt @@ -8,9 +8,11 @@ import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.transactions.transaction import org.koin.core.annotation.Single +import org.springframework.stereotype.Repository import java.time.Instant @Single +@Repository class UserRepositoryImpl(private val database: Database, private val idGenerateService: IdGenerateService) : UserRepository { init { diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/RegisterRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/RegisterRouting.kt index 8628f340..232ee918 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/RegisterRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/RegisterRouting.kt @@ -8,6 +8,7 @@ import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* +@Deprecated("Ktor is deprecated") fun Application.register(userApiService: UserApiService) { routing { get("/register") { diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt index dbdcd666..e1204f39 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt @@ -11,6 +11,7 @@ import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* +@Deprecated("Ktor is deprecated") fun Routing.inbox( httpSignatureVerifyService: HttpSignatureVerifyService, apService: dev.usbharu.hideout.service.ap.APService diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/OutboxRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/OutboxRouting.kt index 3ad07137..9f6c2689 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/OutboxRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/OutboxRouting.kt @@ -5,6 +5,7 @@ import io.ktor.server.application.* import io.ktor.server.response.* import io.ktor.server.routing.* +@Deprecated("Ktor is deprecated") fun Routing.outbox() { route("/outbox") { get { diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt index 0d0dbea5..39d7a299 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt @@ -15,6 +15,7 @@ import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* +@Deprecated("Ktor is deprecated") fun Routing.usersAP( apUserService: APUserService, userQueryService: UserQueryService, @@ -50,6 +51,7 @@ fun Routing.usersAP( } } +@Deprecated("Ktor is deprecated") class ContentTypeRouteSelector(private vararg val contentType: ContentType) : RouteSelector() { override fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation { context.call.application.log.debug("Accept: ${context.call.request.accept()}") diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/api/internal/v1/Auth.kt b/src/main/kotlin/dev/usbharu/hideout/routing/api/internal/v1/Auth.kt index 610d39b4..4a70ab5b 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/api/internal/v1/Auth.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/api/internal/v1/Auth.kt @@ -11,6 +11,7 @@ import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* +@Deprecated("Ktor is deprecated") fun Route.auth(userAuthApiService: UserAuthApiService) { post("/login") { val loginUser = call.receive() diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/api/internal/v1/Posts.kt b/src/main/kotlin/dev/usbharu/hideout/routing/api/internal/v1/Posts.kt index a3c3cd23..3137bc6c 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/api/internal/v1/Posts.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/api/internal/v1/Posts.kt @@ -14,6 +14,7 @@ import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* +@Deprecated("Ktor is deprecated") @Suppress("LongMethod") fun Route.posts(postApiService: PostApiService) { route("/posts") { diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/api/internal/v1/Users.kt b/src/main/kotlin/dev/usbharu/hideout/routing/api/internal/v1/Users.kt index dd7b64f9..96f4f198 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/api/internal/v1/Users.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/api/internal/v1/Users.kt @@ -16,6 +16,7 @@ import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* +@Deprecated("Ktor is deprecated") @Suppress("LongMethod", "CognitiveComplexMethod") fun Route.users(userService: UserService, userApiService: UserApiService) { route("/users") { diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/wellknown/WebfingerRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/wellknown/WebfingerRouting.kt index 9642afd0..efa782c9 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/wellknown/WebfingerRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/wellknown/WebfingerRouting.kt @@ -11,6 +11,7 @@ import io.ktor.server.application.* import io.ktor.server.response.* import io.ktor.server.routing.* +@Deprecated("Ktor is deprecated") fun Routing.webfinger(webFingerApiService: WebFingerApiService) { route("/.well-known/webfinger") { get { diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/APAcceptService.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/APAcceptService.kt index 3af3fbcb..9c3f10ed 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/APAcceptService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/APAcceptService.kt @@ -11,12 +11,15 @@ import dev.usbharu.hideout.service.core.Transaction import dev.usbharu.hideout.service.user.UserService import io.ktor.http.* import org.koin.core.annotation.Single +import org.springframework.stereotype.Service +@Service interface APAcceptService { suspend fun receiveAccept(accept: Accept): ActivityPubResponse } @Single +@Service class APAcceptServiceImpl( private val userService: UserService, private val userQueryService: UserQueryService, diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/APCreateService.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/APCreateService.kt index b3e84557..51ea4eee 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/APCreateService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/APCreateService.kt @@ -8,12 +8,15 @@ import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException import dev.usbharu.hideout.service.core.Transaction import io.ktor.http.* import org.koin.core.annotation.Single +import org.springframework.stereotype.Service +@Service interface APCreateService { suspend fun receiveCreate(create: Create): ActivityPubResponse } @Single +@Service class APCreateServiceImpl( private val apNoteService: APNoteService, private val transaction: Transaction diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/APLikeService.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/APLikeService.kt index 6bf2132e..c98b8cfc 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/APLikeService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/APLikeService.kt @@ -9,12 +9,15 @@ import dev.usbharu.hideout.service.core.Transaction import dev.usbharu.hideout.service.reaction.ReactionService import io.ktor.http.* import org.koin.core.annotation.Single +import org.springframework.stereotype.Service +@Service interface APLikeService { suspend fun receiveLike(like: Like): ActivityPubResponse } @Single +@Service class APLikeServiceImpl( private val reactionService: ReactionService, private val apUserService: APUserService, diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt index 9e1875b6..b706c4fa 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt @@ -21,8 +21,10 @@ import io.ktor.client.statement.* import kjob.core.job.JobProps import org.koin.core.annotation.Single import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service import java.time.Instant +@Service interface APNoteService { suspend fun createNote(post: Post) @@ -33,6 +35,7 @@ interface APNoteService { } @Single +@Service class APNoteServiceImpl( private val httpClient: HttpClient, private val jobQueueParentService: JobQueueParentService, diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/APReactionService.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/APReactionService.kt index ba7ff79c..78698512 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/APReactionService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/APReactionService.kt @@ -15,8 +15,10 @@ import dev.usbharu.hideout.service.job.JobQueueParentService import io.ktor.client.* import kjob.core.job.JobProps import org.koin.core.annotation.Single +import org.springframework.stereotype.Service import java.time.Instant +@Service interface APReactionService { suspend fun reaction(like: Reaction) suspend fun removeReaction(like: Reaction) @@ -25,6 +27,7 @@ interface APReactionService { } @Single +@Service class APReactionServiceImpl( private val jobQueueParentService: JobQueueParentService, private val httpClient: HttpClient, diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/APReceiveFollowService.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/APReceiveFollowService.kt index 9156abfd..602a8740 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/APReceiveFollowService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/APReceiveFollowService.kt @@ -16,13 +16,16 @@ import io.ktor.client.* import io.ktor.http.* import kjob.core.job.JobProps import org.koin.core.annotation.Single +import org.springframework.stereotype.Service +@Service interface APReceiveFollowService { suspend fun receiveFollow(follow: Follow): ActivityPubResponse suspend fun receiveFollowJob(props: JobProps) } @Single +@Service class APReceiveFollowServiceImpl( private val jobQueueParentService: JobQueueParentService, private val apUserService: APUserService, diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/APSendFollowService.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/APSendFollowService.kt index 58ae74f5..ad67b955 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/APSendFollowService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/APSendFollowService.kt @@ -5,12 +5,15 @@ import dev.usbharu.hideout.domain.model.hideout.dto.SendFollowDto import dev.usbharu.hideout.plugins.postAp import io.ktor.client.* import org.koin.core.annotation.Single +import org.springframework.stereotype.Service +@Service interface APSendFollowService { suspend fun sendFollow(sendFollowDto: SendFollowDto) } @Single +@Service class APSendFollowServiceImpl(private val httpClient: HttpClient) : APSendFollowService { override suspend fun sendFollow(sendFollowDto: SendFollowDto) { val follow = Follow( diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/APService.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/APService.kt index baf1b023..4c93439d 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/APService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/APService.kt @@ -12,7 +12,9 @@ import kjob.core.job.JobProps import org.koin.core.annotation.Single import org.slf4j.Logger import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service +@Service interface APService { fun parseActivity(json: String): ActivityType @@ -173,6 +175,7 @@ enum class ExtendedVocabulary { } @Single +@Service class APServiceImpl( private val apReceiveFollowService: APReceiveFollowService, private val apNoteService: APNoteService, diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/APUndoService.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/APUndoService.kt index 8ae5ab13..03bb22f4 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/APUndoService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/APUndoService.kt @@ -9,12 +9,15 @@ import dev.usbharu.hideout.service.core.Transaction import dev.usbharu.hideout.service.user.UserService import io.ktor.http.* import org.koin.core.annotation.Single +import org.springframework.stereotype.Service +@Service interface APUndoService { suspend fun receiveUndo(undo: Undo): ActivityPubResponse } @Single +@Service @Suppress("UnsafeCallOnNullableType") class APUndoServiceImpl( private val userService: UserService, diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/APUserService.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/APUserService.kt index 2f095a6a..6e6e11bc 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/APUserService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/APUserService.kt @@ -19,7 +19,9 @@ import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* import org.koin.core.annotation.Single +import org.springframework.stereotype.Service +@Service interface APUserService { suspend fun getPersonByName(name: String): Person @@ -36,6 +38,7 @@ interface APUserService { } @Single +@Service class APUserServiceImpl( private val userService: UserService, private val httpClient: HttpClient, diff --git a/src/main/kotlin/dev/usbharu/hideout/service/api/PostApiService.kt b/src/main/kotlin/dev/usbharu/hideout/service/api/PostApiService.kt index 8edee9a7..71a904f0 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/api/PostApiService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/api/PostApiService.kt @@ -13,9 +13,11 @@ import dev.usbharu.hideout.service.post.PostService import dev.usbharu.hideout.service.reaction.ReactionService import dev.usbharu.hideout.util.AcctUtil import org.koin.core.annotation.Single +import org.springframework.stereotype.Service import java.time.Instant @Suppress("LongParameterList") +@Service interface PostApiService { suspend fun createPost(postForm: dev.usbharu.hideout.domain.model.hideout.form.Post, userId: Long): PostResponse suspend fun getById(id: Long, userId: Long?): PostResponse @@ -44,6 +46,7 @@ interface PostApiService { } @Single +@Service class PostApiServiceImpl( private val postService: PostService, private val userRepository: UserRepository, diff --git a/src/main/kotlin/dev/usbharu/hideout/service/api/UserApiService.kt b/src/main/kotlin/dev/usbharu/hideout/service/api/UserApiService.kt index 8fed3222..935cad11 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/api/UserApiService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/api/UserApiService.kt @@ -10,9 +10,11 @@ import dev.usbharu.hideout.query.UserQueryService import dev.usbharu.hideout.service.core.Transaction import dev.usbharu.hideout.service.user.UserService import org.koin.core.annotation.Single +import org.springframework.stereotype.Service import kotlin.math.min @Suppress("TooManyFunctions") +@Service interface UserApiService { suspend fun findAll(limit: Int? = 100, offset: Long = 0): List @@ -37,6 +39,7 @@ interface UserApiService { } @Single +@Service class UserApiServiceImpl( private val userQueryService: UserQueryService, private val followerQueryService: FollowerQueryService, diff --git a/src/main/kotlin/dev/usbharu/hideout/service/api/UserAuthApiService.kt b/src/main/kotlin/dev/usbharu/hideout/service/api/UserAuthApiService.kt index e576f8ef..fd8e64b3 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/api/UserAuthApiService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/api/UserAuthApiService.kt @@ -9,13 +9,16 @@ import dev.usbharu.hideout.service.auth.JwtService import dev.usbharu.hideout.service.core.Transaction import dev.usbharu.hideout.service.user.UserAuthServiceImpl import org.koin.core.annotation.Single +import org.springframework.stereotype.Service +@Service interface UserAuthApiService { suspend fun login(username: String, password: String): JwtToken suspend fun refreshToken(refreshToken: RefreshToken): JwtToken } @Single +@Service class UserAuthApiServiceImpl( private val userAuthService: UserAuthServiceImpl, private val userQueryService: UserQueryService, diff --git a/src/main/kotlin/dev/usbharu/hideout/service/api/WebFingerApiService.kt b/src/main/kotlin/dev/usbharu/hideout/service/api/WebFingerApiService.kt index 5311723c..f2ff9f81 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/api/WebFingerApiService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/api/WebFingerApiService.kt @@ -4,12 +4,15 @@ import dev.usbharu.hideout.domain.model.hideout.entity.User import dev.usbharu.hideout.query.UserQueryService import dev.usbharu.hideout.service.core.Transaction import org.koin.core.annotation.Single +import org.springframework.stereotype.Service +@Service interface WebFingerApiService { suspend fun findByNameAndDomain(name: String, domain: String): User } @Single +@Service class WebFingerApiServiceImpl(private val transaction: Transaction, private val userQueryService: UserQueryService) : WebFingerApiService { override suspend fun findByNameAndDomain(name: String, domain: String): User { diff --git a/src/main/kotlin/dev/usbharu/hideout/service/auth/ExposedOAuth2AuthorizationConsentService.kt b/src/main/kotlin/dev/usbharu/hideout/service/auth/ExposedOAuth2AuthorizationConsentService.kt new file mode 100644 index 00000000..aff981c3 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/auth/ExposedOAuth2AuthorizationConsentService.kt @@ -0,0 +1,72 @@ +package dev.usbharu.hideout.service.auth + +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService +import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository +import org.springframework.stereotype.Service +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent as AuthorizationConsent + +@Service +class ExposedOAuth2AuthorizationConsentService(private val registeredClientRepository: RegisteredClientRepository) : + OAuth2AuthorizationConsentService { + override fun save(authorizationConsent: AuthorizationConsent?) { + requireNotNull(authorizationConsent) + val singleOrNull = + OAuth2AuthorizationConsent.select { + OAuth2AuthorizationConsent.registeredClientId + .eq(authorizationConsent.registeredClientId) + .and(OAuth2AuthorizationConsent.principalName.eq(authorizationConsent.principalName)) + } + .singleOrNull() + if (singleOrNull == null) { + OAuth2AuthorizationConsent.insert { + it[registeredClientId] = authorizationConsent.registeredClientId + it[principalName] = authorizationConsent.principalName + it[authorities] = authorizationConsent.authorities.joinToString(",") + } + } + } + + override fun remove(authorizationConsent: AuthorizationConsent?) { + if (authorizationConsent == null) { + return + } + OAuth2AuthorizationConsent.deleteWhere { + registeredClientId eq authorizationConsent.registeredClientId and (principalName eq principalName) + } + } + + override fun findById(registeredClientId: String?, principalName: String?): AuthorizationConsent? { + requireNotNull(registeredClientId) + requireNotNull(principalName) + + return OAuth2AuthorizationConsent.select { + (OAuth2AuthorizationConsent.registeredClientId eq registeredClientId) + .and(OAuth2AuthorizationConsent.principalName eq principalName) + } + .singleOrNull()?.toAuthorizationConsent() + } + + fun ResultRow.toAuthorizationConsent(): AuthorizationConsent { + val registeredClientId = this[OAuth2AuthorizationConsent.registeredClientId] + registeredClientRepository.findById(registeredClientId) + + val principalName = this[OAuth2AuthorizationConsent.principalName] + val builder = AuthorizationConsent.withId(registeredClientId, principalName) + + this[OAuth2AuthorizationConsent.authorities].split(",").forEach { + builder.authority(SimpleGrantedAuthority(it)) + } + + return builder.build() + } +} + +object OAuth2AuthorizationConsent : Table("oauth2_authorization_consent") { + val registeredClientId = varchar("registered_client_id", 100) + val principalName = varchar("principal_name", 200) + val authorities = varchar("authorities", 1000) + override val primaryKey = PrimaryKey(registeredClientId, principalName) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/auth/ExposedOAuth2AuthorizationService.kt b/src/main/kotlin/dev/usbharu/hideout/service/auth/ExposedOAuth2AuthorizationService.kt new file mode 100644 index 00000000..9e25e117 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/auth/ExposedOAuth2AuthorizationService.kt @@ -0,0 +1,331 @@ +package dev.usbharu.hideout.service.auth + +import dev.usbharu.hideout.util.JsonUtil +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.javatime.timestamp +import org.springframework.security.oauth2.core.* +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames +import org.springframework.security.oauth2.core.oidc.OidcIdToken +import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames +import org.springframework.security.oauth2.server.authorization.OAuth2Authorization +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService +import org.springframework.security.oauth2.server.authorization.OAuth2TokenType +import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository +import org.springframework.stereotype.Service + +@Service +class ExposedOAuth2AuthorizationService(private val registeredClientRepository: RegisteredClientRepository) : + OAuth2AuthorizationService { + override fun save(authorization: OAuth2Authorization?) { + requireNotNull(authorization) + val singleOrNull = Authorization.select { Authorization.id eq authorization.id }.singleOrNull() + if (singleOrNull == null) { + val authorizationCodeToken = authorization.getToken(OAuth2AuthorizationCode::class.java) + val accessToken = authorization.getToken(OAuth2AccessToken::class.java) + val refreshToken = authorization.getToken(OAuth2RefreshToken::class.java) + val oidcIdToken = authorization.getToken(OidcIdToken::class.java) + val userCode = authorization.getToken(OAuth2UserCode::class.java) + val deviceCode = authorization.getToken(OAuth2DeviceCode::class.java) + Authorization.insert { + it[id] = authorization.id + it[registeredClientId] = authorization.registeredClientId + it[principalName] = authorization.principalName + it[authorizationGrantType] = authorization.authorizationGrantType.value + it[authorizedScopes] = authorization.authorizedScopes.joinToString(",").takeIf { it.isEmpty() } + it[attributes] = JsonUtil.mapToJson(authorization.attributes) + it[state] = authorization.getAttribute(OAuth2ParameterNames.STATE) + it[authorizationCodeValue] = authorizationCodeToken?.token?.tokenValue + it[authorizationCodeIssuedAt] = authorizationCodeToken?.token?.issuedAt + it[authorizationCodeExpiresAt] = authorizationCodeToken?.token?.expiresAt + it[authorizationCodeMetadata] = authorizationCodeToken?.metadata?.let { it1 -> JsonUtil.mapToJson(it1) } + it[accessTokenValue] = accessToken?.token?.tokenValue + it[accessTokenIssuedAt] = accessToken?.token?.issuedAt + it[accessTokenExpiresAt] = accessToken?.token?.expiresAt + it[accessTokenMetadata] = accessToken?.metadata?.let { it1 -> JsonUtil.mapToJson(it1) } + it[accessTokenType] = accessToken?.token?.tokenType?.value + it[accessTokenScopes] = accessToken?.run { token.scopes.joinToString(",").takeIf { it.isEmpty() } } + it[refreshTokenValue] = refreshToken?.token?.tokenValue + it[refreshTokenIssuedAt] = refreshToken?.token?.issuedAt + it[refreshTokenExpiresAt] = refreshToken?.token?.expiresAt + it[refreshTokenMetadata] = refreshToken?.metadata?.let { it1 -> JsonUtil.mapToJson(it1) } + it[oidcIdTokenValue] = oidcIdToken?.token?.tokenValue + it[oidcIdTokenIssuedAt] = oidcIdToken?.token?.issuedAt + it[oidcIdTokenExpiresAt] = oidcIdToken?.token?.expiresAt + it[oidcIdTokenMetadata] = oidcIdToken?.metadata?.let { it1 -> JsonUtil.mapToJson(it1) } + it[userCodeValue] = userCode?.token?.tokenValue + it[userCodeIssuedAt] = userCode?.token?.issuedAt + it[userCodeExpiresAt] = userCode?.token?.expiresAt + it[userCodeMetadata] = userCode?.metadata?.let { it1 -> JsonUtil.mapToJson(it1) } + it[deviceCodeValue] = deviceCode?.token?.tokenValue + it[deviceCodeIssuedAt] = deviceCode?.token?.issuedAt + it[deviceCodeExpiresAt] = deviceCode?.token?.expiresAt + it[deviceCodeMetadata] = deviceCode?.metadata?.let { it1 -> JsonUtil.mapToJson(it1) } + } + } else { + val authorizationCodeToken = authorization.getToken(OAuth2AuthorizationCode::class.java) + val accessToken = authorization.getToken(OAuth2AccessToken::class.java) + val refreshToken = authorization.getToken(OAuth2RefreshToken::class.java) + val oidcIdToken = authorization.getToken(OidcIdToken::class.java) + val userCode = authorization.getToken(OAuth2UserCode::class.java) + val deviceCode = authorization.getToken(OAuth2DeviceCode::class.java) + Authorization.update({ Authorization.id eq authorization.id }) { + it[registeredClientId] = authorization.registeredClientId + it[principalName] = authorization.principalName + it[authorizationGrantType] = authorization.authorizationGrantType.value + it[authorizedScopes] = authorization.authorizedScopes.joinToString(",").takeIf { it.isEmpty() } + it[attributes] = JsonUtil.mapToJson(authorization.attributes) + it[state] = authorization.getAttribute(OAuth2ParameterNames.STATE) + it[authorizationCodeValue] = authorizationCodeToken?.token?.tokenValue + it[authorizationCodeIssuedAt] = authorizationCodeToken?.token?.issuedAt + it[authorizationCodeExpiresAt] = authorizationCodeToken?.token?.expiresAt + it[authorizationCodeMetadata] = authorizationCodeToken?.metadata?.let { it1 -> JsonUtil.mapToJson(it1) } + it[accessTokenValue] = accessToken?.token?.tokenValue + it[accessTokenIssuedAt] = accessToken?.token?.issuedAt + it[accessTokenExpiresAt] = accessToken?.token?.expiresAt + it[accessTokenMetadata] = accessToken?.metadata?.let { it1 -> JsonUtil.mapToJson(it1) } + it[accessTokenType] = accessToken?.token?.tokenType?.value + it[accessTokenScopes] = accessToken?.token?.scopes?.joinToString(",")?.takeIf { it.isEmpty() } + it[refreshTokenValue] = refreshToken?.token?.tokenValue + it[refreshTokenIssuedAt] = refreshToken?.token?.issuedAt + it[refreshTokenExpiresAt] = refreshToken?.token?.expiresAt + it[refreshTokenMetadata] = refreshToken?.metadata?.let { it1 -> JsonUtil.mapToJson(it1) } + it[oidcIdTokenValue] = oidcIdToken?.token?.tokenValue + it[oidcIdTokenIssuedAt] = oidcIdToken?.token?.issuedAt + it[oidcIdTokenExpiresAt] = oidcIdToken?.token?.expiresAt + it[oidcIdTokenMetadata] = oidcIdToken?.metadata?.let { it1 -> JsonUtil.mapToJson(it1) } + it[userCodeValue] = userCode?.token?.tokenValue + it[userCodeIssuedAt] = userCode?.token?.issuedAt + it[userCodeExpiresAt] = userCode?.token?.expiresAt + it[userCodeMetadata] = userCode?.metadata?.let { it1 -> JsonUtil.mapToJson(it1) } + it[deviceCodeValue] = deviceCode?.token?.tokenValue + it[deviceCodeIssuedAt] = deviceCode?.token?.issuedAt + it[deviceCodeExpiresAt] = deviceCode?.token?.expiresAt + it[deviceCodeMetadata] = deviceCode?.metadata?.let { it1 -> JsonUtil.mapToJson(it1) } + } + } + } + + override fun remove(authorization: OAuth2Authorization?) { + if (authorization == null) { + return + } + Authorization.deleteWhere { Authorization.id eq authorization.id } + } + + override fun findById(id: String?): OAuth2Authorization? { + if (id == null) { + return null + } + return Authorization.select { Authorization.id eq id }.singleOrNull()?.toAuthorization() + } + + override fun findByToken(token: String?, tokenType: OAuth2TokenType?): OAuth2Authorization? { + requireNotNull(token) + return when (tokenType?.value) { + null -> { + Authorization.select { + Authorization.authorizationCodeValue eq token + }.orWhere { + Authorization.accessTokenValue eq token + }.orWhere { + Authorization.oidcIdTokenValue eq token + }.orWhere { + Authorization.refreshTokenValue eq token + }.orWhere { + Authorization.userCodeValue eq token + }.orWhere { + Authorization.deviceCodeValue eq token + } + } + + OAuth2ParameterNames.STATE -> { + Authorization.select { Authorization.state eq token } + } + + OAuth2ParameterNames.CODE -> { + Authorization.select { Authorization.authorizationCodeValue eq token } + } + + OAuth2ParameterNames.ACCESS_TOKEN -> { + Authorization.select { Authorization.accessTokenValue eq token } + } + + OidcParameterNames.ID_TOKEN -> { + Authorization.select { Authorization.oidcIdTokenValue eq token } + } + + OAuth2ParameterNames.REFRESH_TOKEN -> { + Authorization.select { Authorization.refreshTokenValue eq token } + } + + OAuth2ParameterNames.USER_CODE -> { + Authorization.select { Authorization.userCodeValue eq token } + } + + OAuth2ParameterNames.DEVICE_CODE -> { + Authorization.select { Authorization.deviceCodeValue eq token } + } + + else -> { + null + } + }?.singleOrNull()?.toAuthorization() + } + + fun ResultRow.toAuthorization(): OAuth2Authorization { + val registeredClientId = this[Authorization.registeredClientId] + + val registeredClient = registeredClientRepository.findById(registeredClientId) + + val builder = OAuth2Authorization.withRegisteredClient(registeredClient) + val id = this[Authorization.id] + val principalName = this[Authorization.principalName] + val authorizationGrantType = this[Authorization.authorizationGrantType] + val authorizedScopes = this[Authorization.authorizedScopes]?.split(",").orEmpty().toSet() + val attributes = this[Authorization.attributes]?.let { JsonUtil.jsonToMap(it) }.orEmpty() + + builder.id(id).principalName(principalName) + .authorizationGrantType(AuthorizationGrantType(authorizationGrantType)).authorizedScopes(authorizedScopes) + .attributes { it.putAll(attributes) } + + val state = this[Authorization.state].orEmpty() + if (state.isNotBlank()) { + builder.attribute(OAuth2ParameterNames.STATE, state) + } + + val authorizationCodeValue = this[Authorization.authorizationCodeValue] + if (authorizationCodeValue.isNullOrBlank()) { + val authorizationCodeIssuedAt = this[Authorization.authorizationCodeIssuedAt] + val authorizationCodeExpiresAt = this[Authorization.authorizationCodeExpiresAt] + val authorizationCodeMetadata = this[Authorization.authorizationCodeMetadata]?.let { + JsonUtil.jsonToMap( + it + ) + }.orEmpty() + val oAuth2AuthorizationCode = + OAuth2AuthorizationCode(authorizationCodeValue, authorizationCodeIssuedAt, authorizationCodeExpiresAt) + builder.token(oAuth2AuthorizationCode) { + it.putAll(authorizationCodeMetadata) + } + } + + val accessTokenValue = this[Authorization.accessTokenValue].orEmpty() + if (accessTokenValue.isNotBlank()) { + val accessTokenIssuedAt = this[Authorization.accessTokenIssuedAt] + val accessTokenExpiresAt = this[Authorization.accessTokenExpiresAt] + val accessTokenMetadata = + this[Authorization.accessTokenMetadata]?.let { JsonUtil.jsonToMap(it) }.orEmpty() + val accessTokenType = + if (this[Authorization.accessTokenType].equals(OAuth2AccessToken.TokenType.BEARER.value, true)) { + OAuth2AccessToken.TokenType.BEARER + } else { + null + } + + val accessTokenScope = this[Authorization.accessTokenScopes]?.split(",").orEmpty().toSet() + + val oAuth2AccessToken = OAuth2AccessToken( + accessTokenType, + accessTokenValue, + accessTokenIssuedAt, + accessTokenExpiresAt, + accessTokenScope + ) + + builder.token(oAuth2AccessToken) { it.putAll(accessTokenMetadata) } + } + + val oidcIdTokenValue = this[Authorization.oidcIdTokenValue].orEmpty() + if (oidcIdTokenValue.isNotBlank()) { + val oidcTokenIssuedAt = this[Authorization.oidcIdTokenIssuedAt] + val oidcTokenExpiresAt = this[Authorization.oidcIdTokenExpiresAt] + val oidcTokenMetadata = + this[Authorization.oidcIdTokenMetadata]?.let { JsonUtil.jsonToMap(it) }.orEmpty() + + val oidcIdToken = OidcIdToken( + oidcIdTokenValue, + oidcTokenIssuedAt, + oidcTokenExpiresAt, + oidcTokenMetadata.getValue(OAuth2Authorization.Token.CLAIMS_METADATA_NAME) as MutableMap? + ) + + builder.token(oidcIdToken) { it.putAll(oidcTokenMetadata) } + } + + val refreshTokenValue = this[Authorization.refreshTokenValue].orEmpty() + if (refreshTokenValue.isNotBlank()) { + val refreshTokenIssuedAt = this[Authorization.refreshTokenIssuedAt] + val refreshTokenExpiresAt = this[Authorization.refreshTokenExpiresAt] + val refreshTokenMetadata = + this[Authorization.refreshTokenMetadata]?.let { JsonUtil.jsonToMap(it) }.orEmpty() + + val oAuth2RefreshToken = OAuth2RefreshToken(refreshTokenValue, refreshTokenIssuedAt, refreshTokenExpiresAt) + + builder.token(oAuth2RefreshToken) { it.putAll(refreshTokenMetadata) } + } + + val userCodeValue = this[Authorization.userCodeValue].orEmpty() + if (userCodeValue.isNotBlank()) { + val userCodeIssuedAt = this[Authorization.userCodeIssuedAt] + val userCodeExpiresAt = this[Authorization.userCodeExpiresAt] + val userCodeMetadata = + this[Authorization.userCodeMetadata]?.let { JsonUtil.jsonToMap(it) }.orEmpty() + val oAuth2UserCode = OAuth2UserCode(userCodeValue, userCodeIssuedAt, userCodeExpiresAt) + builder.token(oAuth2UserCode) { it.putAll(userCodeMetadata) } + } + + val deviceCodeValue = this[Authorization.deviceCodeValue].orEmpty() + if (deviceCodeValue.isNotBlank()) { + val deviceCodeIssuedAt = this[Authorization.deviceCodeIssuedAt] + val deviceCodeExpiresAt = this[Authorization.deviceCodeExpiresAt] + val deviceCodeMetadata = + this[Authorization.deviceCodeMetadata]?.let { JsonUtil.jsonToMap(it) }.orEmpty() + + val oAuth2DeviceCode = OAuth2DeviceCode(deviceCodeValue, deviceCodeIssuedAt, deviceCodeExpiresAt) + builder.token(oAuth2DeviceCode) { it.putAll(deviceCodeMetadata) } + } + + return builder.build() + } +} + +object Authorization : Table("authorization") { + val id = varchar("id", 255) + val registeredClientId = varchar("registered_client_id", 255) + val principalName = varchar("principal_name", 255) + val authorizationGrantType = varchar("authorization_grant_type", 255) + val authorizedScopes = varchar("authorized_scopes", 1000).nullable().default(null) + val attributes = varchar("attributes", 4000).nullable().default(null) + val state = varchar("state", 500).nullable().default(null) + val authorizationCodeValue = varchar("authorization_code_value", 4000).nullable().default(null) + val authorizationCodeIssuedAt = timestamp("authorization_code_issued_at").nullable().default(null) + val authorizationCodeExpiresAt = timestamp("authorization_code_expires_at").nullable().default(null) + val authorizationCodeMetadata = varchar("authorization_code_metadata", 2000).nullable().default(null) + val accessTokenValue = varchar("access_token_value", 4000).nullable().default(null) + val accessTokenIssuedAt = timestamp("access_token_issued_at").nullable().default(null) + val accessTokenExpiresAt = timestamp("access_token_expires_at").nullable().default(null) + val accessTokenMetadata = varchar("access_token_metadata", 2000).nullable().default(null) + val accessTokenType = varchar("access_token_type", 255).nullable().default(null) + val accessTokenScopes = varchar("access_token_scopes", 1000).nullable().default(null) + val refreshTokenValue = varchar("refresh_token_value", 4000).nullable().default(null) + val refreshTokenIssuedAt = timestamp("refresh_token_issued_at").nullable().default(null) + val refreshTokenExpiresAt = timestamp("refresh_token_expires_at").nullable().default(null) + val refreshTokenMetadata = varchar("refresh_token_metadata", 2000).nullable().default(null) + val oidcIdTokenValue = varchar("oidc_id_token_value", 4000).nullable().default(null) + val oidcIdTokenIssuedAt = timestamp("oidc_id_token_issued_at").nullable().default(null) + val oidcIdTokenExpiresAt = timestamp("oidc_id_token_expires_at").nullable().default(null) + val oidcIdTokenMetadata = varchar("oidc_id_token_metadata", 2000).nullable().default(null) + val oidcIdTokenClaims = varchar("oidc_id_token_claims", 2000).nullable().default(null) + val userCodeValue = varchar("user_code_value", 4000).nullable().default(null) + val userCodeIssuedAt = timestamp("user_code_issued_at").nullable().default(null) + val userCodeExpiresAt = timestamp("user_code_expires_at").nullable().default(null) + val userCodeMetadata = varchar("user_code_metadata", 2000).nullable().default(null) + val deviceCodeValue = varchar("device_code_value", 4000).nullable().default(null) + val deviceCodeIssuedAt = timestamp("device_code_issued_at").nullable().default(null) + val deviceCodeExpiresAt = timestamp("device_code_expires_at").nullable().default(null) + val deviceCodeMetadata = varchar("device_code_metadata", 2000).nullable().default(null) + + override val primaryKey = PrimaryKey(id) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/auth/HttpSignatureVerifyService.kt b/src/main/kotlin/dev/usbharu/hideout/service/auth/HttpSignatureVerifyService.kt index eb30c903..e7697992 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/auth/HttpSignatureVerifyService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/auth/HttpSignatureVerifyService.kt @@ -5,13 +5,16 @@ import dev.usbharu.hideout.query.UserQueryService import dev.usbharu.hideout.service.core.Transaction import io.ktor.http.* import org.koin.core.annotation.Single +import org.springframework.stereotype.Service import tech.barbero.http.message.signing.SignatureHeaderVerifier +@Service interface HttpSignatureVerifyService { fun verify(headers: Headers): Boolean } @Single +@Service class HttpSignatureVerifyServiceImpl( private val userQueryService: UserQueryService, private val transaction: Transaction diff --git a/src/main/kotlin/dev/usbharu/hideout/service/auth/JwtService.kt b/src/main/kotlin/dev/usbharu/hideout/service/auth/JwtService.kt index 462430ac..53dc2ae4 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/auth/JwtService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/auth/JwtService.kt @@ -15,10 +15,12 @@ import dev.usbharu.hideout.service.core.MetaService import dev.usbharu.hideout.util.RsaUtil import kotlinx.coroutines.runBlocking import org.koin.core.annotation.Single +import org.springframework.stereotype.Service import java.time.Instant import java.time.temporal.ChronoUnit import java.util.* +@Service interface JwtService { suspend fun createToken(user: User): JwtToken suspend fun refreshToken(refreshToken: RefreshToken): JwtToken @@ -30,6 +32,7 @@ interface JwtService { @Suppress("InjectDispatcher") @Single +@Service class JwtServiceImpl( private val metaService: MetaService, private val refreshTokenRepository: JwtRefreshTokenRepository, diff --git a/src/main/kotlin/dev/usbharu/hideout/service/auth/UserDetailsServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/auth/UserDetailsServiceImpl.kt new file mode 100644 index 00000000..34bd9b60 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/auth/UserDetailsServiceImpl.kt @@ -0,0 +1,28 @@ +package dev.usbharu.hideout.service.auth + +import dev.usbharu.hideout.query.UserQueryService +import dev.usbharu.hideout.service.core.Transaction +import kotlinx.coroutines.runBlocking +import org.springframework.security.core.userdetails.User +import org.springframework.security.core.userdetails.UserDetails +import org.springframework.security.core.userdetails.UserDetailsService +import org.springframework.security.core.userdetails.UsernameNotFoundException +import org.springframework.stereotype.Service + +@Service +class UserDetailsServiceImpl(private val userQueryService: UserQueryService, private val transaction: Transaction) : + UserDetailsService { + override fun loadUserByUsername(username: String?): UserDetails = runBlocking { + if (username == null) { + throw UsernameNotFoundException("$username not found") + } + transaction.transaction { + val findById = userQueryService.findByNameAndDomain(username, "") + User( + findById.name, + findById.password, + emptyList() + ) + } + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/core/ExposedTransaction.kt b/src/main/kotlin/dev/usbharu/hideout/service/core/ExposedTransaction.kt index c15327a4..283fa7c8 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/core/ExposedTransaction.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/core/ExposedTransaction.kt @@ -2,8 +2,10 @@ package dev.usbharu.hideout.service.core import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction import org.koin.core.annotation.Single +import org.springframework.stereotype.Service @Single +@Service class ExposedTransaction : Transaction { override suspend fun transaction(block: suspend () -> T): T { return newSuspendedTransaction(transactionIsolation = java.sql.Connection.TRANSACTION_SERIALIZABLE) { diff --git a/src/main/kotlin/dev/usbharu/hideout/service/core/IdGenerateService.kt b/src/main/kotlin/dev/usbharu/hideout/service/core/IdGenerateService.kt index 2962f4e3..fae8a683 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/core/IdGenerateService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/core/IdGenerateService.kt @@ -1,5 +1,8 @@ package dev.usbharu.hideout.service.core +import org.springframework.stereotype.Service + +@Service interface IdGenerateService { suspend fun generateId(): Long } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/core/MetaService.kt b/src/main/kotlin/dev/usbharu/hideout/service/core/MetaService.kt index 91da1a90..a455d1b1 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/core/MetaService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/core/MetaService.kt @@ -2,7 +2,9 @@ package dev.usbharu.hideout.service.core import dev.usbharu.hideout.domain.model.hideout.entity.Jwt import dev.usbharu.hideout.domain.model.hideout.entity.Meta +import org.springframework.stereotype.Service +@Service interface MetaService { suspend fun getMeta(): Meta suspend fun updateMeta(meta: Meta) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/core/MetaServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/core/MetaServiceImpl.kt index e35ff3f7..1492c7a8 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/core/MetaServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/core/MetaServiceImpl.kt @@ -5,8 +5,10 @@ import dev.usbharu.hideout.domain.model.hideout.entity.Meta import dev.usbharu.hideout.exception.NotInitException import dev.usbharu.hideout.repository.MetaRepository import org.koin.core.annotation.Single +import org.springframework.stereotype.Service @Single +@Service class MetaServiceImpl(private val metaRepository: MetaRepository, private val transaction: Transaction) : MetaService { override suspend fun getMeta(): Meta = diff --git a/src/main/kotlin/dev/usbharu/hideout/service/core/ServerInitialiseService.kt b/src/main/kotlin/dev/usbharu/hideout/service/core/ServerInitialiseService.kt index d65f8fa6..edd234cd 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/core/ServerInitialiseService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/core/ServerInitialiseService.kt @@ -1,5 +1,8 @@ package dev.usbharu.hideout.service.core +import org.springframework.stereotype.Service + +@Service interface ServerInitialiseService { suspend fun init() } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/core/ServerInitialiseServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/core/ServerInitialiseServiceImpl.kt index 4fc950c1..2917b15b 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/core/ServerInitialiseServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/core/ServerInitialiseServiceImpl.kt @@ -7,10 +7,12 @@ import dev.usbharu.hideout.util.ServerUtil import org.koin.core.annotation.Single import org.slf4j.Logger import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service import java.security.KeyPairGenerator import java.util.* @Single +@Service class ServerInitialiseServiceImpl( private val metaRepository: MetaRepository, private val transaction: Transaction diff --git a/src/main/kotlin/dev/usbharu/hideout/service/core/Transaction.kt b/src/main/kotlin/dev/usbharu/hideout/service/core/Transaction.kt index 105420ed..a5c787a2 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/core/Transaction.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/core/Transaction.kt @@ -1,5 +1,8 @@ package dev.usbharu.hideout.service.core +import org.springframework.stereotype.Service + +@Service interface Transaction { suspend fun transaction(block: suspend () -> T): T suspend fun transaction(transactionLevel: Int, block: suspend () -> T): T diff --git a/src/main/kotlin/dev/usbharu/hideout/service/core/TwitterSnowflakeIdGenerateService.kt b/src/main/kotlin/dev/usbharu/hideout/service/core/TwitterSnowflakeIdGenerateService.kt index 35a9cd14..e3e46739 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/core/TwitterSnowflakeIdGenerateService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/core/TwitterSnowflakeIdGenerateService.kt @@ -1,5 +1,10 @@ package dev.usbharu.hideout.service.core +import org.springframework.context.annotation.Primary +import org.springframework.stereotype.Service + // 2010-11-04T01:42:54.657 @Suppress("MagicNumber") +@Service +@Primary object TwitterSnowflakeIdGenerateService : SnowflakeIdGenerateService(1288834974657L) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueParentService.kt b/src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueParentService.kt index f553c227..dfecf8a4 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueParentService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueParentService.kt @@ -2,7 +2,9 @@ package dev.usbharu.hideout.service.job import kjob.core.Job import kjob.core.dsl.ScheduleContext +import org.springframework.stereotype.Service +@Service interface JobQueueParentService { fun init(jobDefines: List) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueWorkerService.kt b/src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueWorkerService.kt index 567f9e21..80413c79 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueWorkerService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueWorkerService.kt @@ -2,9 +2,11 @@ package dev.usbharu.hideout.service.job import kjob.core.Job import kjob.core.dsl.KJobFunctions +import org.springframework.stereotype.Service import kjob.core.dsl.JobContextWithProps as JCWP import kjob.core.dsl.JobRegisterContext as JRC +@Service interface JobQueueWorkerService { fun init(defines: List>.(Job) -> KJobFunctions>>>) } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobQueueParentService.kt b/src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobQueueParentService.kt index d5367d80..e7d9fc30 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobQueueParentService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobQueueParentService.kt @@ -7,7 +7,9 @@ import kjob.core.dsl.ScheduleContext import kjob.core.kjob import org.jetbrains.exposed.sql.Database import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service +@Service class KJobJobQueueParentService(private val database: Database) : JobQueueParentService { private val logger = LoggerFactory.getLogger(this::class.java) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobQueueWorkerService.kt b/src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobQueueWorkerService.kt index 67d84821..368aae99 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobQueueWorkerService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobQueueWorkerService.kt @@ -5,9 +5,11 @@ import kjob.core.Job import kjob.core.dsl.KJobFunctions import kjob.core.kjob import org.jetbrains.exposed.sql.Database +import org.springframework.stereotype.Service import kjob.core.dsl.JobContextWithProps as JCWP import kjob.core.dsl.JobRegisterContext as JRC +@Service class KJobJobQueueWorkerService(private val database: Database) : JobQueueWorkerService { val kjob by lazy { diff --git a/src/main/kotlin/dev/usbharu/hideout/service/post/PostService.kt b/src/main/kotlin/dev/usbharu/hideout/service/post/PostService.kt index 28c90710..0eed9d72 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/post/PostService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/post/PostService.kt @@ -2,7 +2,9 @@ package dev.usbharu.hideout.service.post import dev.usbharu.hideout.domain.model.hideout.dto.PostCreateDto import dev.usbharu.hideout.domain.model.hideout.entity.Post +import org.springframework.stereotype.Service +@Service interface PostService { suspend fun createLocal(post: PostCreateDto): Post } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/post/PostServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/post/PostServiceImpl.kt index c1b58d1b..2e81aeef 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/post/PostServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/post/PostServiceImpl.kt @@ -7,8 +7,10 @@ import dev.usbharu.hideout.repository.PostRepository import dev.usbharu.hideout.repository.UserRepository import dev.usbharu.hideout.service.ap.APNoteService import org.koin.core.annotation.Single +import org.springframework.stereotype.Service import java.time.Instant +@Service @Single class PostServiceImpl( private val postRepository: PostRepository, diff --git a/src/main/kotlin/dev/usbharu/hideout/service/reaction/ReactionService.kt b/src/main/kotlin/dev/usbharu/hideout/service/reaction/ReactionService.kt index a7b9ed0d..d39dc483 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/reaction/ReactionService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/reaction/ReactionService.kt @@ -1,5 +1,8 @@ package dev.usbharu.hideout.service.reaction +import org.springframework.stereotype.Service + +@Service interface ReactionService { suspend fun receiveReaction(name: String, domain: String, userId: Long, postId: Long) suspend fun sendReaction(name: String, userId: Long, postId: Long) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/reaction/ReactionServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/reaction/ReactionServiceImpl.kt index f8c24df9..cc822a18 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/reaction/ReactionServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/reaction/ReactionServiceImpl.kt @@ -5,8 +5,10 @@ import dev.usbharu.hideout.query.ReactionQueryService import dev.usbharu.hideout.repository.ReactionRepository import dev.usbharu.hideout.service.ap.APReactionService import org.koin.core.annotation.Single +import org.springframework.stereotype.Service @Single +@Service class ReactionServiceImpl( private val reactionRepository: ReactionRepository, private val apReactionService: APReactionService, diff --git a/src/main/kotlin/dev/usbharu/hideout/service/user/UserAuthService.kt b/src/main/kotlin/dev/usbharu/hideout/service/user/UserAuthService.kt index 61853b73..9630f8fa 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/user/UserAuthService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/user/UserAuthService.kt @@ -1,7 +1,9 @@ package dev.usbharu.hideout.service.user +import org.springframework.stereotype.Service import java.security.KeyPair +@Service interface UserAuthService { fun hash(password: String): String diff --git a/src/main/kotlin/dev/usbharu/hideout/service/user/UserAuthServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/user/UserAuthServiceImpl.kt index 0c234430..a4e0bba6 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/user/UserAuthServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/user/UserAuthServiceImpl.kt @@ -4,10 +4,12 @@ import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.query.UserQueryService import io.ktor.util.* import org.koin.core.annotation.Single +import org.springframework.stereotype.Service import java.security.* import java.util.* @Single +@Service class UserAuthServiceImpl( val userQueryService: UserQueryService ) : UserAuthService { diff --git a/src/main/kotlin/dev/usbharu/hideout/service/user/UserService.kt b/src/main/kotlin/dev/usbharu/hideout/service/user/UserService.kt index a141fa24..665d14f5 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/user/UserService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/user/UserService.kt @@ -3,8 +3,10 @@ package dev.usbharu.hideout.service.user import dev.usbharu.hideout.domain.model.hideout.dto.RemoteUserCreateDto import dev.usbharu.hideout.domain.model.hideout.dto.UserCreateDto import dev.usbharu.hideout.domain.model.hideout.entity.User +import org.springframework.stereotype.Service @Suppress("TooManyFunctions") +@Service interface UserService { suspend fun usernameAlreadyUse(username: String): Boolean diff --git a/src/main/kotlin/dev/usbharu/hideout/service/user/UserServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/user/UserServiceImpl.kt index 3d2a93dd..22cc6001 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/user/UserServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/user/UserServiceImpl.kt @@ -11,9 +11,11 @@ import dev.usbharu.hideout.query.UserQueryService import dev.usbharu.hideout.repository.UserRepository import dev.usbharu.hideout.service.ap.APSendFollowService import org.koin.core.annotation.Single +import org.springframework.stereotype.Service import java.time.Instant @Single +@Service class UserServiceImpl( private val userRepository: UserRepository, private val userAuthService: UserAuthService, diff --git a/src/main/kotlin/dev/usbharu/hideout/util/JsonUtil.kt b/src/main/kotlin/dev/usbharu/hideout/util/JsonUtil.kt new file mode 100644 index 00000000..25d282bc --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/util/JsonUtil.kt @@ -0,0 +1,15 @@ +package dev.usbharu.hideout.util + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue + +object JsonUtil { + val objectMapper = jacksonObjectMapper() + + fun mapToJson(map: Map<*, *>, objectMapper: ObjectMapper = this.objectMapper): String = + objectMapper.writeValueAsString(map) + + fun jsonToMap(json: String, objectMapper: ObjectMapper = this.objectMapper): Map = + objectMapper.readValue(json) +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 00000000..5683266b --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,12 @@ +hideout: + database: + url: "jdbc:h2:./test;MODE=POSTGRESQL" + driver: "org.h2.Driver" + user: "" + password: "" +spring: + datasource: + driver-class-name: org.h2.Driver + url: "jdbc:h2:./test;MODE=POSTGRESQL" + username: "" + password: "" diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index ad457f2b..4593b633 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -4,7 +4,7 @@ %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - + diff --git a/src/main/resources/openapi/api.yaml b/src/main/resources/openapi/api.yaml index 840daedd..8cbfd648 100644 --- a/src/main/resources/openapi/api.yaml +++ b/src/main/resources/openapi/api.yaml @@ -214,12 +214,7 @@ paths: content: application/json: schema: - type: object - properties: - username: - type: string - password: - type: string + $ref: "#/components/schemas/UserCreate" responses: 201: description: ユーザーが作成された @@ -509,6 +504,14 @@ components: url: type: string + UserCreate: + type: object + properties: + username: + type: string + password: + type: string + securitySchemes: BearerAuth: type: http diff --git a/src/main/resources/openapi/mastodon.yaml b/src/main/resources/openapi/mastodon.yaml new file mode 100644 index 00000000..dbcf6195 --- /dev/null +++ b/src/main/resources/openapi/mastodon.yaml @@ -0,0 +1,22 @@ +openapi: 3.0.3 +info: + title: Hideout Mastodon Compatible API + description: Hideout Mastodon Compatible API + version: 1.0.0 +servers: + - url: 'https://test-hideout.usbharu.dev' +paths: + +components: + schemas: + Account: + type: object + properties: + id: + type: string + username: + type: string + acct: + type: string + url: + type: string