mirror of https://github.com/usbharu/Hideout.git
Merge pull request #44 from usbharu/feature/delete-ktor
Feature/delete ktor
This commit is contained in:
commit
34b19829f4
107
build.gradle.kts
107
build.gradle.kts
|
@ -1,4 +1,3 @@
|
||||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
|
||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
import org.openapitools.generator.gradle.plugin.tasks.GenerateTask
|
import org.openapitools.generator.gradle.plugin.tasks.GenerateTask
|
||||||
|
|
||||||
|
@ -11,7 +10,6 @@ val koin_version: String by project
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "1.8.21"
|
kotlin("jvm") version "1.8.21"
|
||||||
id("io.ktor.plugin") version "2.3.0"
|
|
||||||
id("org.graalvm.buildtools.native") version "0.9.21"
|
id("org.graalvm.buildtools.native") version "0.9.21"
|
||||||
id("io.gitlab.arturbosch.detekt") version "1.23.1"
|
id("io.gitlab.arturbosch.detekt") version "1.23.1"
|
||||||
id("com.google.devtools.ksp") version "1.8.21-1.0.11"
|
id("com.google.devtools.ksp") version "1.8.21-1.0.11"
|
||||||
|
@ -27,12 +25,6 @@ apply {
|
||||||
|
|
||||||
group = "dev.usbharu"
|
group = "dev.usbharu"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
application {
|
|
||||||
mainClass.set("dev.usbharu.hideout.SpringApplicationKt")
|
|
||||||
|
|
||||||
val isDevelopment: Boolean = project.ext.has("development")
|
|
||||||
applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment")
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.withType<Test> {
|
tasks.withType<Test> {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
|
@ -51,47 +43,11 @@ tasks.withType<KotlinCompile> {
|
||||||
mustRunAfter("openApiGenerateMastodonCompatibleApi")
|
mustRunAfter("openApiGenerateMastodonCompatibleApi")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<ShadowJar> {
|
|
||||||
manifest {
|
|
||||||
attributes(
|
|
||||||
"Implementation-Version" to project.version.toString()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.clean {
|
tasks.clean {
|
||||||
delete += listOf("$rootDir/src/main/resources/static")
|
delete += listOf("$rootDir/src/main/resources/static")
|
||||||
}
|
}
|
||||||
|
|
||||||
//tasks.create<GenerateTask>("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"))
|
|
||||||
//}
|
|
||||||
|
|
||||||
tasks.create<GenerateTask>("openApiGenerateMastodonCompatibleApi", GenerateTask::class) {
|
tasks.create<GenerateTask>("openApiGenerateMastodonCompatibleApi", GenerateTask::class) {
|
||||||
generatorName.set("kotlin-spring")
|
generatorName.set("kotlin-spring")
|
||||||
inputSpec.set("$rootDir/src/main/resources/openapi/mastodon.yaml")
|
inputSpec.set("$rootDir/src/main/resources/openapi/mastodon.yaml")
|
||||||
|
@ -128,31 +84,21 @@ sourceSets.main {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("io.ktor:ktor-server-core-jvm:$ktor_version")
|
|
||||||
implementation("io.ktor:ktor-server-auth:$ktor_version")
|
|
||||||
implementation("io.ktor:ktor-server-auth-jwt:$ktor_version")
|
|
||||||
implementation("io.ktor:ktor-server-sessions-jvm:$ktor_version")
|
|
||||||
implementation("io.ktor:ktor-server-auto-head-response-jvm:$ktor_version")
|
|
||||||
implementation("io.ktor:ktor-server-cors-jvm:$ktor_version")
|
|
||||||
implementation("io.ktor:ktor-server-default-headers-jvm:$ktor_version")
|
|
||||||
implementation("io.ktor:ktor-server-forwarded-header-jvm:$ktor_version")
|
|
||||||
implementation("io.ktor:ktor-server-call-logging-jvm:$ktor_version")
|
|
||||||
implementation("io.ktor:ktor-server-content-negotiation-jvm:$ktor_version")
|
|
||||||
implementation("io.ktor:ktor-serialization-jackson:$ktor_version")
|
implementation("io.ktor:ktor-serialization-jackson:$ktor_version")
|
||||||
implementation("org.jetbrains.exposed:exposed-core:$exposed_version")
|
implementation("org.jetbrains.exposed:exposed-core:$exposed_version")
|
||||||
implementation("org.jetbrains.exposed:exposed-jdbc:$exposed_version")
|
implementation("org.jetbrains.exposed:exposed-jdbc:$exposed_version")
|
||||||
implementation("com.h2database:h2:$h2_version")
|
implementation("com.h2database:h2:$h2_version")
|
||||||
implementation("org.xerial:sqlite-jdbc:3.40.1.0")
|
implementation("org.xerial:sqlite-jdbc:3.40.1.0")
|
||||||
implementation("io.ktor:ktor-server-websockets-jvm:$ktor_version")
|
|
||||||
implementation("io.ktor:ktor-server-cio-jvm:$ktor_version")
|
|
||||||
implementation("io.ktor:ktor-server-compression:$ktor_version")
|
|
||||||
implementation("ch.qos.logback:logback-classic:$logback_version")
|
implementation("ch.qos.logback:logback-classic:$logback_version")
|
||||||
|
implementation("com.auth0:java-jwt:4.4.0")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.0")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
|
||||||
|
|
||||||
implementation("io.insert-koin:koin-core:$koin_version")
|
implementation("io.insert-koin:koin-core:$koin_version")
|
||||||
implementation("io.insert-koin:koin-ktor:$koin_version")
|
implementation("io.insert-koin:koin-ktor:$koin_version")
|
||||||
implementation("io.insert-koin:koin-logger-slf4j:$koin_version")
|
implementation("io.insert-koin:koin-logger-slf4j:$koin_version")
|
||||||
implementation("io.insert-koin:koin-annotations:1.2.0")
|
implementation("io.insert-koin:koin-annotations:1.2.0")
|
||||||
implementation("io.ktor:ktor-server-compression-jvm:2.3.0")
|
|
||||||
implementation("org.springframework.boot:spring-boot-starter-actuator")
|
implementation("org.springframework.boot:spring-boot-starter-actuator")
|
||||||
ksp("io.insert-koin:koin-ksp-compiler:1.2.0")
|
ksp("io.insert-koin:koin-ksp-compiler:1.2.0")
|
||||||
|
|
||||||
|
@ -178,10 +124,7 @@ dependencies {
|
||||||
implementation("org.springframework.security:spring-security-oauth2-jose")
|
implementation("org.springframework.security:spring-security-oauth2-jose")
|
||||||
|
|
||||||
implementation("io.ktor:ktor-client-logging-jvm:$ktor_version")
|
implementation("io.ktor:ktor-client-logging-jvm:$ktor_version")
|
||||||
implementation("io.ktor:ktor-server-host-common-jvm:$ktor_version")
|
|
||||||
implementation("io.ktor:ktor-server-status-pages-jvm:$ktor_version")
|
|
||||||
|
|
||||||
testImplementation("io.ktor:ktor-server-tests-jvm:$ktor_version")
|
|
||||||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_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")
|
||||||
|
|
||||||
|
@ -197,53 +140,11 @@ dependencies {
|
||||||
|
|
||||||
|
|
||||||
implementation("org.drewcarlson:kjob-core:0.6.0")
|
implementation("org.drewcarlson:kjob-core:0.6.0")
|
||||||
testImplementation("io.ktor:ktor-server-test-host-jvm:$ktor_version")
|
|
||||||
|
|
||||||
testImplementation("org.slf4j:slf4j-simple:2.0.7")
|
testImplementation("org.slf4j:slf4j-simple:2.0.7")
|
||||||
|
|
||||||
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.22.0")
|
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.22.0")
|
||||||
}
|
}
|
||||||
|
|
||||||
jib {
|
|
||||||
if (System.getProperty("os.name").toLowerCase().contains("windows")) {
|
|
||||||
dockerClient.environment = mapOf(
|
|
||||||
"DOCKER_HOST" to "localhost:2375"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ktor {
|
|
||||||
docker {
|
|
||||||
localImageName.set("hideout")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
graalvmNative {
|
|
||||||
binaries {
|
|
||||||
named("main") {
|
|
||||||
fallback.set(false)
|
|
||||||
verbose.set(true)
|
|
||||||
agent {
|
|
||||||
enabled.set(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
buildArgs.add("--initialize-at-build-time=io.ktor,kotlin,kotlinx")
|
|
||||||
// buildArgs.add("--trace-class-initialization=ch.qos.logback.classic.Logger")
|
|
||||||
// buildArgs.add("--trace-object-instantiation=ch.qos.logback.core.AsyncAppenderBase"+"$"+"Worker")
|
|
||||||
// buildArgs.add("--trace-object-instantiation=ch.qos.logback.classic.Logger")
|
|
||||||
buildArgs.add("--initialize-at-build-time=org.slf4j.LoggerFactory,ch.qos.logback")
|
|
||||||
// buildArgs.add("--trace-object-instantiation=kotlinx.coroutines.channels.ArrayChannel")
|
|
||||||
buildArgs.add("--initialize-at-build-time=kotlinx.coroutines.channels.ArrayChannel")
|
|
||||||
buildArgs.add("-H:+InstallExitHandlers")
|
|
||||||
buildArgs.add("-H:+ReportUnsupportedElementsAtRuntime")
|
|
||||||
buildArgs.add("-H:+ReportExceptionStackTraces")
|
|
||||||
|
|
||||||
runtimeArgs.add("-config=$buildDir/resources/main/application-native.conf")
|
|
||||||
imageName.set("graal-server")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
detekt {
|
detekt {
|
||||||
parallel = true
|
parallel = true
|
||||||
config = files("detekt.yml")
|
config = files("detekt.yml")
|
||||||
|
|
|
@ -1,172 +0,0 @@
|
||||||
package dev.usbharu.hideout
|
|
||||||
|
|
||||||
import com.auth0.jwk.JwkProvider
|
|
||||||
import com.auth0.jwk.JwkProviderBuilder
|
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude
|
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
|
||||||
import dev.usbharu.hideout.config.CharacterLimit
|
|
||||||
import dev.usbharu.hideout.config.Config
|
|
||||||
import dev.usbharu.hideout.config.ConfigData
|
|
||||||
import dev.usbharu.hideout.domain.model.job.DeliverPostJob
|
|
||||||
import dev.usbharu.hideout.domain.model.job.DeliverReactionJob
|
|
||||||
import dev.usbharu.hideout.domain.model.job.DeliverRemoveReactionJob
|
|
||||||
import dev.usbharu.hideout.domain.model.job.ReceiveFollowJob
|
|
||||||
import dev.usbharu.hideout.plugins.*
|
|
||||||
import dev.usbharu.hideout.query.FollowerQueryService
|
|
||||||
import dev.usbharu.hideout.query.UserQueryService
|
|
||||||
import dev.usbharu.hideout.routing.register
|
|
||||||
import dev.usbharu.hideout.service.ap.APService
|
|
||||||
import dev.usbharu.hideout.service.ap.APUserService
|
|
||||||
import dev.usbharu.hideout.service.api.PostApiService
|
|
||||||
import dev.usbharu.hideout.service.api.UserApiService
|
|
||||||
import dev.usbharu.hideout.service.api.UserAuthApiService
|
|
||||||
import dev.usbharu.hideout.service.api.WebFingerApiService
|
|
||||||
import dev.usbharu.hideout.service.auth.HttpSignatureVerifyService
|
|
||||||
import dev.usbharu.hideout.service.core.*
|
|
||||||
import dev.usbharu.hideout.service.job.JobQueueParentService
|
|
||||||
import dev.usbharu.hideout.service.job.KJobJobQueueParentService
|
|
||||||
import dev.usbharu.hideout.service.user.UserService
|
|
||||||
import dev.usbharu.kjob.exposed.ExposedKJob
|
|
||||||
import io.ktor.client.*
|
|
||||||
import io.ktor.client.engine.cio.*
|
|
||||||
import io.ktor.client.plugins.logging.*
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
import kjob.core.kjob
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import org.jetbrains.exposed.sql.Database
|
|
||||||
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<String>): Unit = io.ktor.server.cio.EngineMain.main(args)
|
|
||||||
|
|
||||||
val Application.property: Application.(propertyName: String) -> String
|
|
||||||
get() = {
|
|
||||||
environment.config.property(it).getString()
|
|
||||||
}
|
|
||||||
|
|
||||||
val Application.propertyOrNull: Application.(propertyName: String) -> String?
|
|
||||||
get() = {
|
|
||||||
environment.config.propertyOrNull(it)?.getString()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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(
|
|
||||||
url = property("hideout.url"),
|
|
||||||
objectMapper = jacksonObjectMapper().enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
|
|
||||||
.setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
|
|
||||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false),
|
|
||||||
characterLimit = CharacterLimit(
|
|
||||||
general = CharacterLimit.General.of(
|
|
||||||
url = propertyOrNull("hideout.character-limit.general.url")?.toIntOrNull(),
|
|
||||||
domain = propertyOrNull("hideout.character-limit.general.domain")?.toIntOrNull(),
|
|
||||||
publicKey = propertyOrNull("hideout.character-limit.general.publicKey")?.toIntOrNull(),
|
|
||||||
privateKey = propertyOrNull("hideout.character-limit.general.privateKey")?.toIntOrNull()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
val module = org.koin.dsl.module {
|
|
||||||
single<Database> {
|
|
||||||
Database.connect(
|
|
||||||
url = property("hideout.database.url"),
|
|
||||||
driver = property("hideout.database.driver"),
|
|
||||||
user = property("hideout.database.username"),
|
|
||||||
password = property("hideout.database.password")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
single<JobQueueParentService> {
|
|
||||||
val kJobJobQueueService = KJobJobQueueParentService(get())
|
|
||||||
kJobJobQueueService.init(emptyList())
|
|
||||||
kJobJobQueueService
|
|
||||||
}
|
|
||||||
single<HttpClient> {
|
|
||||||
HttpClient(CIO).config {
|
|
||||||
install(Logging) {
|
|
||||||
logger = Logger.DEFAULT
|
|
||||||
level = LogLevel.INFO
|
|
||||||
}
|
|
||||||
install(httpSignaturePlugin) {
|
|
||||||
keyMap = KtorKeyMap(get(), get())
|
|
||||||
}
|
|
||||||
expectSuccess = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
single<IdGenerateService> { TwitterSnowflakeIdGenerateService }
|
|
||||||
single<JwkProvider> {
|
|
||||||
JwkProviderBuilder(Config.configData.url).cached(
|
|
||||||
10,
|
|
||||||
24,
|
|
||||||
TimeUnit.HOURS
|
|
||||||
)
|
|
||||||
.rateLimited(10, 1, TimeUnit.MINUTES).build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
configureKoin(module, HideoutModule().module)
|
|
||||||
configureStatusPages()
|
|
||||||
runBlocking {
|
|
||||||
inject<ServerInitialiseService>().value.init()
|
|
||||||
}
|
|
||||||
configureCompression()
|
|
||||||
configureHTTP()
|
|
||||||
configureStaticRouting()
|
|
||||||
configureMonitoring()
|
|
||||||
configureSerialization()
|
|
||||||
register(inject<UserApiService>().value)
|
|
||||||
configureSecurity(
|
|
||||||
|
|
||||||
inject<JwkProvider>().value,
|
|
||||||
inject<MetaService>().value
|
|
||||||
)
|
|
||||||
configureRouting(
|
|
||||||
httpSignatureVerifyService = inject<HttpSignatureVerifyService>().value,
|
|
||||||
apService = inject<APService>().value,
|
|
||||||
userService = inject<UserService>().value,
|
|
||||||
apUserService = inject<APUserService>().value,
|
|
||||||
postService = inject<PostApiService>().value,
|
|
||||||
userApiService = inject<UserApiService>().value,
|
|
||||||
userQueryService = inject<UserQueryService>().value,
|
|
||||||
followerQueryService = inject<FollowerQueryService>().value,
|
|
||||||
userAuthApiService = inject<UserAuthApiService>().value,
|
|
||||||
webFingerApiService = inject<WebFingerApiService>().value,
|
|
||||||
transaction = inject<Transaction>().value
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated("Ktor is deprecated")
|
|
||||||
@Suppress("unused")
|
|
||||||
fun Application.worker() {
|
|
||||||
val kJob = kjob(ExposedKJob) {
|
|
||||||
connectionDatabase = inject<Database>().value
|
|
||||||
}.start()
|
|
||||||
|
|
||||||
val apService = inject<APService>().value
|
|
||||||
|
|
||||||
kJob.register(ReceiveFollowJob) {
|
|
||||||
execute {
|
|
||||||
apService.processActivity(this, it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
kJob.register(DeliverPostJob) {
|
|
||||||
execute {
|
|
||||||
apService.processActivity(this, it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
kJob.register(DeliverReactionJob) {
|
|
||||||
execute {
|
|
||||||
apService.processActivity(this, it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
kJob.register(DeliverRemoveReactionJob) {
|
|
||||||
execute {
|
|
||||||
apService.processActivity(this, it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -35,7 +35,6 @@ import java.security.interfaces.RSAPrivateKey
|
||||||
import java.security.interfaces.RSAPublicKey
|
import java.security.interfaces.RSAPublicKey
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
@EnableWebSecurity(debug = true)
|
@EnableWebSecurity(debug = true)
|
||||||
@Configuration
|
@Configuration
|
||||||
class SecurityConfig {
|
class SecurityConfig {
|
||||||
|
@ -152,8 +151,6 @@ class SecurityConfig {
|
||||||
if (OAuth2TokenType.ACCESS_TOKEN == context.tokenType) {
|
if (OAuth2TokenType.ACCESS_TOKEN == context.tokenType) {
|
||||||
val userDetailsImpl = context.getPrincipal<Authentication>().principal as UserDetailsImpl
|
val userDetailsImpl = context.getPrincipal<Authentication>().principal as UserDetailsImpl
|
||||||
context.claims.claim("uid", userDetailsImpl.id.toString())
|
context.claims.claim("uid", userDetailsImpl.id.toString())
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,5 +35,4 @@ class MastodonAppsApiController(private val appApiService: AppApiService) : AppA
|
||||||
HttpStatus.OK
|
HttpStatus.OK
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,8 +30,6 @@ class UserDetailsImpl(
|
||||||
@Serial
|
@Serial
|
||||||
private const val serialVersionUID: Long = -899168205656607781L
|
private const val serialVersionUID: Long = -899168205656607781L
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
||||||
|
@ -46,11 +44,9 @@ class UserDetailsImpl(
|
||||||
@JsonSubTypes
|
@JsonSubTypes
|
||||||
abstract class UserDetailsMixin
|
abstract class UserDetailsMixin
|
||||||
|
|
||||||
|
|
||||||
class UserDetailsDeserializer : JsonDeserializer<UserDetailsImpl>() {
|
class UserDetailsDeserializer : JsonDeserializer<UserDetailsImpl>() {
|
||||||
val SIMPLE_GRANTED_AUTHORITY_SET = object : TypeReference<Set<SimpleGrantedAuthority>>() {}
|
val SIMPLE_GRANTED_AUTHORITY_SET = object : TypeReference<Set<SimpleGrantedAuthority>>() {}
|
||||||
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): UserDetailsImpl {
|
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): UserDetailsImpl {
|
||||||
|
|
||||||
val mapper = p.codec as ObjectMapper
|
val mapper = p.codec as ObjectMapper
|
||||||
val jsonNode: JsonNode = mapper.readTree(p)
|
val jsonNode: JsonNode = mapper.readTree(p)
|
||||||
println(jsonNode)
|
println(jsonNode)
|
||||||
|
@ -70,7 +66,6 @@ class UserDetailsDeserializer : JsonDeserializer<UserDetailsImpl>() {
|
||||||
true,
|
true,
|
||||||
authorities.toMutableList(),
|
authorities.toMutableList(),
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun JsonNode.readText(field: String, defaultValue: String = ""): String {
|
fun JsonNode.readText(field: String, defaultValue: String = ""): String {
|
||||||
|
@ -79,5 +74,4 @@ class UserDetailsDeserializer : JsonDeserializer<UserDetailsImpl>() {
|
||||||
else -> defaultValue
|
else -> defaultValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,6 @@ import io.ktor.client.plugins.api.*
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.*
|
||||||
import io.ktor.client.statement.*
|
import io.ktor.client.statement.*
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import io.ktor.server.application.*
|
|
||||||
import io.ktor.server.response.*
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import tech.barbero.http.message.signing.HttpMessage
|
import tech.barbero.http.message.signing.HttpMessage
|
||||||
import tech.barbero.http.message.signing.HttpMessageSigner
|
import tech.barbero.http.message.signing.HttpMessageSigner
|
||||||
|
@ -28,12 +26,6 @@ import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.crypto.SecretKey
|
import javax.crypto.SecretKey
|
||||||
|
|
||||||
suspend fun <T : JsonLd> ApplicationCall.respondAp(message: T, status: HttpStatusCode = HttpStatusCode.OK) {
|
|
||||||
message.context += "https://www.w3.org/ns/activitystreams"
|
|
||||||
val activityJson = Config.configData.objectMapper.writeValueAsString(message)
|
|
||||||
respondText(activityJson, ContentType.Application.Activity, status)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun HttpClient.postAp(urlString: String, username: String, jsonLd: JsonLd): HttpResponse {
|
suspend fun HttpClient.postAp(urlString: String, username: String, jsonLd: JsonLd): HttpResponse {
|
||||||
jsonLd.context += "https://www.w3.org/ns/activitystreams"
|
jsonLd.context += "https://www.w3.org/ns/activitystreams"
|
||||||
return this.post(urlString) {
|
return this.post(urlString) {
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
package dev.usbharu.hideout.plugins
|
|
||||||
|
|
||||||
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 {
|
|
||||||
matchContentType(ContentType.Application.JavaScript)
|
|
||||||
priority = 1.0
|
|
||||||
}
|
|
||||||
deflate {
|
|
||||||
matchContentType(ContentType.Application.JavaScript)
|
|
||||||
priority = 10.0
|
|
||||||
minimumSize(1024) // condition
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
package dev.usbharu.hideout.plugins
|
|
||||||
|
|
||||||
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)
|
|
||||||
// allowMethod(HttpMethod.Put)
|
|
||||||
// allowMethod(HttpMethod.Delete)
|
|
||||||
// allowMethod(HttpMethod.Patch)
|
|
||||||
// allowHeader(HttpHeaders.Authorization)
|
|
||||||
// allow
|
|
||||||
// allowHeader("MyCustomHeader")
|
|
||||||
// anyHost() // @TODO: Don't do this in production if possible. Try to limit it.
|
|
||||||
// }
|
|
||||||
install(DefaultHeaders) {
|
|
||||||
header("X-Engine", "Ktor") // will send this header with each response
|
|
||||||
}
|
|
||||||
install(ForwardedHeaders) // WARNING: for security, do not include this if not behind a reverse proxy
|
|
||||||
install(XForwardedHeaders) // WARNING: for security, do not include this if not behind a reverse proxy
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
package dev.usbharu.hideout.plugins
|
|
||||||
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
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()
|
|
||||||
modules(*module)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
package dev.usbharu.hideout.plugins
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
package dev.usbharu.hideout.plugins
|
|
||||||
|
|
||||||
import dev.usbharu.hideout.query.FollowerQueryService
|
|
||||||
import dev.usbharu.hideout.query.UserQueryService
|
|
||||||
import dev.usbharu.hideout.routing.activitypub.inbox
|
|
||||||
import dev.usbharu.hideout.routing.activitypub.outbox
|
|
||||||
import dev.usbharu.hideout.routing.activitypub.usersAP
|
|
||||||
import dev.usbharu.hideout.routing.api.internal.v1.auth
|
|
||||||
import dev.usbharu.hideout.routing.api.internal.v1.posts
|
|
||||||
import dev.usbharu.hideout.routing.api.internal.v1.users
|
|
||||||
import dev.usbharu.hideout.routing.wellknown.webfinger
|
|
||||||
import dev.usbharu.hideout.service.ap.APService
|
|
||||||
import dev.usbharu.hideout.service.ap.APUserService
|
|
||||||
import dev.usbharu.hideout.service.api.PostApiService
|
|
||||||
import dev.usbharu.hideout.service.api.UserApiService
|
|
||||||
import dev.usbharu.hideout.service.api.UserAuthApiService
|
|
||||||
import dev.usbharu.hideout.service.api.WebFingerApiService
|
|
||||||
import dev.usbharu.hideout.service.auth.HttpSignatureVerifyService
|
|
||||||
import dev.usbharu.hideout.service.core.Transaction
|
|
||||||
import dev.usbharu.hideout.service.user.UserService
|
|
||||||
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,
|
|
||||||
apService: APService,
|
|
||||||
userService: UserService,
|
|
||||||
apUserService: APUserService,
|
|
||||||
postService: PostApiService,
|
|
||||||
userApiService: UserApiService,
|
|
||||||
userQueryService: UserQueryService,
|
|
||||||
followerQueryService: FollowerQueryService,
|
|
||||||
userAuthApiService: UserAuthApiService,
|
|
||||||
webFingerApiService: WebFingerApiService,
|
|
||||||
transaction: Transaction
|
|
||||||
) {
|
|
||||||
install(AutoHeadResponse)
|
|
||||||
routing {
|
|
||||||
inbox(httpSignatureVerifyService, apService)
|
|
||||||
outbox()
|
|
||||||
usersAP(apUserService, userQueryService, followerQueryService, transaction)
|
|
||||||
webfinger(webFingerApiService)
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
posts(postService)
|
|
||||||
users(userService, userApiService)
|
|
||||||
auth(userAuthApiService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
package dev.usbharu.hideout.plugins
|
|
||||||
|
|
||||||
import com.auth0.jwk.JwkProvider
|
|
||||||
import dev.usbharu.hideout.config.Config
|
|
||||||
import dev.usbharu.hideout.service.core.MetaService
|
|
||||||
import dev.usbharu.hideout.util.JsonWebKeyUtil
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
import io.ktor.server.auth.*
|
|
||||||
import io.ktor.server.auth.jwt.*
|
|
||||||
import io.ktor.server.response.*
|
|
||||||
import io.ktor.server.routing.*
|
|
||||||
|
|
||||||
const val TOKEN_AUTH = "jwt-auth"
|
|
||||||
|
|
||||||
@Deprecated("Ktor is deprecated")
|
|
||||||
@Suppress("MagicNumber")
|
|
||||||
fun Application.configureSecurity(
|
|
||||||
jwkProvider: JwkProvider,
|
|
||||||
metaService: MetaService
|
|
||||||
) {
|
|
||||||
val issuer = Config.configData.url
|
|
||||||
install(Authentication) {
|
|
||||||
jwt(TOKEN_AUTH) {
|
|
||||||
verifier(jwkProvider, issuer) {
|
|
||||||
acceptLeeway(3)
|
|
||||||
}
|
|
||||||
validate { jwtCredential ->
|
|
||||||
val uid = jwtCredential.payload.getClaim("uid")
|
|
||||||
if (uid.isMissing) {
|
|
||||||
return@validate null
|
|
||||||
}
|
|
||||||
if (uid.asLong() == null) {
|
|
||||||
return@validate null
|
|
||||||
}
|
|
||||||
return@validate JWTPrincipal(jwtCredential.payload)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
routing {
|
|
||||||
get("/.well-known/jwks.json") {
|
|
||||||
//language=JSON
|
|
||||||
val jwt = metaService.getJwtMeta()
|
|
||||||
call.respondText(
|
|
||||||
contentType = ContentType.Application.Json,
|
|
||||||
text = JsonWebKeyUtil.publicKeyToJwk(jwt.publicKey, jwt.kid.toString())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
package dev.usbharu.hideout.plugins
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude
|
|
||||||
import com.fasterxml.jackson.annotation.JsonSetter
|
|
||||||
import com.fasterxml.jackson.annotation.Nulls
|
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
|
||||||
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 {
|
|
||||||
enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
|
|
||||||
setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
|
|
||||||
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
|
||||||
configOverride(List::class.java).setSetterInfo(JsonSetter.Value.forContentNulls(Nulls.AS_EMPTY))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
package dev.usbharu.hideout.plugins
|
|
||||||
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
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("/") {
|
|
||||||
call.respondText(
|
|
||||||
String.javaClass.classLoader.getResourceAsStream("static/index.html").readAllBytes().decodeToString(),
|
|
||||||
contentType = ContentType.Text.Html
|
|
||||||
)
|
|
||||||
}
|
|
||||||
static("/") {
|
|
||||||
resources("static")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
package dev.usbharu.hideout.plugins
|
|
||||||
|
|
||||||
import dev.usbharu.hideout.exception.InvalidUsernameOrPasswordException
|
|
||||||
import io.ktor.http.*
|
|
||||||
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<IllegalArgumentException> { call, cause ->
|
|
||||||
call.respondText(text = "400: $cause", status = HttpStatusCode.BadRequest)
|
|
||||||
call.application.log.warn("Bad Request", cause)
|
|
||||||
}
|
|
||||||
exception<InvalidUsernameOrPasswordException> { call, _ ->
|
|
||||||
call.respond(HttpStatusCode.Unauthorized)
|
|
||||||
}
|
|
||||||
exception<Throwable> { call, cause ->
|
|
||||||
call.respondText(text = "500: ${cause.stackTraceToString()}", status = HttpStatusCode.InternalServerError)
|
|
||||||
call.application.log.error("Internal Server Error", cause)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -175,7 +175,6 @@ class RegisteredClientRepositoryImpl(private val database: Database) : Registere
|
||||||
|
|
||||||
return builder.build()
|
return builder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql
|
// org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
package dev.usbharu.hideout.routing
|
|
||||||
|
|
||||||
import dev.usbharu.hideout.service.api.UserApiService
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
import io.ktor.server.auth.*
|
|
||||||
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") {
|
|
||||||
val principal = call.principal<UserIdPrincipal>()
|
|
||||||
if (principal != null) {
|
|
||||||
call.respondRedirect("/users/${principal.name}")
|
|
||||||
}
|
|
||||||
call.respondText(ContentType.Text.Html) {
|
|
||||||
//language=HTML
|
|
||||||
"""
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<form method='post' action=''>
|
|
||||||
<input type='text' name='username' value=''>
|
|
||||||
<input type='password' name='password'>
|
|
||||||
<input type="submit">
|
|
||||||
</form>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
""".trimIndent()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
post("/register") {
|
|
||||||
val parameters = call.receiveParameters()
|
|
||||||
val password = parameters["password"] ?: return@post call.respondRedirect("/register")
|
|
||||||
val username = parameters["username"] ?: return@post call.respondRedirect("/register")
|
|
||||||
userApiService.createUser(username, password)
|
|
||||||
call.respondRedirect("/users/$username")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
package dev.usbharu.hideout.routing.activitypub
|
|
||||||
|
|
||||||
import dev.usbharu.hideout.config.Config
|
|
||||||
import dev.usbharu.hideout.domain.model.ActivityPubObjectResponse
|
|
||||||
import dev.usbharu.hideout.domain.model.ActivityPubStringResponse
|
|
||||||
import dev.usbharu.hideout.exception.HttpSignatureVerifyException
|
|
||||||
import dev.usbharu.hideout.service.auth.HttpSignatureVerifyService
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
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
|
|
||||||
) {
|
|
||||||
route("/inbox") {
|
|
||||||
get {
|
|
||||||
call.respond(HttpStatusCode.MethodNotAllowed)
|
|
||||||
}
|
|
||||||
post {
|
|
||||||
if (httpSignatureVerifyService.verify(call.request.headers).not()) {
|
|
||||||
throw HttpSignatureVerifyException()
|
|
||||||
}
|
|
||||||
val json = call.receiveText()
|
|
||||||
call.application.log.trace("Received: $json")
|
|
||||||
val activityTypes = apService.parseActivity(json)
|
|
||||||
call.application.log.debug("ActivityTypes: ${activityTypes.name}")
|
|
||||||
val response = apService.processActivity(json, activityTypes)
|
|
||||||
when (response) {
|
|
||||||
is ActivityPubObjectResponse -> call.respond(
|
|
||||||
response.httpStatusCode,
|
|
||||||
Config.configData.objectMapper.writeValueAsString(
|
|
||||||
response.message.apply {
|
|
||||||
context =
|
|
||||||
listOf("https://www.w3.org/ns/activitystreams")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
is ActivityPubStringResponse -> call.respond(response.httpStatusCode, response.message)
|
|
||||||
null -> call.respond(HttpStatusCode.NotImplemented)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
route("/users/{name}/inbox") {
|
|
||||||
get {
|
|
||||||
call.respond(HttpStatusCode.MethodNotAllowed)
|
|
||||||
}
|
|
||||||
post {
|
|
||||||
if (httpSignatureVerifyService.verify(call.request.headers).not()) {
|
|
||||||
throw HttpSignatureVerifyException()
|
|
||||||
}
|
|
||||||
val json = call.receiveText()
|
|
||||||
call.application.log.trace("Received: $json")
|
|
||||||
val activityTypes = apService.parseActivity(json)
|
|
||||||
call.application.log.debug("ActivityTypes: ${activityTypes.name}")
|
|
||||||
val response = apService.processActivity(json, activityTypes)
|
|
||||||
when (response) {
|
|
||||||
is ActivityPubObjectResponse -> call.respond(
|
|
||||||
response.httpStatusCode,
|
|
||||||
Config.configData.objectMapper.writeValueAsString(
|
|
||||||
response.message.apply {
|
|
||||||
context =
|
|
||||||
listOf("https://www.w3.org/ns/activitystreams")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
is ActivityPubStringResponse -> call.respond(response.httpStatusCode, response.message)
|
|
||||||
null -> call.respond(HttpStatusCode.NotImplemented)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
package dev.usbharu.hideout.routing.activitypub
|
|
||||||
|
|
||||||
import io.ktor.http.*
|
|
||||||
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 {
|
|
||||||
call.respond(HttpStatusCode.NotImplemented)
|
|
||||||
}
|
|
||||||
post {
|
|
||||||
call.respond(HttpStatusCode.NotImplemented)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
route("/users/{name}/outbox") {
|
|
||||||
get {
|
|
||||||
call.respond(HttpStatusCode.NotImplemented)
|
|
||||||
}
|
|
||||||
post {
|
|
||||||
call.respond(HttpStatusCode.NotImplemented)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
package dev.usbharu.hideout.routing.activitypub
|
|
||||||
|
|
||||||
import dev.usbharu.hideout.config.Config
|
|
||||||
import dev.usbharu.hideout.exception.ParameterNotExistException
|
|
||||||
import dev.usbharu.hideout.plugins.respondAp
|
|
||||||
import dev.usbharu.hideout.query.FollowerQueryService
|
|
||||||
import dev.usbharu.hideout.query.UserQueryService
|
|
||||||
import dev.usbharu.hideout.service.ap.APUserService
|
|
||||||
import dev.usbharu.hideout.service.core.Transaction
|
|
||||||
import dev.usbharu.hideout.util.HttpUtil.Activity
|
|
||||||
import dev.usbharu.hideout.util.HttpUtil.JsonLd
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
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,
|
|
||||||
followerQueryService: FollowerQueryService,
|
|
||||||
transaction: Transaction
|
|
||||||
) {
|
|
||||||
route("/users/{name}") {
|
|
||||||
createChild(ContentTypeRouteSelector(ContentType.Application.Activity, ContentType.Application.JsonLd)).handle {
|
|
||||||
call.application.log.debug("Signature: ${call.request.header("Signature")}")
|
|
||||||
call.application.log.debug("Authorization: ${call.request.header("Authorization")}")
|
|
||||||
val name =
|
|
||||||
call.parameters["name"] ?: throw ParameterNotExistException("Parameter(name='name') does not exist.")
|
|
||||||
val person = apUserService.getPersonByName(name)
|
|
||||||
return@handle call.respondAp(person, HttpStatusCode.OK)
|
|
||||||
}
|
|
||||||
get {
|
|
||||||
// TODO: 暫定処置なので治す
|
|
||||||
transaction.transaction {
|
|
||||||
val userEntity = userQueryService.findByNameAndDomain(
|
|
||||||
call.parameters["name"]
|
|
||||||
?: throw ParameterNotExistException("Parameter(name='name') does not exist."),
|
|
||||||
Config.configData.domain
|
|
||||||
)
|
|
||||||
val personByName = apUserService.getPersonByName(userEntity.name)
|
|
||||||
call.respondText(
|
|
||||||
userEntity.toString() + "\n" + followerQueryService.findFollowersById(userEntity.id) +
|
|
||||||
"\n" + Config.configData.objectMapper.writeValueAsString(
|
|
||||||
personByName
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@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()}")
|
|
||||||
val requestContentType = context.call.request.accept() ?: return RouteSelectorEvaluation.FailedParameter
|
|
||||||
return if (requestContentType.split(",").any { contentType.any { contentType -> contentType.match(it) } }) {
|
|
||||||
RouteSelectorEvaluation.Constant
|
|
||||||
} else {
|
|
||||||
RouteSelectorEvaluation.FailedParameter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
package dev.usbharu.hideout.routing.api.internal.v1
|
|
||||||
|
|
||||||
import dev.usbharu.hideout.domain.model.hideout.form.RefreshToken
|
|
||||||
import dev.usbharu.hideout.domain.model.hideout.form.UserLogin
|
|
||||||
import dev.usbharu.hideout.plugins.TOKEN_AUTH
|
|
||||||
import dev.usbharu.hideout.service.api.UserAuthApiService
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
import io.ktor.server.auth.*
|
|
||||||
import io.ktor.server.auth.jwt.*
|
|
||||||
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<UserLogin>()
|
|
||||||
return@post call.respond(userAuthApiService.login(loginUser.username, loginUser.password))
|
|
||||||
}
|
|
||||||
|
|
||||||
post("/refresh-token") {
|
|
||||||
val refreshToken = call.receive<RefreshToken>()
|
|
||||||
return@post call.respond(userAuthApiService.refreshToken(refreshToken))
|
|
||||||
}
|
|
||||||
authenticate(TOKEN_AUTH) {
|
|
||||||
get("/auth-check") {
|
|
||||||
val principal = call.principal<JWTPrincipal>() ?: throw IllegalStateException("no principal")
|
|
||||||
val username = principal.payload.getClaim("uid")
|
|
||||||
call.respondText("Hello $username")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,102 +0,0 @@
|
||||||
package dev.usbharu.hideout.routing.api.internal.v1
|
|
||||||
|
|
||||||
import dev.usbharu.hideout.domain.model.hideout.form.Post
|
|
||||||
import dev.usbharu.hideout.domain.model.hideout.form.Reaction
|
|
||||||
import dev.usbharu.hideout.exception.ParameterNotExistException
|
|
||||||
import dev.usbharu.hideout.plugins.TOKEN_AUTH
|
|
||||||
import dev.usbharu.hideout.service.api.PostApiService
|
|
||||||
import dev.usbharu.hideout.util.InstantParseUtil
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
import io.ktor.server.auth.*
|
|
||||||
import io.ktor.server.auth.jwt.*
|
|
||||||
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") {
|
|
||||||
authenticate(TOKEN_AUTH) {
|
|
||||||
post {
|
|
||||||
val principal = call.principal<JWTPrincipal>() ?: throw IllegalStateException("no principal")
|
|
||||||
val userId = principal.payload.getClaim("uid").asLong()
|
|
||||||
|
|
||||||
val receive = call.receive<Post>()
|
|
||||||
val create = postApiService.createPost(receive, userId)
|
|
||||||
call.response.header("Location", create.url)
|
|
||||||
call.respond(HttpStatusCode.OK)
|
|
||||||
}
|
|
||||||
route("/{id}/reactions") {
|
|
||||||
get {
|
|
||||||
val principal = call.principal<JWTPrincipal>() ?: throw IllegalStateException("no principal")
|
|
||||||
val userId = principal.payload.getClaim("uid").asLong()
|
|
||||||
val postId = (
|
|
||||||
call.parameters["id"]?.toLong()
|
|
||||||
?: throw ParameterNotExistException("Parameter(id='postsId') does not exist.")
|
|
||||||
)
|
|
||||||
call.respond(postApiService.getReactionByPostId(postId, userId))
|
|
||||||
}
|
|
||||||
post {
|
|
||||||
val jwtPrincipal = call.principal<JWTPrincipal>() ?: throw IllegalStateException("no principal")
|
|
||||||
val userId = jwtPrincipal.payload.getClaim("uid").asLong()
|
|
||||||
val postId = call.parameters["id"]?.toLong()
|
|
||||||
?: throw ParameterNotExistException("Parameter(id='postsId') does not exist.")
|
|
||||||
val reaction = try {
|
|
||||||
call.receive<Reaction>()
|
|
||||||
} catch (_: ContentTransformationException) {
|
|
||||||
Reaction(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
postApiService.appendReaction(reaction.reaction ?: "❤", userId, postId)
|
|
||||||
call.respond(HttpStatusCode.NoContent)
|
|
||||||
}
|
|
||||||
delete {
|
|
||||||
val jwtPrincipal = call.principal<JWTPrincipal>() ?: throw IllegalStateException("no principal")
|
|
||||||
val userId = jwtPrincipal.payload.getClaim("uid").asLong()
|
|
||||||
val postId = call.parameters["id"]?.toLong()
|
|
||||||
?: throw ParameterNotExistException("Parameter(id='postsId') does not exist.")
|
|
||||||
postApiService.removeReaction(userId, postId)
|
|
||||||
call.respond(HttpStatusCode.NoContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
authenticate(TOKEN_AUTH, optional = true) {
|
|
||||||
get {
|
|
||||||
val userId = call.principal<JWTPrincipal>()?.payload?.getClaim("uid")?.asLong()
|
|
||||||
val since = InstantParseUtil.parse(call.request.queryParameters["since"])
|
|
||||||
val until = InstantParseUtil.parse(call.request.queryParameters["until"])
|
|
||||||
val minId = call.request.queryParameters["minId"]?.toLong()
|
|
||||||
val maxId = call.request.queryParameters["maxId"]?.toLong()
|
|
||||||
val limit = call.request.queryParameters["limit"]?.toInt()
|
|
||||||
call.respond(HttpStatusCode.OK, postApiService.getAll(since, until, minId, maxId, limit, userId))
|
|
||||||
}
|
|
||||||
get("/{id}") {
|
|
||||||
val userId = call.principal<JWTPrincipal>()?.payload?.getClaim("uid")?.asLong()
|
|
||||||
val id = call.parameters["id"]?.toLong()
|
|
||||||
?: throw ParameterNotExistException("Parameter(id='postsId') does not exist.")
|
|
||||||
val post = postApiService.getById(id, userId)
|
|
||||||
call.respond(post)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
route("/users/{name}/posts") {
|
|
||||||
authenticate(TOKEN_AUTH, optional = true) {
|
|
||||||
get {
|
|
||||||
val userId = call.principal<JWTPrincipal>()?.payload?.getClaim("uid")?.asLong()
|
|
||||||
val targetUserName = call.parameters["name"]
|
|
||||||
?: throw ParameterNotExistException("Parameter(name='userName@domain') does not exist.")
|
|
||||||
val posts = postApiService.getByUser(targetUserName, userId = userId)
|
|
||||||
call.respond(posts)
|
|
||||||
}
|
|
||||||
get("/{id}") {
|
|
||||||
val userId = call.principal<JWTPrincipal>()?.payload?.getClaim("uid")?.asLong()
|
|
||||||
val id = call.parameters["id"]?.toLong()
|
|
||||||
?: throw ParameterNotExistException("Parameter(name='postsId' does not exist.")
|
|
||||||
val post = postApiService.getById(id, userId)
|
|
||||||
call.respond(post)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,107 +0,0 @@
|
||||||
package dev.usbharu.hideout.routing.api.internal.v1
|
|
||||||
|
|
||||||
import dev.usbharu.hideout.config.Config
|
|
||||||
import dev.usbharu.hideout.domain.model.hideout.dto.UserCreateDto
|
|
||||||
import dev.usbharu.hideout.domain.model.hideout.form.UserCreate
|
|
||||||
import dev.usbharu.hideout.exception.ParameterNotExistException
|
|
||||||
import dev.usbharu.hideout.plugins.TOKEN_AUTH
|
|
||||||
import dev.usbharu.hideout.service.api.UserApiService
|
|
||||||
import dev.usbharu.hideout.service.user.UserService
|
|
||||||
import dev.usbharu.hideout.util.AcctUtil
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
import io.ktor.server.auth.*
|
|
||||||
import io.ktor.server.auth.jwt.*
|
|
||||||
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") {
|
|
||||||
get {
|
|
||||||
call.respond(userApiService.findAll())
|
|
||||||
}
|
|
||||||
post {
|
|
||||||
val userCreate = call.receive<UserCreate>()
|
|
||||||
if (userService.usernameAlreadyUse(userCreate.username)) {
|
|
||||||
return@post call.respond(HttpStatusCode.BadRequest)
|
|
||||||
}
|
|
||||||
val user = userService.createLocalUser(
|
|
||||||
UserCreateDto(
|
|
||||||
userCreate.username,
|
|
||||||
userCreate.username,
|
|
||||||
"",
|
|
||||||
userCreate.password
|
|
||||||
)
|
|
||||||
)
|
|
||||||
call.response.header("Location", "${Config.configData.url}/api/internal/v1/users/${user.name}")
|
|
||||||
call.respond(HttpStatusCode.Created)
|
|
||||||
}
|
|
||||||
route("/{name}") {
|
|
||||||
authenticate(TOKEN_AUTH, optional = true) {
|
|
||||||
get {
|
|
||||||
val userParameter = (
|
|
||||||
call.parameters["name"]
|
|
||||||
?: throw ParameterNotExistException(
|
|
||||||
"Parameter(name='userName@domain') does not exist."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (userParameter.toLongOrNull() != null) {
|
|
||||||
return@get call.respond(userApiService.findById(userParameter.toLong()))
|
|
||||||
} else {
|
|
||||||
val acct = AcctUtil.parse(userParameter)
|
|
||||||
return@get call.respond(userApiService.findByAcct(acct))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
route("/followers") {
|
|
||||||
get {
|
|
||||||
val userParameter = call.parameters["name"]
|
|
||||||
?: throw ParameterNotExistException("Parameter(name='userName@domain') does not exist.")
|
|
||||||
if (userParameter.toLongOrNull() != null) {
|
|
||||||
return@get call.respond(userApiService.findFollowers(userParameter.toLong()))
|
|
||||||
}
|
|
||||||
val acct = AcctUtil.parse(userParameter)
|
|
||||||
return@get call.respond(userApiService.findFollowersByAcct(acct))
|
|
||||||
}
|
|
||||||
authenticate(TOKEN_AUTH) {
|
|
||||||
post {
|
|
||||||
val userId = call.principal<JWTPrincipal>()?.payload?.getClaim("uid")?.asLong()
|
|
||||||
?: throw IllegalStateException("no principal")
|
|
||||||
val userParameter = call.parameters["name"]
|
|
||||||
?: throw ParameterNotExistException("Parameter(name='userName@domain') does not exist.")
|
|
||||||
if (if (userParameter.toLongOrNull() != null) {
|
|
||||||
userApiService.follow(userParameter.toLong(), userId)
|
|
||||||
} else {
|
|
||||||
val parse = AcctUtil.parse(userParameter)
|
|
||||||
userApiService.follow(parse, userId)
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
call.respond(HttpStatusCode.OK)
|
|
||||||
} else {
|
|
||||||
call.respond(HttpStatusCode.Accepted)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
route("/following") {
|
|
||||||
get {
|
|
||||||
val userParameter = (
|
|
||||||
call.parameters["name"]
|
|
||||||
?: throw ParameterNotExistException(
|
|
||||||
"Parameter(name='userName@domain') does not exist."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (userParameter.toLongOrNull() != null) {
|
|
||||||
return@get call.respond(userApiService.findFollowings(userParameter.toLong()))
|
|
||||||
}
|
|
||||||
val acct = AcctUtil.parse(userParameter)
|
|
||||||
return@get call.respond(userApiService.findFollowingsByAcct(acct))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
package dev.usbharu.hideout.routing.api.mastodon.v1
|
|
||||||
|
|
||||||
// @Suppress("UnusedPrivateMember")
|
|
||||||
// fun Route.statuses(postService: IPostService) {
|
|
||||||
// // route("/statuses") {
|
|
||||||
// // post {
|
|
||||||
// // val status: StatusForPost = call.receive()
|
|
||||||
// // val post = dev.usbharu.hideout.domain.model.hideout.form.Post(
|
|
||||||
// // userId = status.userId,
|
|
||||||
// // createdAt = System.currentTimeMillis(),
|
|
||||||
// // text = status.status,
|
|
||||||
// // visibility = 1
|
|
||||||
// // )
|
|
||||||
// // postService.create(post)
|
|
||||||
// // call.respond(status)
|
|
||||||
// // }
|
|
||||||
// // }
|
|
||||||
// }
|
|
|
@ -1,45 +0,0 @@
|
||||||
package dev.usbharu.hideout.routing.wellknown
|
|
||||||
|
|
||||||
import dev.usbharu.hideout.config.Config
|
|
||||||
import dev.usbharu.hideout.domain.model.wellknown.WebFinger
|
|
||||||
import dev.usbharu.hideout.exception.IllegalParameterException
|
|
||||||
import dev.usbharu.hideout.exception.ParameterNotExistException
|
|
||||||
import dev.usbharu.hideout.service.api.WebFingerApiService
|
|
||||||
import dev.usbharu.hideout.util.HttpUtil.Activity
|
|
||||||
import io.ktor.http.*
|
|
||||||
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 {
|
|
||||||
val acct = call.request.queryParameters["resource"]?.decodeURLPart()
|
|
||||||
?: throw ParameterNotExistException("Parameter(name='resource') does not exist.")
|
|
||||||
|
|
||||||
if (acct.startsWith("acct:").not()) {
|
|
||||||
throw IllegalParameterException("Parameter(name='resource') is not start with 'acct:'.")
|
|
||||||
}
|
|
||||||
|
|
||||||
val accountName = acct.substringBeforeLast("@")
|
|
||||||
.substringAfter("acct:")
|
|
||||||
.trimStart('@')
|
|
||||||
|
|
||||||
val userEntity = webFingerApiService.findByNameAndDomain(accountName, Config.configData.domain)
|
|
||||||
|
|
||||||
val webFinger = WebFinger(
|
|
||||||
subject = acct,
|
|
||||||
links = listOf(
|
|
||||||
WebFinger.Link(
|
|
||||||
rel = "self",
|
|
||||||
type = ContentType.Application.Activity.toString(),
|
|
||||||
href = "${Config.configData.url}/users/${userEntity.name}"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return@get call.respond(webFinger)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -13,7 +13,6 @@ interface AccountApiService {
|
||||||
suspend fun verifyCredentials(userid: Long): CredentialAccount
|
suspend fun verifyCredentials(userid: Long): CredentialAccount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class AccountApiServiceImpl(private val accountService: AccountService, private val transaction: Transaction) :
|
class AccountApiServiceImpl(private val accountService: AccountService, private val transaction: Transaction) :
|
||||||
AccountApiService {
|
AccountApiService {
|
||||||
|
@ -59,5 +58,4 @@ class AccountApiServiceImpl(private val accountService: AccountService, private
|
||||||
role = Role(0, "Admin", "", 32)
|
role = Role(0, "Admin", "", 32)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@ interface StatusesApiService {
|
||||||
suspend fun postStatus(statusesRequest: StatusesRequest, user: UserDetailsImpl): Status
|
suspend fun postStatus(statusesRequest: StatusesRequest, user: UserDetailsImpl): Status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class StatsesApiServiceImpl(
|
class StatsesApiServiceImpl(
|
||||||
private val postService: PostService,
|
private val postService: PostService,
|
||||||
|
@ -28,7 +27,6 @@ class StatsesApiServiceImpl(
|
||||||
) :
|
) :
|
||||||
StatusesApiService {
|
StatusesApiService {
|
||||||
override suspend fun postStatus(statusesRequest: StatusesRequest, user: UserDetailsImpl): Status {
|
override suspend fun postStatus(statusesRequest: StatusesRequest, user: UserDetailsImpl): Status {
|
||||||
|
|
||||||
val visibility = when (statusesRequest.visibility) {
|
val visibility = when (statusesRequest.visibility) {
|
||||||
StatusesRequest.Visibility.public -> Visibility.PUBLIC
|
StatusesRequest.Visibility.public -> Visibility.PUBLIC
|
||||||
StatusesRequest.Visibility.unlisted -> Visibility.UNLISTED
|
StatusesRequest.Visibility.unlisted -> Visibility.UNLISTED
|
||||||
|
@ -67,7 +65,6 @@ class StatsesApiServiceImpl(
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return Status(
|
return Status(
|
||||||
id = post.id.toString(),
|
id = post.id.toString(),
|
||||||
uri = post.apId,
|
uri = post.apId,
|
||||||
|
|
|
@ -26,11 +26,9 @@ class ExposedOAuth2AuthorizationConsentService(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun save(authorizationConsent: AuthorizationConsent?) = runBlocking {
|
override fun save(authorizationConsent: AuthorizationConsent?) = runBlocking {
|
||||||
requireNotNull(authorizationConsent)
|
requireNotNull(authorizationConsent)
|
||||||
transaction.transaction {
|
transaction.transaction {
|
||||||
|
|
||||||
val singleOrNull =
|
val singleOrNull =
|
||||||
OAuth2AuthorizationConsent.select {
|
OAuth2AuthorizationConsent.select {
|
||||||
OAuth2AuthorizationConsent.registeredClientId
|
OAuth2AuthorizationConsent.registeredClientId
|
||||||
|
@ -61,7 +59,6 @@ class ExposedOAuth2AuthorizationConsentService(
|
||||||
requireNotNull(registeredClientId)
|
requireNotNull(registeredClientId)
|
||||||
requireNotNull(principalName)
|
requireNotNull(principalName)
|
||||||
transaction.transaction {
|
transaction.transaction {
|
||||||
|
|
||||||
OAuth2AuthorizationConsent.select {
|
OAuth2AuthorizationConsent.select {
|
||||||
(OAuth2AuthorizationConsent.registeredClientId eq registeredClientId)
|
(OAuth2AuthorizationConsent.registeredClientId eq registeredClientId)
|
||||||
.and(OAuth2AuthorizationConsent.principalName eq principalName)
|
.and(OAuth2AuthorizationConsent.principalName eq principalName)
|
||||||
|
|
|
@ -151,8 +151,6 @@ class ExposedOAuth2AuthorizationService(
|
||||||
override fun findByToken(token: String?, tokenType: OAuth2TokenType?): OAuth2Authorization? = runBlocking {
|
override fun findByToken(token: String?, tokenType: OAuth2TokenType?): OAuth2Authorization? = runBlocking {
|
||||||
requireNotNull(token)
|
requireNotNull(token)
|
||||||
transaction.transaction {
|
transaction.transaction {
|
||||||
|
|
||||||
|
|
||||||
when (tokenType?.value) {
|
when (tokenType?.value) {
|
||||||
null -> {
|
null -> {
|
||||||
Authorization.select {
|
Authorization.select {
|
||||||
|
|
|
@ -7,12 +7,10 @@ import java.util.*
|
||||||
@Component
|
@Component
|
||||||
class SecureTokenGeneratorImpl : SecureTokenGenerator {
|
class SecureTokenGeneratorImpl : SecureTokenGenerator {
|
||||||
override fun generate(): String {
|
override fun generate(): String {
|
||||||
|
|
||||||
val byteArray = ByteArray(16)
|
val byteArray = ByteArray(16)
|
||||||
val secureRandom = SecureRandom()
|
val secureRandom = SecureRandom()
|
||||||
secureRandom.nextBytes(byteArray)
|
secureRandom.nextBytes(byteArray)
|
||||||
|
|
||||||
|
|
||||||
return Base64.getUrlEncoder().encodeToString(byteArray)
|
return Base64.getUrlEncoder().encodeToString(byteArray)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,573 +0,0 @@
|
||||||
package dev.usbharu.hideout.plugins
|
|
||||||
|
|
||||||
import com.auth0.jwk.Jwk
|
|
||||||
import com.auth0.jwk.JwkProvider
|
|
||||||
import com.auth0.jwt.JWT
|
|
||||||
import com.auth0.jwt.algorithms.Algorithm
|
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
|
||||||
import dev.usbharu.hideout.config.Config
|
|
||||||
import dev.usbharu.hideout.config.ConfigData
|
|
||||||
import dev.usbharu.hideout.domain.model.hideout.dto.JwtToken
|
|
||||||
import dev.usbharu.hideout.domain.model.hideout.entity.Jwt
|
|
||||||
import dev.usbharu.hideout.domain.model.hideout.entity.User
|
|
||||||
import dev.usbharu.hideout.domain.model.hideout.form.RefreshToken
|
|
||||||
import dev.usbharu.hideout.domain.model.hideout.form.UserLogin
|
|
||||||
import dev.usbharu.hideout.exception.InvalidRefreshTokenException
|
|
||||||
import dev.usbharu.hideout.exception.InvalidUsernameOrPasswordException
|
|
||||||
import dev.usbharu.hideout.query.UserQueryService
|
|
||||||
import dev.usbharu.hideout.routing.api.internal.v1.auth
|
|
||||||
import dev.usbharu.hideout.service.api.UserAuthApiService
|
|
||||||
import dev.usbharu.hideout.service.auth.JwtService
|
|
||||||
import dev.usbharu.hideout.service.core.MetaService
|
|
||||||
import dev.usbharu.hideout.service.user.UserAuthService
|
|
||||||
import dev.usbharu.hideout.util.Base64Util
|
|
||||||
import dev.usbharu.hideout.util.JsonWebKeyUtil
|
|
||||||
import io.ktor.client.request.*
|
|
||||||
import io.ktor.client.statement.*
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.server.config.*
|
|
||||||
import io.ktor.server.routing.*
|
|
||||||
import io.ktor.server.testing.*
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.mockito.ArgumentMatchers.anyString
|
|
||||||
import org.mockito.kotlin.*
|
|
||||||
import java.security.KeyPairGenerator
|
|
||||||
import java.security.interfaces.RSAPrivateKey
|
|
||||||
import java.security.interfaces.RSAPublicKey
|
|
||||||
import java.time.Instant
|
|
||||||
import java.time.temporal.ChronoUnit
|
|
||||||
import java.util.*
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
|
|
||||||
class SecurityKtTest {
|
|
||||||
@Test
|
|
||||||
fun `login ログイン出来るか`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
Config.configData = ConfigData(url = "http://example.com", objectMapper = jacksonObjectMapper())
|
|
||||||
val jwtToken = JwtToken("Token", "RefreshToken")
|
|
||||||
val userAuthService = mock<UserAuthApiService> {
|
|
||||||
onBlocking { login(eq("testUser"), eq("password")) } doReturn jwtToken
|
|
||||||
}
|
|
||||||
val metaService = mock<MetaService>()
|
|
||||||
val userQueryService = mock<UserQueryService> {
|
|
||||||
onBlocking { findByNameAndDomain(eq("testUser"), eq("example.com")) } doReturn User.of(
|
|
||||||
id = 1L,
|
|
||||||
name = "testUser",
|
|
||||||
domain = "example.com",
|
|
||||||
screenName = "test",
|
|
||||||
description = "",
|
|
||||||
password = "hashedPassword",
|
|
||||||
inbox = "https://example.com/inbox",
|
|
||||||
outbox = "https://example.com/outbox",
|
|
||||||
url = "https://example.com/profile",
|
|
||||||
publicKey = "",
|
|
||||||
privateKey = "",
|
|
||||||
createdAt = Instant.now()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val jwkProvider = mock<JwkProvider>()
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(jwkProvider, metaService)
|
|
||||||
routing {
|
|
||||||
auth(userAuthService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.post("/login") {
|
|
||||||
contentType(ContentType.Application.Json)
|
|
||||||
setBody(Config.configData.objectMapper.writeValueAsString(UserLogin("testUser", "password")))
|
|
||||||
}.apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, call.response.status)
|
|
||||||
assertEquals(jwtToken, Config.configData.objectMapper.readValue(call.response.bodyAsText()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `login 存在しないユーザーのログインに失敗する`() {
|
|
||||||
testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
Config.configData = ConfigData(url = "http://example.com", objectMapper = jacksonObjectMapper())
|
|
||||||
mock<UserAuthService> {
|
|
||||||
onBlocking { verifyAccount(anyString(), anyString()) }.doReturn(false)
|
|
||||||
}
|
|
||||||
val metaService = mock<MetaService>()
|
|
||||||
mock<UserQueryService>()
|
|
||||||
mock<JwtService>()
|
|
||||||
val jwkProvider = mock<JwkProvider>()
|
|
||||||
val userAuthApiService = mock<UserAuthApiService> {
|
|
||||||
onBlocking { login(anyString(), anyString()) } doThrow InvalidUsernameOrPasswordException()
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureStatusPages()
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(jwkProvider, metaService)
|
|
||||||
routing {
|
|
||||||
auth(userAuthApiService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.post("/login") {
|
|
||||||
contentType(ContentType.Application.Json)
|
|
||||||
setBody(Config.configData.objectMapper.writeValueAsString(UserLogin("InvalidTtestUser", "password")))
|
|
||||||
}.apply {
|
|
||||||
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `login 不正なパスワードのログインに失敗する`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
Config.configData = ConfigData(url = "http://example.com", objectMapper = jacksonObjectMapper())
|
|
||||||
val metaService = mock<MetaService>()
|
|
||||||
val jwkProvider = mock<JwkProvider>()
|
|
||||||
val userAuthApiService = mock<UserAuthApiService> {
|
|
||||||
onBlocking { login(anyString(), eq("InvalidPassword")) } doThrow InvalidUsernameOrPasswordException()
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureStatusPages()
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(jwkProvider, metaService)
|
|
||||||
routing {
|
|
||||||
auth(userAuthApiService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.post("/login") {
|
|
||||||
contentType(ContentType.Application.Json)
|
|
||||||
setBody(Config.configData.objectMapper.writeValueAsString(UserLogin("TestUser", "InvalidPassword")))
|
|
||||||
}.apply {
|
|
||||||
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `auth-check Authorizedヘッダーが無いと401が帰ってくる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
Config.configData = ConfigData(url = "http://example.com", objectMapper = jacksonObjectMapper())
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
auth(mock())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.get("/auth-check").apply {
|
|
||||||
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `auth-check Authorizedヘッダーの形式が間違っていると401が帰ってくる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
Config.configData = ConfigData(url = "http://example.com", objectMapper = jacksonObjectMapper())
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
auth(mock())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.get("/auth-check") {
|
|
||||||
header("Authorization", "Digest dsfjjhogalkjdfmlhaog")
|
|
||||||
}.apply {
|
|
||||||
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `auth-check Authorizedヘッダーが空だと401が帰ってくる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
Config.configData = ConfigData(url = "http://example.com", objectMapper = jacksonObjectMapper())
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
auth(mock())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.get("/auth-check") {
|
|
||||||
header("Authorization", "")
|
|
||||||
}.apply {
|
|
||||||
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `auth-check AuthorizedヘッダーがBeararで空だと401が帰ってくる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
Config.configData = ConfigData(url = "http://example.com", objectMapper = jacksonObjectMapper())
|
|
||||||
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
auth(mock())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.get("/auth-check") {
|
|
||||||
header("Authorization", "Bearer ")
|
|
||||||
}.apply {
|
|
||||||
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `auth-check 正当なJWTだとアクセスできる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
|
|
||||||
keyPairGenerator.initialize(2048)
|
|
||||||
val keyPair = keyPairGenerator.generateKeyPair()
|
|
||||||
val rsaPublicKey = keyPair.public as RSAPublicKey
|
|
||||||
|
|
||||||
Config.configData = ConfigData(url = "https://localhost", objectMapper = jacksonObjectMapper())
|
|
||||||
|
|
||||||
val now = Instant.now()
|
|
||||||
val kid = UUID.randomUUID()
|
|
||||||
val token = JWT.create()
|
|
||||||
.withAudience("${Config.configData.url}/users/test")
|
|
||||||
.withIssuer(Config.configData.url)
|
|
||||||
.withKeyId(kid.toString())
|
|
||||||
.withClaim("uid", 123456L)
|
|
||||||
.withExpiresAt(now.plus(30, ChronoUnit.MINUTES))
|
|
||||||
.sign(Algorithm.RSA256(rsaPublicKey, keyPair.private as RSAPrivateKey))
|
|
||||||
val metaService = mock<MetaService> {
|
|
||||||
onBlocking { getJwtMeta() }.doReturn(
|
|
||||||
Jwt(
|
|
||||||
kid,
|
|
||||||
Base64Util.encode(keyPair.private.encoded),
|
|
||||||
Base64Util.encode(rsaPublicKey.encoded)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val readValue = Config.configData.objectMapper.readerFor(Map::class.java)
|
|
||||||
.readValue<MutableMap<String, Any>?>(
|
|
||||||
JsonWebKeyUtil.publicKeyToJwk(
|
|
||||||
rsaPublicKey,
|
|
||||||
kid.toString()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val jwkProvider = mock<JwkProvider> {
|
|
||||||
onBlocking { get(anyString()) }.doReturn(
|
|
||||||
Jwk.fromValues(
|
|
||||||
(readValue["keys"] as List<Map<String, Any>>)[0]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(jwkProvider, metaService)
|
|
||||||
routing {
|
|
||||||
auth(mock())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.get("/auth-check") {
|
|
||||||
header("Authorization", "Bearer $token")
|
|
||||||
}.apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, call.response.status)
|
|
||||||
assertEquals("Hello 123456", call.response.bodyAsText())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `auth-check 期限切れのトークンではアクセスできない`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
|
|
||||||
keyPairGenerator.initialize(2048)
|
|
||||||
val keyPair = keyPairGenerator.generateKeyPair()
|
|
||||||
val rsaPublicKey = keyPair.public as RSAPublicKey
|
|
||||||
|
|
||||||
Config.configData = ConfigData(url = "https://localhost", objectMapper = jacksonObjectMapper())
|
|
||||||
|
|
||||||
val now = Instant.now()
|
|
||||||
val kid = UUID.randomUUID()
|
|
||||||
val token = JWT.create()
|
|
||||||
.withAudience("${Config.configData.url}/users/test")
|
|
||||||
.withIssuer(Config.configData.url)
|
|
||||||
.withKeyId(kid.toString())
|
|
||||||
.withClaim("uid", 123345L)
|
|
||||||
.withExpiresAt(now.minus(30, ChronoUnit.MINUTES))
|
|
||||||
.sign(Algorithm.RSA256(rsaPublicKey, keyPair.private as RSAPrivateKey))
|
|
||||||
val metaService = mock<MetaService> {
|
|
||||||
onBlocking { getJwtMeta() }.doReturn(
|
|
||||||
Jwt(
|
|
||||||
kid,
|
|
||||||
Base64Util.encode(keyPair.private.encoded),
|
|
||||||
Base64Util.encode(rsaPublicKey.encoded)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val readValue = Config.configData.objectMapper.readerFor(Map::class.java)
|
|
||||||
.readValue<MutableMap<String, Any>?>(
|
|
||||||
JsonWebKeyUtil.publicKeyToJwk(
|
|
||||||
rsaPublicKey,
|
|
||||||
kid.toString()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val jwkProvider = mock<JwkProvider> {
|
|
||||||
onBlocking { get(anyString()) }.doReturn(
|
|
||||||
Jwk.fromValues(
|
|
||||||
(readValue["keys"] as List<Map<String, Any>>)[0]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(jwkProvider, metaService)
|
|
||||||
routing {
|
|
||||||
auth(mock())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.get("/auth-check") {
|
|
||||||
header("Authorization", "Bearer $token")
|
|
||||||
}.apply {
|
|
||||||
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `auth-check issuerが間違っているとアクセスできない`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
|
|
||||||
keyPairGenerator.initialize(2048)
|
|
||||||
val keyPair = keyPairGenerator.generateKeyPair()
|
|
||||||
val rsaPublicKey = keyPair.public as RSAPublicKey
|
|
||||||
|
|
||||||
Config.configData = ConfigData(url = "https://localhost", objectMapper = jacksonObjectMapper())
|
|
||||||
|
|
||||||
val now = Instant.now()
|
|
||||||
val kid = UUID.randomUUID()
|
|
||||||
val token = JWT.create()
|
|
||||||
.withAudience("${Config.configData.url}/users/test")
|
|
||||||
.withIssuer("https://example.com")
|
|
||||||
.withKeyId(kid.toString())
|
|
||||||
.withClaim("uid", 12345L)
|
|
||||||
.withExpiresAt(now.plus(30, ChronoUnit.MINUTES))
|
|
||||||
.sign(Algorithm.RSA256(rsaPublicKey, keyPair.private as RSAPrivateKey))
|
|
||||||
val metaService = mock<MetaService> {
|
|
||||||
onBlocking { getJwtMeta() }.doReturn(
|
|
||||||
Jwt(
|
|
||||||
kid,
|
|
||||||
Base64Util.encode(keyPair.private.encoded),
|
|
||||||
Base64Util.encode(rsaPublicKey.encoded)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val readValue = Config.configData.objectMapper.readerFor(Map::class.java)
|
|
||||||
.readValue<MutableMap<String, Any>?>(
|
|
||||||
JsonWebKeyUtil.publicKeyToJwk(
|
|
||||||
rsaPublicKey,
|
|
||||||
kid.toString()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val jwkProvider = mock<JwkProvider> {
|
|
||||||
onBlocking { get(anyString()) }.doReturn(
|
|
||||||
Jwk.fromValues(
|
|
||||||
(readValue["keys"] as List<Map<String, Any>>)[0]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(jwkProvider, metaService)
|
|
||||||
routing {
|
|
||||||
auth(mock())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.get("/auth-check") {
|
|
||||||
header("Authorization", "Bearer $token")
|
|
||||||
}.apply {
|
|
||||||
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `auth-check usernameが空だと失敗する`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
|
|
||||||
keyPairGenerator.initialize(2048)
|
|
||||||
val keyPair = keyPairGenerator.generateKeyPair()
|
|
||||||
val rsaPublicKey = keyPair.public as RSAPublicKey
|
|
||||||
|
|
||||||
Config.configData = ConfigData(url = "https://localhost", objectMapper = jacksonObjectMapper())
|
|
||||||
|
|
||||||
val now = Instant.now()
|
|
||||||
val kid = UUID.randomUUID()
|
|
||||||
val token = JWT.create()
|
|
||||||
.withAudience("${Config.configData.url}/users/test")
|
|
||||||
.withIssuer(Config.configData.url)
|
|
||||||
.withKeyId(kid.toString())
|
|
||||||
.withClaim("uid", null as Long?)
|
|
||||||
.withExpiresAt(now.plus(30, ChronoUnit.MINUTES))
|
|
||||||
.sign(Algorithm.RSA256(rsaPublicKey, keyPair.private as RSAPrivateKey))
|
|
||||||
val metaService = mock<MetaService> {
|
|
||||||
onBlocking { getJwtMeta() }.doReturn(
|
|
||||||
Jwt(
|
|
||||||
kid,
|
|
||||||
Base64Util.encode(keyPair.private.encoded),
|
|
||||||
Base64Util.encode(rsaPublicKey.encoded)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val readValue = Config.configData.objectMapper.readerFor(Map::class.java)
|
|
||||||
.readValue<MutableMap<String, Any>?>(
|
|
||||||
JsonWebKeyUtil.publicKeyToJwk(
|
|
||||||
rsaPublicKey,
|
|
||||||
kid.toString()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val jwkProvider = mock<JwkProvider> {
|
|
||||||
onBlocking { get(anyString()) }.doReturn(
|
|
||||||
Jwk.fromValues(
|
|
||||||
(readValue["keys"] as List<Map<String, Any>>)[0]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(jwkProvider, metaService)
|
|
||||||
routing {
|
|
||||||
auth(mock())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.get("/auth-check") {
|
|
||||||
header("Authorization", "Bearer $token")
|
|
||||||
}.apply {
|
|
||||||
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `auth-check usernameが存在しないと失敗する`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
|
|
||||||
keyPairGenerator.initialize(2048)
|
|
||||||
val keyPair = keyPairGenerator.generateKeyPair()
|
|
||||||
val rsaPublicKey = keyPair.public as RSAPublicKey
|
|
||||||
|
|
||||||
Config.configData = ConfigData(url = "https://localhost", objectMapper = jacksonObjectMapper())
|
|
||||||
|
|
||||||
val now = Instant.now()
|
|
||||||
val kid = UUID.randomUUID()
|
|
||||||
val token = JWT.create()
|
|
||||||
.withAudience("${Config.configData.url}/users/test")
|
|
||||||
.withIssuer(Config.configData.url)
|
|
||||||
.withKeyId(kid.toString())
|
|
||||||
.withExpiresAt(now.plus(30, ChronoUnit.MINUTES))
|
|
||||||
.sign(Algorithm.RSA256(rsaPublicKey, keyPair.private as RSAPrivateKey))
|
|
||||||
val metaService = mock<MetaService> {
|
|
||||||
onBlocking { getJwtMeta() }.doReturn(
|
|
||||||
Jwt(
|
|
||||||
kid,
|
|
||||||
Base64Util.encode(keyPair.private.encoded),
|
|
||||||
Base64Util.encode(rsaPublicKey.encoded)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val readValue = Config.configData.objectMapper.readerFor(Map::class.java)
|
|
||||||
.readValue<MutableMap<String, Any>?>(
|
|
||||||
JsonWebKeyUtil.publicKeyToJwk(
|
|
||||||
rsaPublicKey,
|
|
||||||
kid.toString()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val jwkProvider = mock<JwkProvider> {
|
|
||||||
onBlocking { get(anyString()) }.doReturn(
|
|
||||||
Jwk.fromValues(
|
|
||||||
(readValue["keys"] as List<Map<String, Any>>)[0]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(jwkProvider, metaService)
|
|
||||||
routing {
|
|
||||||
auth(mock())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.get("/auth-check") {
|
|
||||||
header("Authorization", "Bearer $token")
|
|
||||||
}.apply {
|
|
||||||
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `refresh-token リフレッシュトークンが正当だとトークンを発行する`() = testApplication {
|
|
||||||
Config.configData = ConfigData(url = "https://localhost", objectMapper = jacksonObjectMapper())
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val jwtService = mock<UserAuthApiService> {
|
|
||||||
onBlocking { refreshToken(any()) }.doReturn(JwtToken("token", "refreshToken2"))
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
auth(jwtService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.post("/refresh-token") {
|
|
||||||
header("Content-Type", "application/json")
|
|
||||||
setBody(Config.configData.objectMapper.writeValueAsString(RefreshToken("refreshToken")))
|
|
||||||
}.apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, call.response.status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `refresh-token リフレッシュトークンが不正だと失敗する`() = testApplication {
|
|
||||||
Config.configData = ConfigData(url = "https://localhost", objectMapper = jacksonObjectMapper())
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val jwtService = mock<UserAuthApiService> {
|
|
||||||
onBlocking { refreshToken(any()) } doThrow InvalidRefreshTokenException("Invalid Refresh Token")
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureStatusPages()
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
auth(jwtService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.post("/refresh-token") {
|
|
||||||
header("Content-Type", "application/json")
|
|
||||||
setBody(Config.configData.objectMapper.writeValueAsString(RefreshToken("InvalidRefreshToken")))
|
|
||||||
}.apply {
|
|
||||||
assertEquals(HttpStatusCode.BadRequest, call.response.status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,134 +0,0 @@
|
||||||
package dev.usbharu.hideout.routing.activitypub
|
|
||||||
|
|
||||||
import io.ktor.client.request.*
|
|
||||||
import io.ktor.client.statement.*
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
import io.ktor.server.config.*
|
|
||||||
import io.ktor.server.response.*
|
|
||||||
import io.ktor.server.routing.*
|
|
||||||
import io.ktor.server.testing.*
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
|
|
||||||
class ContentTypeRouteSelectorTest {
|
|
||||||
@Test
|
|
||||||
fun `Content-Typeが一つでマッチする`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
routing {
|
|
||||||
route("/test") {
|
|
||||||
createChild(ContentTypeRouteSelector(ContentType.Application.Json)).handle {
|
|
||||||
call.respondText("OK")
|
|
||||||
}
|
|
||||||
get {
|
|
||||||
call.respondText("NG")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.get("/test") {
|
|
||||||
accept(ContentType.Text.Html)
|
|
||||||
}.apply {
|
|
||||||
assertEquals("NG", bodyAsText())
|
|
||||||
}
|
|
||||||
client.get("/test") {
|
|
||||||
accept(ContentType.Application.Json)
|
|
||||||
}.apply {
|
|
||||||
assertEquals("OK", bodyAsText())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Content-Typeが一つのとき違うとマッチしない`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
routing {
|
|
||||||
route("/test") {
|
|
||||||
createChild(ContentTypeRouteSelector(ContentType.Application.Json)).handle {
|
|
||||||
call.respondText("OK")
|
|
||||||
}
|
|
||||||
get {
|
|
||||||
call.respondText("NG")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.get("/test") {
|
|
||||||
accept(ContentType.Text.Html)
|
|
||||||
}.apply {
|
|
||||||
assertEquals("NG", bodyAsText())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Content-Typeがからのときマッチしない`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
routing {
|
|
||||||
route("/test") {
|
|
||||||
createChild(ContentTypeRouteSelector()).handle {
|
|
||||||
call.respondText("OK")
|
|
||||||
}
|
|
||||||
get {
|
|
||||||
call.respondText("NG")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.get("/test") {
|
|
||||||
accept(ContentType.Text.Html)
|
|
||||||
}.apply {
|
|
||||||
assertEquals("NG", bodyAsText())
|
|
||||||
}
|
|
||||||
|
|
||||||
client.get("/test").apply {
|
|
||||||
assertEquals("NG", bodyAsText())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Content-Typeが複数指定されていてマッチする`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
routing {
|
|
||||||
route("/test") {
|
|
||||||
createChild(ContentTypeRouteSelector(ContentType.Application.Json, ContentType.Text.Html)).handle {
|
|
||||||
call.respondText("OK")
|
|
||||||
}
|
|
||||||
get {
|
|
||||||
call.respondText("NG")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.get("/test") {
|
|
||||||
accept(ContentType.Text.Html)
|
|
||||||
}.apply {
|
|
||||||
assertEquals("OK", bodyAsText())
|
|
||||||
}
|
|
||||||
|
|
||||||
client.get("/test") {
|
|
||||||
accept(ContentType.Application.Json)
|
|
||||||
}.apply {
|
|
||||||
assertEquals("OK", bodyAsText())
|
|
||||||
}
|
|
||||||
client.get("/test") {
|
|
||||||
accept(ContentType.Application.Xml)
|
|
||||||
}.apply {
|
|
||||||
assertEquals("NG", bodyAsText())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,104 +0,0 @@
|
||||||
package dev.usbharu.hideout.routing.activitypub
|
|
||||||
|
|
||||||
import dev.usbharu.hideout.exception.JsonParseException
|
|
||||||
import dev.usbharu.hideout.plugins.configureSerialization
|
|
||||||
import dev.usbharu.hideout.plugins.configureStatusPages
|
|
||||||
import dev.usbharu.hideout.service.ap.APService
|
|
||||||
import dev.usbharu.hideout.service.ap.APUserService
|
|
||||||
import dev.usbharu.hideout.service.auth.HttpSignatureVerifyService
|
|
||||||
import dev.usbharu.hideout.service.user.UserService
|
|
||||||
import io.ktor.client.request.*
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.server.config.*
|
|
||||||
import io.ktor.server.routing.*
|
|
||||||
import io.ktor.server.testing.*
|
|
||||||
import org.junit.jupiter.api.Assertions
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.mockito.kotlin.any
|
|
||||||
import org.mockito.kotlin.doReturn
|
|
||||||
import org.mockito.kotlin.doThrow
|
|
||||||
import org.mockito.kotlin.mock
|
|
||||||
|
|
||||||
class InboxRoutingKtTest {
|
|
||||||
@Test
|
|
||||||
fun `sharedInboxにGETしたら405が帰ってくる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
routing {
|
|
||||||
inbox(mock(), mock())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.get("/inbox").let {
|
|
||||||
Assertions.assertEquals(HttpStatusCode.MethodNotAllowed, it.status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `sharedInboxに空のリクエストボディでPOSTしたら400が帰ってくる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val httpSignatureVerifyService = mock<HttpSignatureVerifyService> {
|
|
||||||
on { verify(any()) } doReturn true
|
|
||||||
}
|
|
||||||
val apService = mock<APService> {
|
|
||||||
on { parseActivity(any()) } doThrow JsonParseException()
|
|
||||||
}
|
|
||||||
mock<UserService>()
|
|
||||||
mock<APUserService>()
|
|
||||||
application {
|
|
||||||
configureStatusPages()
|
|
||||||
configureSerialization()
|
|
||||||
routing {
|
|
||||||
inbox(httpSignatureVerifyService, apService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.post("/inbox").let {
|
|
||||||
Assertions.assertEquals(HttpStatusCode.BadRequest, it.status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `ユーザのinboxにGETしたら405が帰ってくる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
routing {
|
|
||||||
inbox(mock(), mock())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.get("/users/test/inbox").let {
|
|
||||||
Assertions.assertEquals(HttpStatusCode.MethodNotAllowed, it.status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `ユーザーのinboxに空のリクエストボディでPOSTしたら400が帰ってくる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val httpSignatureVerifyService = mock<HttpSignatureVerifyService> {
|
|
||||||
on { verify(any()) } doReturn true
|
|
||||||
}
|
|
||||||
val apService = mock<APService> {
|
|
||||||
on { parseActivity(any()) } doThrow JsonParseException()
|
|
||||||
}
|
|
||||||
mock<UserService>()
|
|
||||||
mock<APUserService>()
|
|
||||||
application {
|
|
||||||
configureStatusPages()
|
|
||||||
configureSerialization()
|
|
||||||
routing {
|
|
||||||
inbox(httpSignatureVerifyService, apService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.post("/users/test/inbox").let {
|
|
||||||
Assertions.assertEquals(HttpStatusCode.BadRequest, it.status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,202 +0,0 @@
|
||||||
package dev.usbharu.hideout.routing.activitypub
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude
|
|
||||||
import com.fasterxml.jackson.annotation.JsonSetter
|
|
||||||
import com.fasterxml.jackson.annotation.Nulls
|
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
|
||||||
import dev.usbharu.hideout.domain.model.ap.Image
|
|
||||||
import dev.usbharu.hideout.domain.model.ap.Key
|
|
||||||
import dev.usbharu.hideout.domain.model.ap.Person
|
|
||||||
import dev.usbharu.hideout.domain.model.hideout.entity.User
|
|
||||||
import dev.usbharu.hideout.plugins.configureSerialization
|
|
||||||
import dev.usbharu.hideout.query.UserQueryService
|
|
||||||
import dev.usbharu.hideout.service.ap.APUserService
|
|
||||||
import dev.usbharu.hideout.util.HttpUtil.Activity
|
|
||||||
import dev.usbharu.hideout.util.HttpUtil.JsonLd
|
|
||||||
import io.ktor.client.request.*
|
|
||||||
import io.ktor.client.statement.*
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.server.config.*
|
|
||||||
import io.ktor.server.routing.*
|
|
||||||
import io.ktor.server.testing.*
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.mockito.ArgumentMatchers.anyString
|
|
||||||
import org.mockito.kotlin.doReturn
|
|
||||||
import org.mockito.kotlin.eq
|
|
||||||
import org.mockito.kotlin.mock
|
|
||||||
import utils.TestTransaction
|
|
||||||
import java.time.Instant
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.test.assertTrue
|
|
||||||
|
|
||||||
class UsersAPTest {
|
|
||||||
|
|
||||||
@Test()
|
|
||||||
fun `ユーザのURLにAcceptヘッダーをActivityにしてアクセスしたときPersonが返ってくる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val person = Person(
|
|
||||||
type = emptyList(),
|
|
||||||
name = "test",
|
|
||||||
id = "http://example.com/users/test",
|
|
||||||
preferredUsername = "test",
|
|
||||||
summary = "test user",
|
|
||||||
inbox = "http://example.com/users/test/inbox",
|
|
||||||
outbox = "http://example.com/users/test/outbox",
|
|
||||||
url = "http://example.com/users/test",
|
|
||||||
icon = Image(
|
|
||||||
type = emptyList(),
|
|
||||||
name = "http://example.com/users/test/icon.png",
|
|
||||||
mediaType = "image/png",
|
|
||||||
url = "http://example.com/users/test/icon.png"
|
|
||||||
),
|
|
||||||
publicKey = Key(
|
|
||||||
type = emptyList(),
|
|
||||||
name = "Public Key",
|
|
||||||
id = "http://example.com/users/test#pubkey",
|
|
||||||
owner = "https://example.com/users/test",
|
|
||||||
publicKeyPem = "-----BEGIN PUBLIC KEY-----\n\n-----END PUBLIC KEY-----"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
person.context = listOf("https://www.w3.org/ns/activitystreams")
|
|
||||||
|
|
||||||
val apUserService = mock<APUserService> {
|
|
||||||
onBlocking { getPersonByName(anyString()) } doReturn person
|
|
||||||
}
|
|
||||||
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
routing {
|
|
||||||
usersAP(apUserService, mock(), mock(), TestTransaction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.get("/users/test") {
|
|
||||||
accept(ContentType.Application.Activity)
|
|
||||||
}.let {
|
|
||||||
val objectMapper = jacksonObjectMapper().enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
|
|
||||||
.setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
|
|
||||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
|
||||||
objectMapper.configOverride(List::class.java).setSetterInfo(
|
|
||||||
JsonSetter.Value.forValueNulls(
|
|
||||||
Nulls.AS_EMPTY
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val actual = it.bodyAsText()
|
|
||||||
val readValue = objectMapper.readValue<Person>(actual)
|
|
||||||
assertEquals(person, readValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Disabled
|
|
||||||
@Test()
|
|
||||||
fun `ユーザのURLにAcceptヘッダーをActivityとJson-LDにしてアクセスしたときPersonが返ってくる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val person = Person(
|
|
||||||
type = emptyList(),
|
|
||||||
name = "test",
|
|
||||||
id = "http://example.com/users/test",
|
|
||||||
preferredUsername = "test",
|
|
||||||
summary = "test user",
|
|
||||||
inbox = "http://example.com/users/test/inbox",
|
|
||||||
outbox = "http://example.com/users/test/outbox",
|
|
||||||
url = "http://example.com/users/test",
|
|
||||||
icon = Image(
|
|
||||||
type = emptyList(),
|
|
||||||
name = "http://example.com/users/test/icon.png",
|
|
||||||
mediaType = "image/png",
|
|
||||||
url = "http://example.com/users/test/icon.png"
|
|
||||||
),
|
|
||||||
publicKey = Key(
|
|
||||||
type = emptyList(),
|
|
||||||
name = "Public Key",
|
|
||||||
id = "http://example.com/users/test#pubkey",
|
|
||||||
owner = "https://example.com/users/test",
|
|
||||||
publicKeyPem = "-----BEGIN PUBLIC KEY-----\n\n-----END PUBLIC KEY-----"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
person.context = listOf("https://www.w3.org/ns/activitystreams")
|
|
||||||
|
|
||||||
val apUserService = mock<APUserService> {
|
|
||||||
onBlocking { getPersonByName(anyString()) } doReturn person
|
|
||||||
}
|
|
||||||
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
routing {
|
|
||||||
usersAP(apUserService, mock(), mock(), TestTransaction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.get("/users/test") {
|
|
||||||
accept(ContentType.Application.JsonLd)
|
|
||||||
accept(ContentType.Application.Activity)
|
|
||||||
}.let {
|
|
||||||
val objectMapper = jacksonObjectMapper().enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
|
|
||||||
.setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
|
|
||||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
|
||||||
objectMapper.configOverride(List::class.java).setSetterInfo(
|
|
||||||
JsonSetter.Value.forValueNulls(
|
|
||||||
Nulls.AS_EMPTY
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val actual = it.bodyAsText()
|
|
||||||
val readValue = objectMapper.readValue<Person>(actual)
|
|
||||||
assertEquals(person, readValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Disabled
|
|
||||||
@Test
|
|
||||||
fun contentType_Test() {
|
|
||||||
assertTrue(ContentType.Application.Activity.match("application/activity+json"))
|
|
||||||
val listOf = listOf(ContentType.Application.JsonLd, ContentType.Application.Activity)
|
|
||||||
assertTrue(
|
|
||||||
listOf.find { contentType ->
|
|
||||||
contentType.match("application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"")
|
|
||||||
}.let { it != null }
|
|
||||||
)
|
|
||||||
assertTrue(
|
|
||||||
ContentType.Application.JsonLd.match(
|
|
||||||
"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun ユーザーのURLにAcceptヘッダーをhtmlにしてアクセスしたときはただの文字を返す() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val userService = mock<UserQueryService> {
|
|
||||||
onBlocking { findByNameAndDomain(eq("test"), anyString()) } doReturn User.of(
|
|
||||||
1L,
|
|
||||||
"test",
|
|
||||||
"example.com",
|
|
||||||
"test",
|
|
||||||
"",
|
|
||||||
"hashedPassword",
|
|
||||||
"https://example.com/inbox",
|
|
||||||
"https://example.com/outbox",
|
|
||||||
"https://example.com",
|
|
||||||
"-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----",
|
|
||||||
"-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----",
|
|
||||||
Instant.now()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
routing {
|
|
||||||
usersAP(mock(), userService, mock(), TestTransaction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.get("/users/test") {
|
|
||||||
accept(ContentType.Text.Html)
|
|
||||||
}.let {
|
|
||||||
assertEquals(HttpStatusCode.OK, it.status)
|
|
||||||
assertTrue(it.contentType()?.match(ContentType.Text.Plain) == true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,734 +0,0 @@
|
||||||
package dev.usbharu.hideout.routing.api.internal.v1
|
|
||||||
|
|
||||||
import com.auth0.jwt.interfaces.Claim
|
|
||||||
import com.auth0.jwt.interfaces.Payload
|
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
|
||||||
import dev.usbharu.hideout.config.Config
|
|
||||||
import dev.usbharu.hideout.domain.model.hideout.dto.PostResponse
|
|
||||||
import dev.usbharu.hideout.domain.model.hideout.dto.UserResponse
|
|
||||||
import dev.usbharu.hideout.domain.model.hideout.entity.Visibility
|
|
||||||
import dev.usbharu.hideout.plugins.TOKEN_AUTH
|
|
||||||
import dev.usbharu.hideout.plugins.configureSecurity
|
|
||||||
import dev.usbharu.hideout.plugins.configureSerialization
|
|
||||||
import dev.usbharu.hideout.service.api.PostApiService
|
|
||||||
import io.ktor.client.request.*
|
|
||||||
import io.ktor.client.statement.*
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.server.auth.*
|
|
||||||
import io.ktor.server.auth.jwt.*
|
|
||||||
import io.ktor.server.config.*
|
|
||||||
import io.ktor.server.routing.*
|
|
||||||
import io.ktor.server.testing.*
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.mockito.kotlin.*
|
|
||||||
import utils.JsonObjectMapper
|
|
||||||
import java.time.Instant
|
|
||||||
import kotlin.test.assertContentEquals
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
|
|
||||||
class PostsTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun 認証情報無しでpostsにGETしたらPUBLICな投稿一覧が返ってくる() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val user = UserResponse(
|
|
||||||
id = "54321",
|
|
||||||
name = "user1",
|
|
||||||
domain = "example.com",
|
|
||||||
screenName = "user 1",
|
|
||||||
description = "Test user",
|
|
||||||
url = "https://example.com/users/54321",
|
|
||||||
createdAt = Instant.now().toEpochMilli()
|
|
||||||
)
|
|
||||||
val posts = listOf(
|
|
||||||
PostResponse(
|
|
||||||
id = "12345",
|
|
||||||
user = user,
|
|
||||||
text = "test1",
|
|
||||||
visibility = Visibility.PUBLIC,
|
|
||||||
createdAt = Instant.now().toEpochMilli(),
|
|
||||||
url = "https://example.com/posts/1"
|
|
||||||
),
|
|
||||||
PostResponse(
|
|
||||||
id = "123456",
|
|
||||||
user = user,
|
|
||||||
text = "test2",
|
|
||||||
visibility = Visibility.PUBLIC,
|
|
||||||
createdAt = Instant.now().toEpochMilli(),
|
|
||||||
url = "https://example.com/posts/2"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val postService = mock<PostApiService> {
|
|
||||||
onBlocking {
|
|
||||||
getAll(
|
|
||||||
since = anyOrNull(),
|
|
||||||
until = anyOrNull(),
|
|
||||||
minId = anyOrNull(),
|
|
||||||
maxId = anyOrNull(),
|
|
||||||
limit = anyOrNull(),
|
|
||||||
userId = isNull()
|
|
||||||
)
|
|
||||||
} doReturn posts
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
posts(postService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.get("/api/internal/v1/posts").apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, status)
|
|
||||||
assertContentEquals(posts, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun 認証情報ありでpostsにGETすると権限のある投稿が返ってくる() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val claim = mock<Claim> {
|
|
||||||
on { asLong() } doReturn 1234
|
|
||||||
}
|
|
||||||
val payload = mock<Payload> {
|
|
||||||
on { getClaim(eq("uid")) } doReturn claim
|
|
||||||
}
|
|
||||||
val user = UserResponse(
|
|
||||||
id = "54321",
|
|
||||||
name = "user1",
|
|
||||||
domain = "example.com",
|
|
||||||
screenName = "user 1",
|
|
||||||
description = "Test user",
|
|
||||||
url = "https://example.com/users/54321",
|
|
||||||
createdAt = Instant.now().toEpochMilli()
|
|
||||||
)
|
|
||||||
val posts = listOf(
|
|
||||||
PostResponse(
|
|
||||||
id = "12345",
|
|
||||||
user = user,
|
|
||||||
text = "test1",
|
|
||||||
visibility = Visibility.PUBLIC,
|
|
||||||
createdAt = Instant.now().toEpochMilli(),
|
|
||||||
url = "https://example.com/posts/1"
|
|
||||||
),
|
|
||||||
PostResponse(
|
|
||||||
id = "123456",
|
|
||||||
user = user,
|
|
||||||
text = "test2",
|
|
||||||
visibility = Visibility.PUBLIC,
|
|
||||||
createdAt = Instant.now().toEpochMilli(),
|
|
||||||
url = "https://example.com/posts/2"
|
|
||||||
),
|
|
||||||
PostResponse(
|
|
||||||
id = "1234567",
|
|
||||||
user = user,
|
|
||||||
text = "Followers only",
|
|
||||||
visibility = Visibility.FOLLOWERS,
|
|
||||||
createdAt = Instant.now().toEpochMilli(),
|
|
||||||
url = "https://example.com/posts/3"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
val postService = mock<PostApiService> {
|
|
||||||
onBlocking {
|
|
||||||
getAll(
|
|
||||||
since = anyOrNull(),
|
|
||||||
until = anyOrNull(),
|
|
||||||
minId = anyOrNull(),
|
|
||||||
maxId = anyOrNull(),
|
|
||||||
limit = anyOrNull(),
|
|
||||||
userId = isNotNull()
|
|
||||||
)
|
|
||||||
} doReturn posts
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
authentication {
|
|
||||||
bearer(TOKEN_AUTH) {
|
|
||||||
authenticate {
|
|
||||||
JWTPrincipal(payload)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
configureSerialization()
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
posts(postService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.get("/api/internal/v1/posts") {
|
|
||||||
header("Authorization", "Bearer asdkaf")
|
|
||||||
}.apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `posts id にGETしたらPUBLICな投稿を取得できる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val user = UserResponse(
|
|
||||||
id = "54321",
|
|
||||||
name = "user1",
|
|
||||||
domain = "example.com",
|
|
||||||
screenName = "user 1",
|
|
||||||
description = "Test user",
|
|
||||||
url = "https://example.com/users/54321",
|
|
||||||
createdAt = Instant.now().toEpochMilli()
|
|
||||||
)
|
|
||||||
val post = PostResponse(
|
|
||||||
id = "12345",
|
|
||||||
user = user,
|
|
||||||
text = "aaa",
|
|
||||||
visibility = Visibility.PUBLIC,
|
|
||||||
createdAt = Instant.now().toEpochMilli(),
|
|
||||||
url = "https://example.com/posts/1"
|
|
||||||
)
|
|
||||||
val postService = mock<PostApiService> {
|
|
||||||
onBlocking { getById(any(), anyOrNull()) } doReturn post
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
posts(postService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.get("/api/internal/v1/posts/1").apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, status)
|
|
||||||
assertEquals(post, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `認証情報ありでposts id にGETしたら権限のある投稿を取得できる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val post = PostResponse(
|
|
||||||
"12345",
|
|
||||||
UserResponse(
|
|
||||||
id = "54321",
|
|
||||||
name = "user1",
|
|
||||||
domain = "example.com",
|
|
||||||
screenName = "user 1",
|
|
||||||
description = "Test user",
|
|
||||||
url = "https://example.com/users/54321",
|
|
||||||
createdAt = Instant.now().toEpochMilli()
|
|
||||||
),
|
|
||||||
text = "aaa",
|
|
||||||
visibility = Visibility.FOLLOWERS,
|
|
||||||
createdAt = Instant.now().toEpochMilli(),
|
|
||||||
url = "https://example.com/posts/1"
|
|
||||||
)
|
|
||||||
val postService = mock<PostApiService> {
|
|
||||||
onBlocking { getById(any(), isNotNull()) } doReturn post
|
|
||||||
}
|
|
||||||
val claim = mock<Claim> {
|
|
||||||
on { asLong() } doReturn 1234
|
|
||||||
}
|
|
||||||
val payload = mock<Payload> {
|
|
||||||
on { getClaim(eq("uid")) } doReturn claim
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
authentication {
|
|
||||||
bearer(TOKEN_AUTH) {
|
|
||||||
authenticate {
|
|
||||||
JWTPrincipal(payload)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
posts(postService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.get("/api/internal/v1/posts/1") {
|
|
||||||
header("Authorization", "Bearer asdkaf")
|
|
||||||
}.apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, status)
|
|
||||||
assertEquals(post, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `posts-post postsにpostしたら投稿できる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val claim = mock<Claim> {
|
|
||||||
on { asLong() } doReturn 1234
|
|
||||||
}
|
|
||||||
val payload = mock<Payload> {
|
|
||||||
on { getClaim(eq("uid")) } doReturn claim
|
|
||||||
}
|
|
||||||
val postService = mock<PostApiService> {
|
|
||||||
onBlocking { createPost(any(), any()) } doAnswer {
|
|
||||||
val argument = it.getArgument<dev.usbharu.hideout.domain.model.hideout.form.Post>(0)
|
|
||||||
val userId = it.getArgument<Long>(1)
|
|
||||||
PostResponse(
|
|
||||||
id = "123",
|
|
||||||
user = UserResponse(
|
|
||||||
id = "54321",
|
|
||||||
name = "user1",
|
|
||||||
domain = "example.com",
|
|
||||||
screenName = "user 1",
|
|
||||||
description = "Test user",
|
|
||||||
url = "https://example.com/users/54321",
|
|
||||||
createdAt = Instant.now().toEpochMilli()
|
|
||||||
),
|
|
||||||
overview = null,
|
|
||||||
text = argument.text,
|
|
||||||
createdAt = Instant.now().toEpochMilli(),
|
|
||||||
visibility = Visibility.PUBLIC,
|
|
||||||
url = "https://example.com"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
authentication {
|
|
||||||
bearer(TOKEN_AUTH) {
|
|
||||||
authenticate {
|
|
||||||
println("aaaaaaaaaaaa")
|
|
||||||
JWTPrincipal(payload)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
posts(postService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
configureSerialization()
|
|
||||||
}
|
|
||||||
|
|
||||||
val post = dev.usbharu.hideout.domain.model.hideout.form.Post("test")
|
|
||||||
client.post("/api/internal/v1/posts") {
|
|
||||||
header("Authorization", "Bearer asdkaf")
|
|
||||||
contentType(ContentType.Application.Json)
|
|
||||||
setBody(Config.configData.objectMapper.writeValueAsString(post))
|
|
||||||
}.apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, status)
|
|
||||||
assertEquals("https://example.com", headers["Location"])
|
|
||||||
}
|
|
||||||
argumentCaptor<dev.usbharu.hideout.domain.model.hideout.form.Post> {
|
|
||||||
verify(postService).createPost(capture(), any())
|
|
||||||
assertEquals(dev.usbharu.hideout.domain.model.hideout.form.Post("test"), firstValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `users userId postsにGETしたらユーザーのPUBLICな投稿一覧を取得できる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val user = UserResponse(
|
|
||||||
id = "54321",
|
|
||||||
name = "user1",
|
|
||||||
domain = "example.com",
|
|
||||||
screenName = "user 1",
|
|
||||||
description = "Test user",
|
|
||||||
url = "https://example.com/users/54321",
|
|
||||||
createdAt = Instant.now().toEpochMilli()
|
|
||||||
)
|
|
||||||
val posts = listOf(
|
|
||||||
PostResponse(
|
|
||||||
id = "12345",
|
|
||||||
user = user,
|
|
||||||
text = "test1",
|
|
||||||
visibility = Visibility.PUBLIC,
|
|
||||||
createdAt = Instant.now().toEpochMilli(),
|
|
||||||
url = "https://example.com/posts/1"
|
|
||||||
),
|
|
||||||
PostResponse(
|
|
||||||
id = "123456",
|
|
||||||
user = user,
|
|
||||||
text = "test2",
|
|
||||||
visibility = Visibility.PUBLIC,
|
|
||||||
createdAt = Instant.now().toEpochMilli(),
|
|
||||||
url = "https://example.com/posts/2"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val postService = mock<PostApiService> {
|
|
||||||
onBlocking {
|
|
||||||
getByUser(
|
|
||||||
nameOrId = any(),
|
|
||||||
since = anyOrNull(),
|
|
||||||
until = anyOrNull(),
|
|
||||||
minId = anyOrNull(),
|
|
||||||
maxId = anyOrNull(),
|
|
||||||
limit = anyOrNull(),
|
|
||||||
userId = anyOrNull()
|
|
||||||
)
|
|
||||||
} doReturn posts
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
posts(postService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.get("/api/internal/v1/users/1/posts").apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, status)
|
|
||||||
assertEquals(posts, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `users username postsにGETしたらユーザーのPUBLICな投稿一覧を取得できる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val user = UserResponse(
|
|
||||||
id = "54321",
|
|
||||||
name = "user1",
|
|
||||||
domain = "example.com",
|
|
||||||
screenName = "user 1",
|
|
||||||
description = "Test user",
|
|
||||||
url = "https://example.com/users/54321",
|
|
||||||
createdAt = Instant.now().toEpochMilli()
|
|
||||||
)
|
|
||||||
val posts = listOf(
|
|
||||||
PostResponse(
|
|
||||||
id = "12345",
|
|
||||||
user = user,
|
|
||||||
text = "test1",
|
|
||||||
visibility = Visibility.PUBLIC,
|
|
||||||
createdAt = Instant.now().toEpochMilli(),
|
|
||||||
url = "https://example.com/posts/1"
|
|
||||||
),
|
|
||||||
PostResponse(
|
|
||||||
id = "123456",
|
|
||||||
user = user,
|
|
||||||
text = "test2",
|
|
||||||
visibility = Visibility.PUBLIC,
|
|
||||||
createdAt = Instant.now().toEpochMilli(),
|
|
||||||
url = "https://example.com/posts/2"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val postService = mock<PostApiService> {
|
|
||||||
onBlocking {
|
|
||||||
getByUser(
|
|
||||||
nameOrId = eq("test1"),
|
|
||||||
since = anyOrNull(),
|
|
||||||
until = anyOrNull(),
|
|
||||||
minId = anyOrNull(),
|
|
||||||
maxId = anyOrNull(),
|
|
||||||
limit = anyOrNull(),
|
|
||||||
userId = anyOrNull()
|
|
||||||
)
|
|
||||||
} doReturn posts
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
posts(postService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.get("/api/internal/v1/users/test1/posts").apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, status)
|
|
||||||
assertEquals(posts, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `users username@domain postsにGETしたらユーザーのPUBLICな投稿一覧を取得できる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val user = UserResponse(
|
|
||||||
id = "54321",
|
|
||||||
name = "user1",
|
|
||||||
domain = "example.com",
|
|
||||||
screenName = "user 1",
|
|
||||||
description = "Test user",
|
|
||||||
url = "https://example.com/users/54321",
|
|
||||||
createdAt = Instant.now().toEpochMilli()
|
|
||||||
)
|
|
||||||
val posts = listOf(
|
|
||||||
PostResponse(
|
|
||||||
id = "12345",
|
|
||||||
user = user,
|
|
||||||
text = "test1",
|
|
||||||
visibility = Visibility.PUBLIC,
|
|
||||||
createdAt = Instant.now().toEpochMilli(),
|
|
||||||
url = "https://example.com/posts/1"
|
|
||||||
),
|
|
||||||
PostResponse(
|
|
||||||
id = "123456",
|
|
||||||
user = user,
|
|
||||||
text = "test2",
|
|
||||||
visibility = Visibility.PUBLIC,
|
|
||||||
createdAt = Instant.now().toEpochMilli(),
|
|
||||||
url = "https://example.com/posts/2"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val postService = mock<PostApiService> {
|
|
||||||
onBlocking {
|
|
||||||
getByUser(
|
|
||||||
nameOrId = eq("test1@example.com"),
|
|
||||||
since = anyOrNull(),
|
|
||||||
until = anyOrNull(),
|
|
||||||
minId = anyOrNull(),
|
|
||||||
maxId = anyOrNull(),
|
|
||||||
limit = anyOrNull(),
|
|
||||||
userId = anyOrNull()
|
|
||||||
)
|
|
||||||
} doReturn posts
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
posts(postService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.get("/api/internal/v1/users/test1@example.com/posts").apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, status)
|
|
||||||
assertEquals(posts, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `users @username@domain postsにGETしたらユーザーのPUBLICな投稿一覧を取得できる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val user = UserResponse(
|
|
||||||
id = "54321",
|
|
||||||
name = "user1",
|
|
||||||
domain = "example.com",
|
|
||||||
screenName = "user 1",
|
|
||||||
description = "Test user",
|
|
||||||
url = "https://example.com/users/54321",
|
|
||||||
createdAt = Instant.now().toEpochMilli()
|
|
||||||
)
|
|
||||||
val posts = listOf(
|
|
||||||
PostResponse(
|
|
||||||
id = "12345",
|
|
||||||
user = user,
|
|
||||||
text = "test1",
|
|
||||||
visibility = Visibility.PUBLIC,
|
|
||||||
createdAt = Instant.now().toEpochMilli(),
|
|
||||||
url = "https://example.com/posts/1"
|
|
||||||
),
|
|
||||||
PostResponse(
|
|
||||||
id = "123456",
|
|
||||||
user = user,
|
|
||||||
text = "test2",
|
|
||||||
visibility = Visibility.PUBLIC,
|
|
||||||
createdAt = Instant.now().toEpochMilli(),
|
|
||||||
url = "https://example.com/posts/2"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val postService = mock<PostApiService> {
|
|
||||||
onBlocking {
|
|
||||||
getByUser(
|
|
||||||
nameOrId = eq("@test1@example.com"),
|
|
||||||
since = anyOrNull(),
|
|
||||||
until = anyOrNull(),
|
|
||||||
minId = anyOrNull(),
|
|
||||||
maxId = anyOrNull(),
|
|
||||||
limit = anyOrNull(),
|
|
||||||
userId = anyOrNull()
|
|
||||||
)
|
|
||||||
} doReturn posts
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
posts(postService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.get("/api/internal/v1/users/@test1@example.com/posts").apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, status)
|
|
||||||
assertEquals(posts, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `users name posts id にGETしたらPUBLICな投稿を取得できる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val post = PostResponse(
|
|
||||||
id = "123456",
|
|
||||||
user = UserResponse(
|
|
||||||
id = "54321",
|
|
||||||
name = "user1",
|
|
||||||
domain = "example.com",
|
|
||||||
screenName = "user 1",
|
|
||||||
description = "Test user",
|
|
||||||
url = "https://example.com/users/54321",
|
|
||||||
createdAt = Instant.now().toEpochMilli()
|
|
||||||
),
|
|
||||||
text = "test2",
|
|
||||||
visibility = Visibility.PUBLIC,
|
|
||||||
createdAt = Instant.now().toEpochMilli(),
|
|
||||||
url = "https://example.com/posts/2"
|
|
||||||
)
|
|
||||||
val postService = mock<PostApiService> {
|
|
||||||
onBlocking { getById(eq(12345L), anyOrNull()) } doReturn post
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
posts(postService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.get("/api/internal/v1/users/test/posts/12345").apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, status)
|
|
||||||
assertEquals(post, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `users id posts id にGETしたらPUBLICな投稿を取得できる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val post = PostResponse(
|
|
||||||
id = "123456",
|
|
||||||
user = UserResponse(
|
|
||||||
id = "54321",
|
|
||||||
name = "user1",
|
|
||||||
domain = "example.com",
|
|
||||||
screenName = "user 1",
|
|
||||||
description = "Test user",
|
|
||||||
url = "https://example.com/users/54321",
|
|
||||||
createdAt = Instant.now().toEpochMilli()
|
|
||||||
),
|
|
||||||
text = "test2",
|
|
||||||
visibility = Visibility.PUBLIC,
|
|
||||||
createdAt = Instant.now().toEpochMilli(),
|
|
||||||
url = "https://example.com/posts/2"
|
|
||||||
)
|
|
||||||
val postService = mock<PostApiService> {
|
|
||||||
onBlocking { getById(eq(12345L), anyOrNull()) } doReturn post
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
posts(postService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.get("/api/internal/v1/users/1/posts/12345").apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, status)
|
|
||||||
assertEquals(post, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `users name posts id にGETしたらUserIdが間違っててもPUBLICな投稿を取得できる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val post = PostResponse(
|
|
||||||
id = "123456",
|
|
||||||
user = UserResponse(
|
|
||||||
id = "54321",
|
|
||||||
name = "user1",
|
|
||||||
domain = "example.com",
|
|
||||||
screenName = "user 1",
|
|
||||||
description = "Test user",
|
|
||||||
url = "https://example.com/users/54321",
|
|
||||||
createdAt = Instant.now().toEpochMilli()
|
|
||||||
),
|
|
||||||
text = "test2",
|
|
||||||
visibility = Visibility.PUBLIC,
|
|
||||||
createdAt = Instant.now().toEpochMilli(),
|
|
||||||
url = "https://example.com/posts/2"
|
|
||||||
)
|
|
||||||
val postService = mock<PostApiService> {
|
|
||||||
onBlocking { getById(eq(12345L), anyOrNull()) } doReturn post
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
posts(postService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.get("/api/internal/v1/users/423827849732847/posts/12345").apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, status)
|
|
||||||
assertEquals(post, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `users name posts id にGETしたらuserNameが間違っててもPUBLICな投稿を取得できる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val post = PostResponse(
|
|
||||||
id = "123456",
|
|
||||||
user = UserResponse(
|
|
||||||
id = "54321",
|
|
||||||
name = "user1",
|
|
||||||
domain = "example.com",
|
|
||||||
screenName = "user 1",
|
|
||||||
description = "Test user",
|
|
||||||
url = "https://example.com/users/54321",
|
|
||||||
createdAt = Instant.now().toEpochMilli()
|
|
||||||
),
|
|
||||||
text = "test2",
|
|
||||||
visibility = Visibility.PUBLIC,
|
|
||||||
createdAt = Instant.now().toEpochMilli(),
|
|
||||||
url = "https://example.com/posts/2"
|
|
||||||
)
|
|
||||||
val postService = mock<PostApiService> {
|
|
||||||
onBlocking { getById(eq(12345L), anyOrNull()) } doReturn post
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
posts(postService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.get("/api/internal/v1/users/invalidUserName/posts/12345").apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, status)
|
|
||||||
assertEquals(post, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,692 +0,0 @@
|
||||||
package dev.usbharu.hideout.routing.api.internal.v1
|
|
||||||
|
|
||||||
import com.auth0.jwt.interfaces.Claim
|
|
||||||
import com.auth0.jwt.interfaces.Payload
|
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
|
||||||
import dev.usbharu.hideout.config.Config
|
|
||||||
import dev.usbharu.hideout.domain.model.Acct
|
|
||||||
import dev.usbharu.hideout.domain.model.hideout.dto.UserResponse
|
|
||||||
import dev.usbharu.hideout.domain.model.hideout.entity.User
|
|
||||||
import dev.usbharu.hideout.domain.model.hideout.form.UserCreate
|
|
||||||
import dev.usbharu.hideout.plugins.TOKEN_AUTH
|
|
||||||
import dev.usbharu.hideout.plugins.configureSecurity
|
|
||||||
import dev.usbharu.hideout.plugins.configureSerialization
|
|
||||||
import dev.usbharu.hideout.service.api.UserApiService
|
|
||||||
import dev.usbharu.hideout.service.user.UserService
|
|
||||||
import io.ktor.client.request.*
|
|
||||||
import io.ktor.client.statement.*
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.server.auth.*
|
|
||||||
import io.ktor.server.auth.jwt.*
|
|
||||||
import io.ktor.server.config.*
|
|
||||||
import io.ktor.server.routing.*
|
|
||||||
import io.ktor.server.testing.*
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.mockito.kotlin.*
|
|
||||||
import utils.JsonObjectMapper
|
|
||||||
import java.time.Instant
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
@Suppress("LargeClass")
|
|
||||||
class UsersTest {
|
|
||||||
@Test
|
|
||||||
fun `users にGETするとユーザー一覧を取得できる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
|
|
||||||
val users = listOf(
|
|
||||||
UserResponse(
|
|
||||||
"12345",
|
|
||||||
"test1",
|
|
||||||
"example.com",
|
|
||||||
"test",
|
|
||||||
"",
|
|
||||||
"https://example.com/test",
|
|
||||||
Instant.now().toEpochMilli()
|
|
||||||
),
|
|
||||||
UserResponse(
|
|
||||||
"12343",
|
|
||||||
"tes2",
|
|
||||||
"example.com",
|
|
||||||
"test",
|
|
||||||
"",
|
|
||||||
"https://example.com/tes2",
|
|
||||||
Instant.now().toEpochMilli()
|
|
||||||
),
|
|
||||||
)
|
|
||||||
val userService = mock<UserApiService> {
|
|
||||||
onBlocking { findAll(anyOrNull(), anyOrNull()) } doReturn users
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
users(mock(), userService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.get("/api/internal/v1/users").apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, status)
|
|
||||||
assertEquals(users, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `users にPOSTすると新規ユーザー作成ができる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val userCreateDto = UserCreate("test", "XXXXXXX")
|
|
||||||
val userService = mock<UserService> {
|
|
||||||
onBlocking { usernameAlreadyUse(any()) } doReturn false
|
|
||||||
onBlocking { createLocalUser(any()) } doReturn User.of(
|
|
||||||
id = 12345,
|
|
||||||
name = "test",
|
|
||||||
domain = "example.com",
|
|
||||||
screenName = "testUser",
|
|
||||||
description = "test user",
|
|
||||||
password = "XXXXXXX",
|
|
||||||
inbox = "https://example.com/inbox",
|
|
||||||
outbox = "https://example.com/outbox",
|
|
||||||
url = "https://example.com",
|
|
||||||
publicKey = "-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----",
|
|
||||||
privateKey = "-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----",
|
|
||||||
createdAt = Instant.now()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
users(userService, mock())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.post("/api/internal/v1/users") {
|
|
||||||
contentType(ContentType.Application.Json)
|
|
||||||
setBody(JsonObjectMapper.objectMapper.writeValueAsString(userCreateDto))
|
|
||||||
}.apply {
|
|
||||||
assertEquals(HttpStatusCode.Created, status)
|
|
||||||
assertEquals(
|
|
||||||
"${Config.configData.url}/api/internal/v1/users/${userCreateDto.username}",
|
|
||||||
headers["Location"]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `users 既にユーザー名が使用されているときはBadRequestが帰ってくる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val userCreateDto = UserCreate("test", "XXXXXXX")
|
|
||||||
val userService = mock<UserService> {
|
|
||||||
onBlocking { usernameAlreadyUse(any()) } doReturn true
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
users(userService, mock())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.post("/api/internal/v1/users") {
|
|
||||||
contentType(ContentType.Application.Json)
|
|
||||||
setBody(JsonObjectMapper.objectMapper.writeValueAsString(userCreateDto))
|
|
||||||
}.apply {
|
|
||||||
assertEquals(HttpStatusCode.BadRequest, status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `users name にGETしたらユーザーを取得できる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val userResponse = UserResponse(
|
|
||||||
"1234",
|
|
||||||
"test1",
|
|
||||||
"example.com",
|
|
||||||
"test",
|
|
||||||
"test User",
|
|
||||||
"https://example.com/test",
|
|
||||||
Instant.now().toEpochMilli()
|
|
||||||
)
|
|
||||||
val userApiService = mock<UserApiService> {
|
|
||||||
onBlocking { findByAcct(any()) } doReturn userResponse
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
users(mock(), userApiService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.get("/api/internal/v1/users/test1").apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, status)
|
|
||||||
assertEquals(userResponse, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `users id にGETしたらユーザーを取得できる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val userResponse = UserResponse(
|
|
||||||
"1234",
|
|
||||||
"test1",
|
|
||||||
"example.com",
|
|
||||||
"test",
|
|
||||||
"test User",
|
|
||||||
"https://example.com/test",
|
|
||||||
Instant.now().toEpochMilli()
|
|
||||||
)
|
|
||||||
val userApiService = mock<UserApiService> {
|
|
||||||
onBlocking { findById(any()) } doReturn userResponse
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
users(mock(), userApiService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.get("/api/internal/v1/users/1234").apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, status)
|
|
||||||
assertEquals(userResponse, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `users name@domain にGETしたらユーザーを取得できる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val userResponse = UserResponse(
|
|
||||||
"1234",
|
|
||||||
"test1",
|
|
||||||
"example.com",
|
|
||||||
"test",
|
|
||||||
"test User",
|
|
||||||
"https://example.com/test",
|
|
||||||
Instant.now().toEpochMilli()
|
|
||||||
)
|
|
||||||
val userApiService = mock<UserApiService> {
|
|
||||||
onBlocking { findByAcct(any()) } doReturn userResponse
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
users(mock(), userApiService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.get("/api/internal/v1/users/test1").apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, status)
|
|
||||||
assertEquals(userResponse, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `users @name@domain にGETしたらユーザーを取得できる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
val userResponse = UserResponse(
|
|
||||||
"1234",
|
|
||||||
"test1",
|
|
||||||
"example.com",
|
|
||||||
"test",
|
|
||||||
"test User",
|
|
||||||
"https://example.com/test",
|
|
||||||
Instant.now().toEpochMilli()
|
|
||||||
)
|
|
||||||
val userApiService = mock<UserApiService> {
|
|
||||||
onBlocking { findByAcct(any()) } doReturn userResponse
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
users(mock(), userApiService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.get("/api/internal/v1/users/test1").apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, status)
|
|
||||||
assertEquals(userResponse, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `users name followers にGETしたらフォロワー一覧を取得できる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
|
|
||||||
val followers = listOf(
|
|
||||||
UserResponse(
|
|
||||||
"1235",
|
|
||||||
"follower1",
|
|
||||||
"example.com",
|
|
||||||
"test",
|
|
||||||
"test User",
|
|
||||||
"https://example.com/test",
|
|
||||||
Instant.now().toEpochMilli()
|
|
||||||
),
|
|
||||||
UserResponse(
|
|
||||||
"1236",
|
|
||||||
"follower2",
|
|
||||||
"example.com",
|
|
||||||
"test",
|
|
||||||
"test User",
|
|
||||||
"https://example.com/test",
|
|
||||||
Instant.now().toEpochMilli()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val userApiService = mock<UserApiService> {
|
|
||||||
onBlocking { findFollowersByAcct(any()) } doReturn followers
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
users(mock(), userApiService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.get("/api/internal/v1/users/test1/followers").apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, status)
|
|
||||||
assertEquals(followers, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `users name@domain followers にGETしたらフォロワー一覧を取得できる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
|
|
||||||
val followers = listOf(
|
|
||||||
UserResponse(
|
|
||||||
"1235",
|
|
||||||
"follower1",
|
|
||||||
"example.com",
|
|
||||||
"test",
|
|
||||||
"test User",
|
|
||||||
"https://example.com/test",
|
|
||||||
Instant.now().toEpochMilli()
|
|
||||||
),
|
|
||||||
UserResponse(
|
|
||||||
"1236",
|
|
||||||
"follower2",
|
|
||||||
"example.com",
|
|
||||||
"test",
|
|
||||||
"test User",
|
|
||||||
"https://example.com/test",
|
|
||||||
Instant.now().toEpochMilli()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val userApiService = mock<UserApiService> {
|
|
||||||
onBlocking { findFollowersByAcct(any()) } doReturn followers
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
users(mock(), userApiService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.get("/api/internal/v1/users/@test1@example.com/followers").apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, status)
|
|
||||||
assertEquals(followers, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `users id followers にGETしたらフォロワー一覧を取得できる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
|
|
||||||
val followers = listOf(
|
|
||||||
UserResponse(
|
|
||||||
"1235",
|
|
||||||
"follower1",
|
|
||||||
"example.com",
|
|
||||||
"test",
|
|
||||||
"test User",
|
|
||||||
"https://example.com/test",
|
|
||||||
Instant.now().toEpochMilli()
|
|
||||||
),
|
|
||||||
UserResponse(
|
|
||||||
"1236",
|
|
||||||
"follower2",
|
|
||||||
"example.com",
|
|
||||||
"test",
|
|
||||||
"test User",
|
|
||||||
"https://example.com/test",
|
|
||||||
Instant.now().toEpochMilli()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val userApiService = mock<UserApiService> {
|
|
||||||
onBlocking { findFollowers(any()) } doReturn followers
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
users(mock(), userApiService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.get("/api/internal/v1/users/1234/followers").apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, status)
|
|
||||||
assertEquals(followers, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `users name followers に認証情報ありでGETしたらフォローできる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
|
|
||||||
val claim = mock<Claim> {
|
|
||||||
on { asLong() } doReturn 1234
|
|
||||||
}
|
|
||||||
val payload = mock<Payload> {
|
|
||||||
on { getClaim(eq("uid")) } doReturn claim
|
|
||||||
}
|
|
||||||
|
|
||||||
val userApiService = mock<UserApiService> {
|
|
||||||
onBlocking { findByAcct(any()) } doReturn UserResponse(
|
|
||||||
"1235",
|
|
||||||
"follower1",
|
|
||||||
"example.com",
|
|
||||||
"test",
|
|
||||||
"test User",
|
|
||||||
"https://example.com/test",
|
|
||||||
Instant.now().toEpochMilli()
|
|
||||||
)
|
|
||||||
onBlocking { follow(any<Acct>(), eq(1234)) } doReturn true
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
authentication {
|
|
||||||
bearer(TOKEN_AUTH) {
|
|
||||||
authenticate {
|
|
||||||
JWTPrincipal(payload)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
users(mock(), userApiService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.post("/api/internal/v1/users/test1/followers") {
|
|
||||||
header(HttpHeaders.Authorization, "Bearer test")
|
|
||||||
}.apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `users name followers に認証情報ありでGETしたらフォロー処理受付になることもある`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
|
|
||||||
val claim = mock<Claim> {
|
|
||||||
on { asLong() } doReturn 1234
|
|
||||||
}
|
|
||||||
val payload = mock<Payload> {
|
|
||||||
on { getClaim(eq("uid")) } doReturn claim
|
|
||||||
}
|
|
||||||
|
|
||||||
val userApiService = mock<UserApiService> {
|
|
||||||
onBlocking { findByAcct(any()) } doReturn UserResponse(
|
|
||||||
"1235",
|
|
||||||
"follower1",
|
|
||||||
"example.com",
|
|
||||||
"test",
|
|
||||||
"test User",
|
|
||||||
"https://example.com/test",
|
|
||||||
Instant.now().toEpochMilli()
|
|
||||||
)
|
|
||||||
onBlocking { follow(any<Acct>(), eq(1234)) } doReturn false
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
authentication {
|
|
||||||
bearer(TOKEN_AUTH) {
|
|
||||||
authenticate {
|
|
||||||
JWTPrincipal(payload)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
users(mock(), userApiService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.post("/api/internal/v1/users/test1/followers") {
|
|
||||||
header(HttpHeaders.Authorization, "Bearer test")
|
|
||||||
}.apply {
|
|
||||||
assertEquals(HttpStatusCode.Accepted, status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `users id followers に認証情報ありでGETしたらフォロー処理受付になることもある`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
|
|
||||||
val claim = mock<Claim> {
|
|
||||||
on { asLong() } doReturn 1234
|
|
||||||
}
|
|
||||||
val payload = mock<Payload> {
|
|
||||||
on { getClaim(eq("uid")) } doReturn claim
|
|
||||||
}
|
|
||||||
|
|
||||||
val userApiService = mock<UserApiService> {
|
|
||||||
onBlocking { findById(any()) } doReturn UserResponse(
|
|
||||||
"1235",
|
|
||||||
"follower1",
|
|
||||||
"example.com",
|
|
||||||
"test",
|
|
||||||
"test User",
|
|
||||||
"https://example.com/test",
|
|
||||||
Instant.now().toEpochMilli()
|
|
||||||
)
|
|
||||||
onBlocking { follow(eq(1235), eq(1234)) } doReturn false
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
authentication {
|
|
||||||
bearer(TOKEN_AUTH) {
|
|
||||||
authenticate {
|
|
||||||
JWTPrincipal(payload)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
users(mock(), userApiService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.post("/api/internal/v1/users/1235/followers") {
|
|
||||||
header(HttpHeaders.Authorization, "Bearer test")
|
|
||||||
}.apply {
|
|
||||||
assertEquals(HttpStatusCode.Accepted, status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `users name following にGETしたらフォロイー一覧を取得できる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
|
|
||||||
val followers = listOf(
|
|
||||||
UserResponse(
|
|
||||||
"1235",
|
|
||||||
"follower1",
|
|
||||||
"example.com",
|
|
||||||
"test",
|
|
||||||
"test User",
|
|
||||||
"https://example.com/test",
|
|
||||||
Instant.now().toEpochMilli()
|
|
||||||
),
|
|
||||||
UserResponse(
|
|
||||||
"1236",
|
|
||||||
"follower2",
|
|
||||||
"example.com",
|
|
||||||
"test",
|
|
||||||
"test User",
|
|
||||||
"https://example.com/test",
|
|
||||||
Instant.now().toEpochMilli()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val userApiService = mock<UserApiService> {
|
|
||||||
onBlocking { findFollowingsByAcct(any()) } doReturn followers
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
users(mock(), userApiService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.get("/api/internal/v1/users/test1/following").apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, status)
|
|
||||||
assertEquals(followers, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `users name@domain following にGETしたらフォロイー一覧を取得できる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
|
|
||||||
val followers = listOf(
|
|
||||||
UserResponse(
|
|
||||||
"1235",
|
|
||||||
"follower1",
|
|
||||||
"example.com",
|
|
||||||
"test",
|
|
||||||
"test User",
|
|
||||||
"https://example.com/test",
|
|
||||||
Instant.now().toEpochMilli()
|
|
||||||
),
|
|
||||||
UserResponse(
|
|
||||||
"1236",
|
|
||||||
"follower2",
|
|
||||||
"example.com",
|
|
||||||
"test",
|
|
||||||
"test User",
|
|
||||||
"https://example.com/test",
|
|
||||||
Instant.now().toEpochMilli()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val userApiService = mock<UserApiService> {
|
|
||||||
onBlocking { findFollowingsByAcct(any()) } doReturn followers
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
users(mock(), userApiService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.get("/api/internal/v1/users/test1@domain/following").apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, status)
|
|
||||||
assertEquals(followers, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `users id following にGETしたらフォロイー一覧を取得できる`() = testApplication {
|
|
||||||
environment {
|
|
||||||
config = ApplicationConfig("empty.conf")
|
|
||||||
}
|
|
||||||
|
|
||||||
val followers = listOf(
|
|
||||||
UserResponse(
|
|
||||||
"1235",
|
|
||||||
"follower1",
|
|
||||||
"example.com",
|
|
||||||
"test",
|
|
||||||
"test User",
|
|
||||||
"https://example.com/test",
|
|
||||||
Instant.now().toEpochMilli()
|
|
||||||
),
|
|
||||||
UserResponse(
|
|
||||||
"1236",
|
|
||||||
"follower2",
|
|
||||||
"example.com",
|
|
||||||
"test",
|
|
||||||
"test User",
|
|
||||||
"https://example.com/test",
|
|
||||||
Instant.now().toEpochMilli()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val userApiService = mock<UserApiService> {
|
|
||||||
onBlocking { findFollowings(any()) } doReturn followers
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
configureSerialization()
|
|
||||||
configureSecurity(mock(), mock())
|
|
||||||
routing {
|
|
||||||
route("/api/internal/v1") {
|
|
||||||
users(mock(), userApiService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.get("/api/internal/v1/users/1234/following").apply {
|
|
||||||
assertEquals(HttpStatusCode.OK, status)
|
|
||||||
assertEquals(followers, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue