diff --git a/src/main/kotlin/dev/usbharu/hideout/Application.kt b/src/main/kotlin/dev/usbharu/hideout/Application.kt index e4bca1fe..3fe5d5a1 100644 --- a/src/main/kotlin/dev/usbharu/hideout/Application.kt +++ b/src/main/kotlin/dev/usbharu/hideout/Application.kt @@ -8,12 +8,17 @@ import dev.usbharu.hideout.config.ConfigData import dev.usbharu.hideout.domain.model.job.DeliverPostJob import dev.usbharu.hideout.domain.model.job.ReceiveFollowJob import dev.usbharu.hideout.plugins.* -import dev.usbharu.hideout.repository.* +import dev.usbharu.hideout.repository.IPostRepository +import dev.usbharu.hideout.repository.IUserRepository +import dev.usbharu.hideout.repository.PostRepositoryImpl +import dev.usbharu.hideout.repository.UserRepository import dev.usbharu.hideout.routing.register import dev.usbharu.hideout.service.IPostService import dev.usbharu.hideout.service.IUserAuthService +import dev.usbharu.hideout.service.IdGenerateService import dev.usbharu.hideout.service.TwitterSnowflakeIdGenerateService import dev.usbharu.hideout.service.activitypub.* +import dev.usbharu.hideout.service.impl.IUserService import dev.usbharu.hideout.service.impl.PostService import dev.usbharu.hideout.service.impl.UserAuthService import dev.usbharu.hideout.service.impl.UserService @@ -57,9 +62,8 @@ fun Application.parent() { ) } - single<IUserRepository> { UserRepository(get()) } - single<IUserAuthRepository> { UserAuthRepository(get()) } - single<IUserAuthService> { UserAuthService(get(), get()) } + single<IUserRepository> { UserRepository(get(),get()) } + single<IUserAuthService> { UserAuthService(get()) } single<HttpSignatureVerifyService> { HttpSignatureVerifyServiceImpl(get()) } single<JobQueueParentService> { val kJobJobQueueService = KJobJobQueueParentService(get()) @@ -79,11 +83,12 @@ fun Application.parent() { } single<ActivityPubFollowService> { ActivityPubFollowServiceImpl(get(), get(), get(), get()) } single<ActivityPubService> { ActivityPubServiceImpl(get(), get()) } - single<UserService> { UserService(get()) } - single<ActivityPubUserService> { ActivityPubUserServiceImpl(get(), get(), get()) } + single<IUserService> { UserService(get(),get()) } + single<ActivityPubUserService> { ActivityPubUserServiceImpl(get(), get()) } single<ActivityPubNoteService> { ActivityPubNoteServiceImpl(get(), get(), get()) } single<IPostService> { PostService(get(), get()) } - single<IPostRepository> { PostRepositoryImpl(get(), TwitterSnowflakeIdGenerateService) } + single<IPostRepository> { PostRepositoryImpl(get(), get()) } + single<IdGenerateService> {TwitterSnowflakeIdGenerateService} } @@ -92,11 +97,11 @@ fun Application.parent() { configureStaticRouting() configureMonitoring() configureSerialization() - register(inject<IUserAuthService>().value) + register(inject<IUserService>().value) configureRouting( inject<HttpSignatureVerifyService>().value, inject<ActivityPubService>().value, - inject<UserService>().value, + inject<IUserService>().value, inject<ActivityPubUserService>().value, inject<IPostService>().value ) diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/Posts.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/Posts.kt index 3a6867db..56ae54a3 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/Posts.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/Posts.kt @@ -1,5 +1,6 @@ package dev.usbharu.hideout.domain.model +import dev.usbharu.hideout.repository.Users import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.Table @@ -28,7 +29,7 @@ data class Post( data class PostEntity( val id: Long, - val userId:Long, + val userId: Long, val overview: String? = null, val text: String, val createdAt: Long, @@ -38,7 +39,7 @@ data class PostEntity( val replyId: Long? = null ) -fun ResultRow.toPost():PostEntity{ +fun ResultRow.toPost(): PostEntity { return PostEntity( id = this[Posts.id], userId = this[Posts.userId], diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/User.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/User.kt deleted file mode 100644 index 7239465e..00000000 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/User.kt +++ /dev/null @@ -1,63 +0,0 @@ -package dev.usbharu.hideout.domain.model - -import org.jetbrains.exposed.dao.id.LongIdTable -import org.jetbrains.exposed.sql.ResultRow - -data class User( - val name: String, - val domain: String, - val screenName: String, - val description: String, - val inbox: String, - val outbox: String, - val url: String -) - -data class UserEntity( - val id: Long, - val name: String, - val domain: String, - val screenName: String, - val description: String, - val inbox: String, - val outbox: String, - val url: String -) { - constructor(id: Long, user: User) : this( - id, - user.name, - user.domain, - user.screenName, - user.description, - user.inbox, - user.outbox, - user.url - ) -} - -object Users : LongIdTable("users") { - val name = varchar("name", length = 64) - val domain = varchar("domain", length = 255) - val screenName = varchar("screen_name", length = 64) - val description = varchar("description", length = 600) - val inbox = varchar("inbox", length = 255).uniqueIndex() - val outbox = varchar("outbox", length = 255).uniqueIndex() - val url = varchar("url", length = 255).uniqueIndex() - - init { - uniqueIndex(name, domain) - } -} - - -fun ResultRow.toUser(): User { - return User( - this[Users.name], - this[Users.domain], - this[Users.screenName], - this[Users.description], - this[Users.inbox], - this[Users.outbox], - this[Users.url] - ) -} diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/UserAuthentication.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/UserAuthentication.kt deleted file mode 100644 index d1cd3d82..00000000 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/UserAuthentication.kt +++ /dev/null @@ -1,34 +0,0 @@ -package dev.usbharu.hideout.domain.model - -import org.jetbrains.exposed.dao.id.LongIdTable -import org.jetbrains.exposed.sql.ReferenceOption - -data class UserAuthentication( - val userId: Long, - val hash: String?, - val publicKey: String, - val privateKey: String? -) - -data class UserAuthenticationEntity( - val id: Long, - val userId: Long, - val hash: String?, - val publicKey: String, - val privateKey: String? -) { - constructor(id: Long, userAuthentication: UserAuthentication) : this( - id, - userAuthentication.userId, - userAuthentication.hash, - userAuthentication.publicKey, - userAuthentication.privateKey - ) -} - -object UsersAuthentication : LongIdTable("users_auth") { - val userId = long("user_id").references(Users.id, onUpdate = ReferenceOption.CASCADE) - val hash = varchar("hash", length = 64).nullable() - val publicKey = varchar("public_key", length = 1000_000) - val privateKey = varchar("private_key", length = 1000_000).nullable() -} diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/UsersFollowers.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/UsersFollowers.kt deleted file mode 100644 index 1f5de8ce..00000000 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/UsersFollowers.kt +++ /dev/null @@ -1,11 +0,0 @@ -package dev.usbharu.hideout.domain.model - -import org.jetbrains.exposed.dao.id.LongIdTable - -object UsersFollowers : LongIdTable("users_followers") { - val userId = long("user_id").references(Users.id).index() - val followerId = long("follower_id").references(Users.id) - init { - uniqueIndex(userId, followerId) - } -} diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Accept.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Accept.kt index 58889069..b2274eb6 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Accept.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Accept.kt @@ -1,17 +1,16 @@ package dev.usbharu.hideout.domain.model.ap open class Accept : Object { - public var `object`: Object? = null - public var actor:String? = null + var `object`: Object? = null + protected constructor() : super() constructor( type: List<String> = emptyList(), name: String, `object`: Object?, actor: String? - ) : super(add(type,"Accept"), name) { + ) : super(add(type, "Accept"), name,actor) { this.`object` = `object` - this.actor = actor } override fun equals(other: Any?): Boolean { diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Create.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Create.kt index a0766fd6..540e57aa 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Create.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Create.kt @@ -1,10 +1,21 @@ package dev.usbharu.hideout.domain.model.ap open class Create : Object { - var `object` : Object? = null + var `object`: Object? = null protected constructor() : super() - constructor(type: List<String> = emptyList(), name: String, `object`: Object?) : super(add(type,"Create"), name) { + constructor( + type: List<String> = emptyList(), + name: String? = null, + `object`: Object?, + actor: String? = null, + id: String? = null + ) : super( + add(type, "Create"), + name, + actor, + id + ) { this.`object` = `object` } diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Follow.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Follow.kt index c73c85a3..cf6d1303 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Follow.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Follow.kt @@ -1,17 +1,16 @@ package dev.usbharu.hideout.domain.model.ap open class Follow : Object { - public var `object`:String? = null - public var actor:String? = null + var `object`: String? = null + protected constructor() : super() constructor( type: List<String> = emptyList(), name: String, `object`: String?, actor: String? - ) : super(add(type,"Follow"), name) { + ) : super(add(type, "Follow"), name,actor) { this.`object` = `object` - this.actor = actor } diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Image.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Image.kt index 5767e7b7..5763366b 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Image.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Image.kt @@ -6,7 +6,7 @@ open class Image : Object { protected constructor() : super() constructor(type: List<String> = emptyList(), name: String, mediaType: String?, url: String?) : super( - add(type,"Image"), + add(type, "Image"), name ) { this.mediaType = mediaType diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/JsonLd.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/JsonLd.kt index 4965814a..f6c4c73a 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/JsonLd.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/JsonLd.kt @@ -2,6 +2,7 @@ package dev.usbharu.hideout.domain.model.ap import com.fasterxml.jackson.annotation.JsonAutoDetect import com.fasterxml.jackson.annotation.JsonCreator +import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.databind.JsonDeserializer @@ -15,11 +16,12 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize open class JsonLd { @JsonProperty("@context") @JsonDeserialize(contentUsing = ContextDeserializer::class) - @JsonSerialize(using = ContextSerializer::class) - var context:List<String> = emptyList() + @JsonSerialize(include = JsonSerialize.Inclusion.NON_EMPTY, using = ContextSerializer::class) + @JsonInclude(JsonInclude.Include.NON_EMPTY) + var context: List<String> = emptyList() @JsonCreator - constructor(context:List<String>){ + constructor(context: List<String>) { this.context = context } @@ -43,9 +45,12 @@ open class JsonLd { } -public class ContextDeserializer : JsonDeserializer<String>() { - override fun deserialize(p0: com.fasterxml.jackson.core.JsonParser?, p1: com.fasterxml.jackson.databind.DeserializationContext?): String { - val readTree : JsonNode = p0?.codec?.readTree(p0) ?: return "" +class ContextDeserializer : JsonDeserializer<String>() { + override fun deserialize( + p0: com.fasterxml.jackson.core.JsonParser?, + p1: com.fasterxml.jackson.databind.DeserializationContext? + ): String { + val readTree: JsonNode = p0?.codec?.readTree(p0) ?: return "" if (readTree.isObject) { return "" } @@ -53,17 +58,23 @@ public class ContextDeserializer : JsonDeserializer<String>() { } } -public class ContextSerializer : JsonSerializer<List<String>>() { +class ContextSerializer : JsonSerializer<List<String>>() { + + + override fun isEmpty(value: List<String>?): Boolean { + return value.isNullOrEmpty() + } + override fun serialize(value: List<String>?, gen: JsonGenerator?, serializers: SerializerProvider?) { if (value.isNullOrEmpty()) { gen?.writeNull() return } - if (value?.size == 1) { + if (value.size == 1) { gen?.writeString(value[0]) } else { gen?.writeStartArray() - value?.forEach { + value.forEach { gen?.writeString(it) } gen?.writeEndArray() diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Key.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Key.kt index ec79ace0..935a531b 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Key.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Key.kt @@ -1,9 +1,9 @@ package dev.usbharu.hideout.domain.model.ap open class Key : Object { - var id:String? = null - var owner:String? = null - var publicKeyPem:String? = null + var owner: String? = null + var publicKeyPem: String? = null + protected constructor() : super() constructor( type: List<String>, @@ -11,8 +11,7 @@ open class Key : Object { id: String?, owner: String?, publicKeyPem: String? - ) : super(add(type,"Key"), name) { - this.id = id + ) : super(add(type, "Key"), name,id) { this.owner = owner this.publicKeyPem = publicKeyPem } diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Note.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Note.kt index caced1da..e62fccc2 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Note.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Note.kt @@ -1,11 +1,11 @@ package dev.usbharu.hideout.domain.model.ap open class Note : Object { - var id:String? = null - var attributedTo:String? = null - var content:String? = null - var published:String? = null - var to:List<String> = emptyList() + var attributedTo: String? = null + var content: String? = null + var published: String? = null + var to: List<String> = emptyList() + protected constructor() : super() constructor( type: List<String> = emptyList(), @@ -15,8 +15,11 @@ open class Note : Object { content: String?, published: String?, to: List<String> = emptyList() - ) : super(add(type,"Note"), name) { - this.id = id + ) : super( + type = add(type, "Note"), + name = name, + id = id + ) { this.attributedTo = attributedTo this.content = content this.published = published diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Object.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Object.kt index faca1cd4..c66e7224 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Object.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Object.kt @@ -9,44 +9,54 @@ open class Object : JsonLd { @JsonSerialize(using = TypeSerializer::class) private var type: List<String> = emptyList() var name: String? = null + var actor: String? = null + var id:String? = null protected constructor() - constructor(type: List<String>, name: String) : super() { + constructor(type: List<String>, name: String? = null,actor:String? = null,id:String? = null) : super() { this.type = type this.name = name + this.actor = actor + this.id = id } companion object { @JvmStatic - protected fun add(list:List<String>,type:String):List<String> { + protected fun add(list: List<String>, type: String): List<String> { val toMutableList = list.toMutableList() toMutableList.add(type) return toMutableList.distinct() } } + + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is Object) return false + if (!super.equals(other)) return false if (type != other.type) return false - return name == other.name + if (name != other.name) return false + return actor == other.actor } override fun hashCode(): Int { - var result = type.hashCode() + var result = super.hashCode() + result = 31 * result + type.hashCode() result = 31 * result + (name?.hashCode() ?: 0) + result = 31 * result + (actor?.hashCode() ?: 0) return result } override fun toString(): String { - return "Object(type=$type, name=$name) ${super.toString()}" + return "Object(type=$type, name=$name, actor=$actor) ${super.toString()}" } } -public class TypeSerializer : JsonSerializer<List<String>>() { +class TypeSerializer : JsonSerializer<List<String>>() { override fun serialize(value: List<String>?, gen: JsonGenerator?, serializers: SerializerProvider?) { println(value) if (value?.size == 1) { diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Person.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Person.kt index cc04bceb..8a4e5711 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Person.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Person.kt @@ -1,14 +1,14 @@ package dev.usbharu.hideout.domain.model.ap open class Person : Object { - private var id:String? = null - var preferredUsername:String? = null - var summary:String? = null - var inbox:String? = null - var outbox:String? = null - private var url:String? = null + var preferredUsername: String? = null + var summary: String? = null + var inbox: String? = null + var outbox: String? = null + private var url: String? = null private var icon: Image? = null var publicKey: Key? = null + protected constructor() : super() constructor( type: List<String> = emptyList(), @@ -21,15 +21,14 @@ open class Person : Object { url: String?, icon: Image?, publicKey: Key? - ) : super(add(type,"Person"), name) { - this.id = id + ) : super(add(type, "Person"), name,id = id) { this.preferredUsername = preferredUsername this.summary = summary this.inbox = inbox this.outbox = outbox this.url = url this.icon = icon - this.publicKey = publicKey + this.publicKey = publicKey } override fun equals(other: Any?): Boolean { diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/api/Status.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/api/Status.kt index e8e9bfe0..b89a0516 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/api/Status.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/api/Status.kt @@ -1,6 +1,6 @@ package dev.usbharu.hideout.domain.model.api data class StatusForPost( - val status:String, - val userId:Long + val status: String, + val userId: Long ) diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/RemoteUserCreateDto.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/RemoteUserCreateDto.kt new file mode 100644 index 00000000..35081932 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/RemoteUserCreateDto.kt @@ -0,0 +1,12 @@ +package dev.usbharu.hideout.domain.model.hideout.dto + +data class RemoteUserCreateDto( + val name:String, + val domain:String, + val screenName:String, + val description:String, + val inbox:String, + val outbox:String, + val url:String, + val publicKey:String, +) diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/UserCreateDto.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/UserCreateDto.kt new file mode 100644 index 00000000..e8a59f26 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/UserCreateDto.kt @@ -0,0 +1,8 @@ +package dev.usbharu.hideout.domain.model.hideout.dto + +data class UserCreateDto( + val name:String, + val screenName:String, + val description:String, + val password:String +) diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/User.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/User.kt new file mode 100644 index 00000000..319d1f8c --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/User.kt @@ -0,0 +1,18 @@ +package dev.usbharu.hideout.domain.model.hideout.entity + +import java.time.Instant + +data class User( + val id: Long, + val name: String, + val domain: String, + val screenName: String, + val description: String, + val password: String? = null, + val inbox: String, + val outbox: String, + val url: String, + val publicKey: String, + val privateKey: String? = null, + val createdAt: Instant +) diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/job/HideoutJob.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/job/HideoutJob.kt index 6bce8a95..e2d124ad 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/job/HideoutJob.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/job/HideoutJob.kt @@ -4,13 +4,13 @@ import kjob.core.Job sealed class HideoutJob(name: String = "") : Job(name) -object ReceiveFollowJob : HideoutJob("ReceiveFollowJob"){ +object ReceiveFollowJob : HideoutJob("ReceiveFollowJob") { val actor = string("actor") val follow = string("follow") val targetActor = string("targetActor") } -object DeliverPostJob : HideoutJob("DeliverPostJob"){ +object DeliverPostJob : HideoutJob("DeliverPostJob") { val post = string("post") val actor = string("actor") val inbox = string("inbox") diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/wellknown/WebFinger.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/wellknown/WebFinger.kt index b8542f23..01f0e645 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/wellknown/WebFinger.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/wellknown/WebFinger.kt @@ -1,5 +1,5 @@ package dev.usbharu.hideout.domain.model.wellknown -data class WebFinger(val subject:String,val links:List<Link>){ - data class Link(val rel:String,val type:String,val href:String) +data class WebFinger(val subject: String, val links: List<Link>) { + data class Link(val rel: String, val type: String, val href: String) } diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/ActivityPub.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/ActivityPub.kt index db0e6a1f..af7c4b86 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/ActivityPub.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/ActivityPub.kt @@ -1,8 +1,8 @@ package dev.usbharu.hideout.plugins -import dev.usbharu.hideout.domain.model.ap.JsonLd import dev.usbharu.hideout.config.Config -import dev.usbharu.hideout.service.IUserAuthService +import dev.usbharu.hideout.domain.model.ap.JsonLd +import dev.usbharu.hideout.repository.IUserRepository import dev.usbharu.hideout.service.impl.UserAuthService import dev.usbharu.hideout.util.HttpUtil.Activity import io.ktor.client.* @@ -44,6 +44,13 @@ suspend fun HttpClient.postAp(urlString: String, username: String, jsonLd: JsonL } } +suspend fun HttpClient.getAp(urlString: String, username: String): HttpResponse { + return this.get(urlString) { + header("Accept", ContentType.Application.Activity) + header("Signature", "keyId=\"$username\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date\"") + } +} + class HttpSignaturePluginConfig { lateinit var keyMap: KeyMap } @@ -56,6 +63,7 @@ val httpSignaturePlugin = createClientPlugin("HttpSign", ::HttpSignaturePluginCo request.header("Date", format.format(Date())) + request.header("Host", "${request.url.host}") println(request.bodyType) println(request.bodyType?.type) if (request.bodyType?.type == String::class) { @@ -63,8 +71,8 @@ val httpSignaturePlugin = createClientPlugin("HttpSign", ::HttpSignaturePluginCo println("Digest !!") // UserAuthService.sha256.reset() val digest = - Base64.getEncoder().encodeToString(UserAuthService.sha256.digest(body.toByteArray(Charsets.UTF_8))) - request.headers.append("Digest", "sha-256="+digest) + Base64.getEncoder().encodeToString(UserAuthService.sha256.digest(body.toByteArray(Charsets.UTF_8))) + request.headers.append("Digest", "sha-256=" + digest) } if (request.headers.contains("Signature")) { @@ -104,6 +112,10 @@ val httpSignaturePlugin = createClientPlugin("HttpSign", ::HttpSignaturePluginCo "Date" } + "host" -> { + "Host" + } + else -> { it } @@ -126,7 +138,7 @@ val httpSignaturePlugin = createClientPlugin("HttpSign", ::HttpSignaturePluginCo override fun addHeader(name: String?, value: String?) { val split = value?.split("=").orEmpty() - name?.let { request.header(it, split.get(0)+"=\""+split.get(1).trim('"')+"\"") } + name?.let { request.header(it, split.get(0) + "=\"" + split.get(1).trim('"') + "\"") } } override fun method(): String { @@ -139,19 +151,24 @@ val httpSignaturePlugin = createClientPlugin("HttpSign", ::HttpSignaturePluginCo }) + + val signatureHeader = request.headers.getAll("Signature").orEmpty() + request.headers.remove("Signature") + signatureHeader.map { it.replace("; ", ",").replace(";", ",") }.joinToString(",") + .let { request.header("Signature", it) } } } } -class KtorKeyMap(private val userAuthRepository: IUserAuthService) : KeyMap { +class KtorKeyMap(private val userAuthRepository: IUserRepository) : KeyMap { override fun getPublicKey(keyId: String?): PublicKey = runBlocking { val username = (keyId ?: throw IllegalArgumentException("keyId is null")).substringBeforeLast("#pubkey") .substringAfterLast("/") val publicBytes = Base64.getDecoder().decode( - userAuthRepository.findByUsername( - username - ).publicKey?.replace("-----BEGIN PUBLIC KEY-----", "-----END PUBLIC KEY-----")?.replace("", "") + userAuthRepository.findByNameAndDomain( + username, Config.configData.domain + )?.publicKey?.replace("-----BEGIN PUBLIC KEY-----", "-----END PUBLIC KEY-----")?.replace("", "") ?.replace("\n", "") ) val x509EncodedKeySpec = X509EncodedKeySpec(publicBytes) @@ -162,9 +179,9 @@ class KtorKeyMap(private val userAuthRepository: IUserAuthService) : KeyMap { val username = (keyId ?: throw IllegalArgumentException("keyId is null")).substringBeforeLast("#pubkey") .substringAfterLast("/") val publicBytes = Base64.getDecoder().decode( - userAuthRepository.findByUsername( - username - ).privateKey?.replace("-----BEGIN PRIVATE KEY-----", "")?.replace("-----END PRIVATE KEY-----", "") + userAuthRepository.findByNameAndDomain( + username, Config.configData.domain + )?.privateKey?.replace("-----BEGIN PRIVATE KEY-----", "")?.replace("-----END PRIVATE KEY-----", "") ?.replace("\n", "") ) val x509EncodedKeySpec = PKCS8EncodedKeySpec(publicBytes) diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/HTTP.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/HTTP.kt index c89f9289..234130ad 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/HTTP.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/HTTP.kt @@ -1,10 +1,10 @@ package dev.usbharu.hideout.plugins import io.ktor.http.* +import io.ktor.server.application.* import io.ktor.server.plugins.cors.routing.* import io.ktor.server.plugins.defaultheaders.* import io.ktor.server.plugins.forwardedheaders.* -import io.ktor.server.application.* fun Application.configureHTTP() { install(CORS) { diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/Monitoring.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/Monitoring.kt index 3ce065b7..2f33df7a 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/Monitoring.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/Monitoring.kt @@ -1,9 +1,8 @@ package dev.usbharu.hideout.plugins -import io.ktor.server.plugins.callloging.* -import org.slf4j.event.* -import io.ktor.server.request.* import io.ktor.server.application.* +import io.ktor.server.plugins.callloging.* +import org.slf4j.event.Level fun Application.configureMonitoring() { install(CallLogging) { diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt index fc4f0742..f6bebf53 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt @@ -8,7 +8,7 @@ import dev.usbharu.hideout.routing.wellknown.webfinger import dev.usbharu.hideout.service.IPostService import dev.usbharu.hideout.service.activitypub.ActivityPubService import dev.usbharu.hideout.service.activitypub.ActivityPubUserService -import dev.usbharu.hideout.service.impl.UserService +import dev.usbharu.hideout.service.impl.IUserService import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService import io.ktor.server.application.* import io.ktor.server.plugins.autohead.* @@ -17,7 +17,7 @@ import io.ktor.server.routing.* fun Application.configureRouting( httpSignatureVerifyService: HttpSignatureVerifyService, activityPubService: ActivityPubService, - userService: UserService, + userService: IUserService, activityPubUserService: ActivityPubUserService, postService: IPostService ) { @@ -25,7 +25,7 @@ fun Application.configureRouting( routing { inbox(httpSignatureVerifyService, activityPubService) outbox() - usersAP(activityPubUserService,userService) + usersAP(activityPubUserService, userService) webfinger(userService) route("/api/v1") { diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/Security.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/Security.kt index 94485569..f8a9e2b3 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/Security.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/Security.kt @@ -3,8 +3,6 @@ package dev.usbharu.hideout.plugins import dev.usbharu.hideout.service.IUserAuthService import io.ktor.server.application.* import io.ktor.server.auth.* -import io.ktor.server.sessions.* -import kotlin.collections.set data class UserSession(val username: String) : Principal diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/Serialization.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/Serialization.kt index c8b71a7d..09edfe72 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/Serialization.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/Serialization.kt @@ -4,13 +4,9 @@ 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 dev.usbharu.hideout.util.HttpUtil.Activity -import io.ktor.http.* import io.ktor.serialization.jackson.* import io.ktor.server.application.* import io.ktor.server.plugins.contentnegotiation.* -import io.ktor.server.response.* -import io.ktor.server.routing.* fun Application.configureSerialization() { install(ContentNegotiation) { diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/IUserAuthRepository.kt b/src/main/kotlin/dev/usbharu/hideout/repository/IUserAuthRepository.kt deleted file mode 100644 index 2f6f46ba..00000000 --- a/src/main/kotlin/dev/usbharu/hideout/repository/IUserAuthRepository.kt +++ /dev/null @@ -1,15 +0,0 @@ -package dev.usbharu.hideout.repository - -import dev.usbharu.hideout.domain.model.UserAuthentication -import dev.usbharu.hideout.domain.model.UserAuthenticationEntity - -interface IUserAuthRepository { - suspend fun create(userAuthentication: UserAuthentication):UserAuthenticationEntity - - suspend fun findById(id:Long):UserAuthenticationEntity? - - suspend fun update(userAuthenticationEntity: UserAuthenticationEntity) - - suspend fun delete(id:Long) - suspend fun findByUserId(id: Long): UserAuthenticationEntity? -} diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/IUserRepository.kt b/src/main/kotlin/dev/usbharu/hideout/repository/IUserRepository.kt index dd89dbd9..0d19094f 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/IUserRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/IUserRepository.kt @@ -1,32 +1,38 @@ package dev.usbharu.hideout.repository -import dev.usbharu.hideout.domain.model.User -import dev.usbharu.hideout.domain.model.UserEntity +import dev.usbharu.hideout.domain.model.hideout.entity.User interface IUserRepository { - suspend fun create(user: User): UserEntity + suspend fun save(user: User): User - suspend fun findById(id: Long): UserEntity? + suspend fun findById(id: Long): User? - suspend fun findByIds(ids: List<Long>): List<UserEntity> + suspend fun findByIds(ids: List<Long>): List<User> - suspend fun findByName(name: String): UserEntity? + suspend fun findByName(name: String): List<User> - suspend fun findByNameAndDomains(names: List<Pair<String,String>>): List<UserEntity> + suspend fun findByNameAndDomain(name: String, domain: String): User? - suspend fun findByUrl(url:String):UserEntity? + suspend fun findByDomain(domain:String): List<User> - suspend fun findByUrls(urls: List<String>): List<UserEntity> + suspend fun findByNameAndDomains(names: List<Pair<String,String>>): List<User> - suspend fun update(userEntity: UserEntity) + suspend fun findByUrl(url:String): User? + + suspend fun findByUrls(urls: List<String>): List<User> + + @Deprecated("", ReplaceWith("save(userEntity)")) + suspend fun update(userEntity: User) = save(userEntity) suspend fun delete(id: Long) suspend fun findAll(): List<User> - suspend fun findAllByLimitAndByOffset(limit: Int, offset: Long = 0): List<UserEntity> + suspend fun findAllByLimitAndByOffset(limit: Int, offset: Long = 0): List<User> suspend fun createFollower(id: Long, follower: Long) suspend fun deleteFollower(id: Long, follower: Long) - suspend fun findFollowersById(id: Long): List<UserEntity> + suspend fun findFollowersById(id: Long): List<User> + + suspend fun nextId():Long } diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/UserAuthRepository.kt b/src/main/kotlin/dev/usbharu/hideout/repository/UserAuthRepository.kt deleted file mode 100644 index 0bb5543c..00000000 --- a/src/main/kotlin/dev/usbharu/hideout/repository/UserAuthRepository.kt +++ /dev/null @@ -1,63 +0,0 @@ -package dev.usbharu.hideout.repository - -import dev.usbharu.hideout.domain.model.UserAuthentication -import dev.usbharu.hideout.domain.model.UserAuthenticationEntity -import dev.usbharu.hideout.domain.model.UsersAuthentication -import kotlinx.coroutines.Dispatchers -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction -import org.jetbrains.exposed.sql.transactions.transaction - -class UserAuthRepository(private val database: Database) : IUserAuthRepository { - - init { - transaction(database) { - SchemaUtils.create(UsersAuthentication) - SchemaUtils.createMissingTablesAndColumns(UsersAuthentication) - } - } - - private fun ResultRow.toUserAuth():UserAuthenticationEntity{ - return UserAuthenticationEntity( - id = this[UsersAuthentication.id].value, - userId = this[UsersAuthentication.userId], - hash = this[UsersAuthentication.hash], - publicKey = this[UsersAuthentication.publicKey], - privateKey = this[UsersAuthentication.privateKey] - ) - } - - - suspend fun <T> query(block: suspend () -> T): T = - newSuspendedTransaction(Dispatchers.IO) {block()} - override suspend fun create(userAuthentication: UserAuthentication): UserAuthenticationEntity { - return query { - UserAuthenticationEntity( - UsersAuthentication.insert { - it[userId] = userAuthentication.userId - it[hash] = userAuthentication.hash - it[publicKey] = userAuthentication.publicKey - it[privateKey] = userAuthentication.privateKey - }[UsersAuthentication.id].value,userAuthentication - ) - } - } - - override suspend fun findById(id: Long): UserAuthenticationEntity? { - TODO("Not yet implemented") - } - - override suspend fun findByUserId(id:Long):UserAuthenticationEntity? { - return query { - UsersAuthentication.select { UsersAuthentication.userId eq id }.map { it.toUserAuth() }.singleOrNull() - } - } - - override suspend fun update(userAuthenticationEntity: UserAuthenticationEntity) { - TODO("Not yet implemented") - } - - override suspend fun delete(id: Long) { - TODO("Not yet implemented") - } -} diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/UserRepository.kt b/src/main/kotlin/dev/usbharu/hideout/repository/UserRepository.kt index dfaf32aa..9a577a83 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/UserRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/UserRepository.kt @@ -1,16 +1,17 @@ package dev.usbharu.hideout.repository -import dev.usbharu.hideout.domain.model.User -import dev.usbharu.hideout.domain.model.UserEntity -import dev.usbharu.hideout.domain.model.Users -import dev.usbharu.hideout.domain.model.UsersFollowers +import dev.usbharu.hideout.domain.model.hideout.entity.User +import dev.usbharu.hideout.service.IdGenerateService import kotlinx.coroutines.Dispatchers +import org.jetbrains.exposed.dao.id.LongIdTable import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction import org.jetbrains.exposed.sql.transactions.transaction +import java.time.Instant -class UserRepository(private val database: Database) : IUserRepository { +class UserRepository(private val database: Database, private val idGenerateService: IdGenerateService) : + IUserRepository { init { transaction(database) { SchemaUtils.create(Users) @@ -20,45 +21,43 @@ class UserRepository(private val database: Database) : IUserRepository { } } - private fun ResultRow.toUser(): User { - return User( - this[Users.name], - this[Users.domain], - this[Users.screenName], - this[Users.description], - this[Users.inbox], - this[Users.outbox], - this[Users.url] - ) - } - - private fun ResultRow.toUserEntity(): UserEntity { - return UserEntity( - this[Users.id].value, - this[Users.name], - this[Users.domain], - this[Users.screenName], - this[Users.description], - this[Users.inbox], - this[Users.outbox], - this[Users.url], - ) - } - suspend fun <T> query(block: suspend () -> T): T = newSuspendedTransaction(Dispatchers.IO) { block() } - override suspend fun create(user: User): UserEntity { + override suspend fun save(user: User): User { return query { - UserEntity(Users.insert { - it[name] = user.name - it[domain] = user.domain - it[screenName] = user.screenName - it[description] = user.description - it[inbox] = user.inbox - it[outbox] = user.outbox - it[url] = user.url - }[Users.id].value, user) + val singleOrNull = Users.select { Users.id eq user.id }.singleOrNull() + if (singleOrNull == null) { + Users.insert { + it[id] = user.id + it[name] = user.name + it[domain] = user.domain + it[screenName] = user.screenName + it[description] = user.description + it[password] = user.password + it[inbox] = user.inbox + it[outbox] = user.outbox + it[url] = user.url + it[createdAt] = user.createdAt.toEpochMilli() + it[publicKey] = user.publicKey + it[privateKey] = user.privateKey + } + } else { + Users.update({ Users.id eq user.id }) { + it[name] = user.name + it[domain] = user.domain + it[screenName] = user.screenName + it[description] = user.description + it[password] = user.password + it[inbox] = user.inbox + it[outbox] = user.outbox + it[url] = user.url + it[createdAt] = user.createdAt.toEpochMilli() + it[publicKey] = user.publicKey + it[privateKey] = user.privateKey + } + } + return@query user } } @@ -71,59 +70,73 @@ class UserRepository(private val database: Database) : IUserRepository { } } - override suspend fun findById(id: Long): UserEntity? { + override suspend fun findById(id: Long): User? { return query { Users.select { Users.id eq id }.map { - it.toUserEntity() + it.toUser() }.singleOrNull() } } - override suspend fun findByIds(ids: List<Long>): List<UserEntity> { + override suspend fun findByIds(ids: List<Long>): List<User> { return query { Users.select { Users.id inList ids }.map { - it.toUserEntity() + it.toUser() } } } - override suspend fun findByName(name: String): UserEntity? { + override suspend fun findByName(name: String): List<User> { return query { Users.select { Users.name eq name }.map { - it.toUserEntity() - }.singleOrNull() + it.toUser() + } } } - override suspend fun findByNameAndDomains(names: List<Pair<String, String>>): List<UserEntity> { + override suspend fun findByNameAndDomain(name: String, domain: String): User? { + return query { + Users.select { Users.name eq name and (Users.domain eq domain) }.singleOrNull()?.toUser() + } + } + + override suspend fun findByDomain(domain: String): List<User> { + return query { + Users.select { Users.domain eq domain }.map { + it.toUser() + } + } + } + + override suspend fun findByNameAndDomains(names: List<Pair<String, String>>): List<User> { return query { val selectAll = Users.selectAll() names.forEach { (name, domain) -> selectAll.orWhere { Users.name eq name and (Users.domain eq domain) } } - selectAll.map { it.toUserEntity() } + selectAll.map { it.toUser() } } } - override suspend fun findByUrl(url: String): UserEntity? { + override suspend fun findByUrl(url: String): User? { return query { - Users.select { Users.url eq url }.singleOrNull()?.toUserEntity() + Users.select { Users.url eq url }.singleOrNull()?.toUser() } } - override suspend fun findByUrls(urls: List<String>): List<UserEntity> { + override suspend fun findByUrls(urls: List<String>): List<User> { return query { - Users.select { Users.url inList urls }.map { it.toUserEntity() } + Users.select { Users.url inList urls }.map { it.toUser() } } } - override suspend fun findFollowersById(id: Long): List<UserEntity> { + override suspend fun findFollowersById(id: Long): List<User> { return query { val followers = Users.alias("FOLLOWERS") Users.innerJoin( otherTable = UsersFollowers, onColumn = { Users.id }, - otherColumn = { UsersFollowers.userId }) + otherColumn = { userId }) .innerJoin( otherTable = followers, @@ -136,41 +149,35 @@ class UserRepository(private val database: Database) : IUserRepository { followers.get(Users.domain), followers.get(Users.screenName), followers.get(Users.description), + followers.get(Users.password), followers.get(Users.inbox), followers.get(Users.outbox), - followers.get(Users.url) + followers.get(Users.url), + followers.get(Users.publicKey), + followers.get(Users.privateKey), + followers.get(Users.createdAt) ) .select { Users.id eq id } .map { - UserEntity( - id = it[followers[Users.id]].value, + User( + id = it[followers[Users.id]], name = it[followers[Users.name]], domain = it[followers[Users.domain]], screenName = it[followers[Users.screenName]], description = it[followers[Users.description]], + password = it[followers[Users.password]], inbox = it[followers[Users.inbox]], outbox = it[followers[Users.outbox]], url = it[followers[Users.url]], + publicKey = it[followers[Users.publicKey]], + privateKey = it[followers[Users.privateKey]], + createdAt = Instant.ofEpochMilli(it[followers[Users.createdAt]]) ) } } } - override suspend fun update(userEntity: UserEntity) { - return query { - Users.update({ Users.id eq userEntity.id }) { - it[name] = userEntity.name - it[domain] = userEntity.domain - it[screenName] = userEntity.screenName - it[description] = userEntity.description - it[inbox] = userEntity.inbox - it[outbox] = userEntity.outbox - it[url] = userEntity.url - } - } - } - override suspend fun delete(id: Long) { query { Users.deleteWhere { Users.id.eq(id) } @@ -189,9 +196,60 @@ class UserRepository(private val database: Database) : IUserRepository { } } - override suspend fun findAllByLimitAndByOffset(limit: Int, offset: Long): List<UserEntity> { + override suspend fun findAllByLimitAndByOffset(limit: Int, offset: Long): List<User> { return query { - Users.selectAll().limit(limit, offset).map { it.toUserEntity() } + Users.selectAll().limit(limit, offset).map { it.toUser() } } } + + override suspend fun nextId(): Long { + return idGenerateService.generateId() + } +} + +object Users : Table("users") { + val id = long("id") + val name = varchar("name", length = 64) + val domain = varchar("domain", length = 255) + val screenName = varchar("screen_name", length = 64) + val description = varchar("description", length = 600) + val password = varchar("password", length = 255).nullable() + val inbox = varchar("inbox", length = 255).uniqueIndex() + val outbox = varchar("outbox", length = 255).uniqueIndex() + val url = varchar("url", length = 255).uniqueIndex() + val publicKey = varchar("public_key", length = 10000) + val privateKey = varchar("private_key", length = 10000).nullable() + val createdAt = long("created_at") + + override val primaryKey: PrimaryKey = PrimaryKey(id) + + init { + uniqueIndex(name, domain) + } +} + +fun ResultRow.toUser(): User { + return User( + this[Users.id], + this[Users.name], + this[Users.domain], + this[Users.screenName], + this[Users.description], + this[Users.password], + this[Users.inbox], + this[Users.outbox], + this[Users.url], + this[Users.publicKey], + this[Users.privateKey], + Instant.ofEpochMilli((this[Users.createdAt])) + ) +} + +object UsersFollowers : LongIdTable("users_followers") { + val userId = long("user_id").references(Users.id).index() + val followerId = long("follower_id").references(Users.id) + + init { + uniqueIndex(userId, followerId) + } } diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/RegisterRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/RegisterRouting.kt index a6ba6d4c..c20221cc 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/RegisterRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/RegisterRouting.kt @@ -1,16 +1,15 @@ package dev.usbharu.hideout.routing -import dev.usbharu.hideout.plugins.UserSession -import dev.usbharu.hideout.service.IUserAuthService +import dev.usbharu.hideout.domain.model.hideout.dto.UserCreateDto +import dev.usbharu.hideout.service.impl.IUserService 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.* -import io.ktor.server.sessions.* -fun Application.register(userAuthService: IUserAuthService) { +fun Application.register(userService: IUserService) { routing { get("/register") { @@ -39,13 +38,10 @@ fun Application.register(userAuthService: IUserAuthService) { val parameters = call.receiveParameters() val password = parameters["password"] ?: return@post call.respondRedirect("/register") val username = parameters["username"] ?: return@post call.respondRedirect("/register") - if (userAuthService.usernameAlreadyUse(username)) { + if (userService.usernameAlreadyUse(username)) { return@post call.respondRedirect("/register") } - - val hash = userAuthService.hash(password) - userAuthService.registerAccount(username,hash) -// call.respondRedirect("/login") + userService.createLocalUser(UserCreateDto(username, username, "", password)) call.respondRedirect("/users/$username") } } diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt index 66fc511c..10401627 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt @@ -3,7 +3,7 @@ package dev.usbharu.hideout.routing.activitypub import dev.usbharu.hideout.exception.ParameterNotExistException import dev.usbharu.hideout.plugins.respondAp import dev.usbharu.hideout.service.activitypub.ActivityPubUserService -import dev.usbharu.hideout.service.impl.UserService +import dev.usbharu.hideout.service.impl.IUserService import dev.usbharu.hideout.util.HttpUtil.Activity import dev.usbharu.hideout.util.HttpUtil.JsonLd import io.ktor.http.* @@ -12,9 +12,11 @@ import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* -fun Routing.usersAP(activityPubUserService: ActivityPubUserService, userService: UserService) { +fun Routing.usersAP(activityPubUserService: ActivityPubUserService, userService: IUserService) { 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 = activityPubUserService.getPersonByName(name) @@ -24,7 +26,7 @@ fun Routing.usersAP(activityPubUserService: ActivityPubUserService, userService: ) } get { - val userEntity = userService.findByName(call.parameters["name"]!!) + val userEntity = userService.findByNameLocalUser(call.parameters["name"]!!) call.respondText(userEntity.toString() + "\n" + userService.findFollowersById(userEntity.id)) } } @@ -33,9 +35,11 @@ fun Routing.usersAP(activityPubUserService: ActivityPubUserService, userService: class ContentTypeRouteSelector(private vararg val contentType: ContentType) : RouteSelector() { override fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation { - val requestContentType = - ContentType.parse(context.call.request.accept() ?: return RouteSelectorEvaluation.FailedParameter) - return if (contentType.any { contentType -> contentType.match(requestContentType) }) { + context.call.application.log.debug("Accept: ${context.call.request.accept()}") + val requestContentType = context.call.request.accept() ?: return RouteSelectorEvaluation.FailedParameter + return if (requestContentType.split(",") + .find { contentType.find { contentType -> contentType.match(it) } != null } != null + ) { RouteSelectorEvaluation.Constant } else { RouteSelectorEvaluation.FailedParameter diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/wellknown/WebfingerRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/wellknown/WebfingerRouting.kt index f862f745..411e3095 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/wellknown/WebfingerRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/wellknown/WebfingerRouting.kt @@ -4,14 +4,14 @@ 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.impl.UserService +import dev.usbharu.hideout.service.impl.IUserService 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.* -fun Routing.webfinger(userService:UserService){ +fun Routing.webfinger(userService: IUserService){ route("/.well-known/webfinger"){ get { val acct = call.request.queryParameters["resource"]?.decodeURLPart() @@ -25,7 +25,7 @@ fun Routing.webfinger(userService:UserService){ .substringAfter("acct:") .trimStart('@') - val userEntity = userService.findByName(accountName) + val userEntity = userService.findByNameLocalUser(accountName) val webFinger = WebFinger( subject = acct, diff --git a/src/main/kotlin/dev/usbharu/hideout/service/IUserAuthService.kt b/src/main/kotlin/dev/usbharu/hideout/service/IUserAuthService.kt index d2096d37..5c792ec8 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/IUserAuthService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/IUserAuthService.kt @@ -1,18 +1,14 @@ package dev.usbharu.hideout.service -import dev.usbharu.hideout.domain.model.UserAuthentication -import dev.usbharu.hideout.domain.model.UserAuthenticationEntity +import java.security.KeyPair interface IUserAuthService { fun hash(password: String): String suspend fun usernameAlreadyUse(username: String): Boolean - suspend fun registerAccount(username: String, hash: String) + + suspend fun generateKeyPair():KeyPair suspend fun verifyAccount(username: String, password: String): Boolean - suspend fun findByUserId(userId: Long): UserAuthenticationEntity - - suspend fun findByUsername(username: String): UserAuthenticationEntity - suspend fun createAccount(userEntity: UserAuthentication): UserAuthenticationEntity } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImpl.kt index b734aba1..988cb81e 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImpl.kt @@ -1,14 +1,14 @@ package dev.usbharu.hideout.service.activitypub import com.fasterxml.jackson.module.kotlin.readValue -import dev.usbharu.hideout.domain.model.ap.Accept -import dev.usbharu.hideout.domain.model.ap.Follow import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.domain.model.ActivityPubResponse import dev.usbharu.hideout.domain.model.ActivityPubStringResponse +import dev.usbharu.hideout.domain.model.ap.Accept +import dev.usbharu.hideout.domain.model.ap.Follow import dev.usbharu.hideout.domain.model.job.ReceiveFollowJob import dev.usbharu.hideout.plugins.postAp -import dev.usbharu.hideout.service.impl.UserService +import dev.usbharu.hideout.service.impl.IUserService import dev.usbharu.hideout.service.job.JobQueueParentService import io.ktor.client.* import io.ktor.http.* @@ -17,7 +17,7 @@ import kjob.core.job.JobProps class ActivityPubFollowServiceImpl( private val jobQueueParentService: JobQueueParentService, private val activityPubUserService: ActivityPubUserService, - private val userService: UserService, + private val userService: IUserService, private val httpClient: HttpClient ) : ActivityPubFollowService { override suspend fun receiveFollow(follow: Follow): ActivityPubResponse { @@ -32,9 +32,9 @@ class ActivityPubFollowServiceImpl( override suspend fun receiveFollowJob(props: JobProps<ReceiveFollowJob>) { val actor = props[ReceiveFollowJob.actor] - val person = activityPubUserService.fetchPerson(actor) - val follow = Config.configData.objectMapper.readValue<Follow>(props[ReceiveFollowJob.follow]) val targetActor = props[ReceiveFollowJob.targetActor] + val person = activityPubUserService.fetchPerson(actor,targetActor) + val follow = Config.configData.objectMapper.readValue<Follow>(props[ReceiveFollowJob.follow]) httpClient.postAp( urlString = person.inbox ?: throw IllegalArgumentException("inbox is not found"), username = "$targetActor#pubkey", diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubNoteServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubNoteServiceImpl.kt index 445e5a48..72eb6acf 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubNoteServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubNoteServiceImpl.kt @@ -7,7 +7,7 @@ import dev.usbharu.hideout.domain.model.ap.Create import dev.usbharu.hideout.domain.model.ap.Note import dev.usbharu.hideout.domain.model.job.DeliverPostJob import dev.usbharu.hideout.plugins.postAp -import dev.usbharu.hideout.service.impl.UserService +import dev.usbharu.hideout.service.impl.IUserService import dev.usbharu.hideout.service.job.JobQueueParentService import io.ktor.client.* import kjob.core.job.JobProps @@ -17,7 +17,7 @@ import java.time.Instant class ActivityPubNoteServiceImpl( private val httpClient: HttpClient, private val jobQueueParentService: JobQueueParentService, - private val userService: UserService + private val userService: IUserService ) : ActivityPubNoteService { private val logger = LoggerFactory.getLogger(this::class.java) @@ -45,7 +45,7 @@ class ActivityPubNoteServiceImpl( attributedTo = actor, content = postEntity.text, published = Instant.ofEpochMilli(postEntity.createdAt).toString(), - to = listOf("https://www.w3.org/ns/activitystreams#Public", actor + "/followers") + to = listOf("https://www.w3.org/ns/activitystreams#Public", actor + "/follower") ) val inbox = props[DeliverPostJob.inbox] logger.debug("createNoteJob: actor={}, note={}, inbox={}", actor, postEntity, inbox) @@ -54,7 +54,9 @@ class ActivityPubNoteServiceImpl( username = "$actor#pubkey", jsonLd = Create( name = "Create Note", - `object` = note + `object` = note, + actor = note.attributedTo, + id = "${Config.configData.url}/create/note/${postEntity.id}" ) ) } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt index e86f967f..320359d3 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt @@ -33,7 +33,7 @@ class ActivityPubServiceImpl( return ActivityType.values().first { it.name.equals(type.asText(), true) } } - override suspend fun processActivity(json: String, type: ActivityType): ActivityPubResponse? { + override suspend fun processActivity(json: String, type: ActivityType): ActivityPubResponse { return when (type) { ActivityType.Accept -> TODO() ActivityType.Add -> TODO() diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserService.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserService.kt index a698670b..3446da24 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserService.kt @@ -5,5 +5,5 @@ import dev.usbharu.hideout.domain.model.ap.Person interface ActivityPubUserService { suspend fun getPersonByName(name:String): Person - suspend fun fetchPerson(url:String): Person + suspend fun fetchPerson(url: String, targetActor: String? = null): Person } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserServiceImpl.kt index 88272bdf..94c8557a 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserServiceImpl.kt @@ -1,16 +1,15 @@ package dev.usbharu.hideout.service.activitypub import com.fasterxml.jackson.module.kotlin.readValue +import dev.usbharu.hideout.config.Config 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.config.Config -import dev.usbharu.hideout.domain.model.User -import dev.usbharu.hideout.domain.model.UserAuthentication +import dev.usbharu.hideout.domain.model.hideout.dto.RemoteUserCreateDto import dev.usbharu.hideout.exception.UserNotFoundException import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException -import dev.usbharu.hideout.service.IUserAuthService -import dev.usbharu.hideout.service.impl.UserService +import dev.usbharu.hideout.plugins.getAp +import dev.usbharu.hideout.service.impl.IUserService import dev.usbharu.hideout.util.HttpUtil.Activity import io.ktor.client.* import io.ktor.client.request.* @@ -19,8 +18,7 @@ import io.ktor.http.* import org.slf4j.LoggerFactory class ActivityPubUserServiceImpl( - private val userService: UserService, - private val userAuthService: IUserAuthService, + private val userService: IUserService, private val httpClient: HttpClient ) : ActivityPubUserService { @@ -28,8 +26,7 @@ class ActivityPubUserServiceImpl( private val logger = LoggerFactory.getLogger(this::class.java) override suspend fun getPersonByName(name: String): Person { // TODO: JOINで書き直し - val userEntity = userService.findByName(name) - val userAuthEntity = userAuthService.findByUserId(userEntity.id) + val userEntity = userService.findByNameLocalUser(name) val userUrl = "${Config.configData.url}/users/$name" return Person( type = emptyList(), @@ -51,15 +48,14 @@ class ActivityPubUserServiceImpl( name = "Public Key", id = "$userUrl#pubkey", owner = userUrl, - publicKeyPem = userAuthEntity.publicKey + publicKeyPem = userEntity.publicKey ) ) } - override suspend fun fetchPerson(url: String): Person { + override suspend fun fetchPerson(url: String, targetActor: String?): Person { return try { val userEntity = userService.findByUrl(url) - val userAuthEntity = userAuthService.findByUsername(userEntity.name) return Person( type = emptyList(), name = userEntity.name, @@ -80,33 +76,31 @@ class ActivityPubUserServiceImpl( name = "Public Key", id = "$url#pubkey", owner = url, - publicKeyPem = userAuthEntity.publicKey + publicKeyPem = userEntity.publicKey ) ) } catch (e: UserNotFoundException) { - val httpResponse = httpClient.get(url) { - accept(ContentType.Application.Activity) + val httpResponse = if (targetActor != null) { + httpClient.getAp(url,"$targetActor#pubkey") + }else { + httpClient.get(url) { + accept(ContentType.Application.Activity) + } } val person = Config.configData.objectMapper.readValue<Person>(httpResponse.bodyAsText()) - val userEntity = userService.create( - User( + + userService.createRemoteUser( + RemoteUserCreateDto( name = person.preferredUsername ?: throw IllegalActivityPubObjectException("preferredUsername is null"), - domain = url.substringAfter(":").substringBeforeLast("/"), + domain = url.substringAfter("://").substringBeforeLast("/"), screenName = (person.name ?: person.preferredUsername) ?: throw IllegalActivityPubObjectException("preferredUsername is null"), description = person.summary ?: "", inbox = person.inbox ?: throw IllegalActivityPubObjectException("inbox is null"), outbox = person.outbox ?: throw IllegalActivityPubObjectException("outbox is null"), - url = url - ) - ) - userAuthService.createAccount( - UserAuthentication( - userEntity.id, - null, - person.publicKey?.publicKeyPem ?: throw IllegalActivityPubObjectException("publicKey is null"), - null + url = url, + publicKey = person.publicKey?.publicKeyPem ?: throw IllegalActivityPubObjectException("publicKey is null"), ) ) person diff --git a/src/main/kotlin/dev/usbharu/hideout/service/impl/IUserService.kt b/src/main/kotlin/dev/usbharu/hideout/service/impl/IUserService.kt new file mode 100644 index 00000000..b41f31fd --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/impl/IUserService.kt @@ -0,0 +1,33 @@ +package dev.usbharu.hideout.service.impl + +import dev.usbharu.hideout.domain.model.hideout.dto.RemoteUserCreateDto +import dev.usbharu.hideout.domain.model.hideout.dto.UserCreateDto +import dev.usbharu.hideout.domain.model.hideout.entity.User + +interface IUserService { + suspend fun findAll(limit: Int? = 100, offset: Long? = 0): List<User> + + suspend fun findById(id: Long): User + + suspend fun findByIds(ids: List<Long>): List<User> + + suspend fun findByName(name: String): List<User> + + suspend fun findByNameLocalUser(name: String): User + + suspend fun findByNameAndDomains(names: List<Pair<String, String>>): List<User> + + suspend fun findByUrl(url: String): User + + suspend fun findByUrls(urls: List<String>): List<User> + + suspend fun usernameAlreadyUse(username: String): Boolean + + suspend fun createLocalUser(user: UserCreateDto): User + + suspend fun createRemoteUser(user: RemoteUserCreateDto): User + + suspend fun findFollowersById(id: Long): List<User> + + suspend fun addFollowers(id: Long, follower: Long) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/impl/UserAuthService.kt b/src/main/kotlin/dev/usbharu/hideout/service/impl/UserAuthService.kt index 5542d1ee..60aa97a4 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/impl/UserAuthService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/impl/UserAuthService.kt @@ -1,23 +1,15 @@ package dev.usbharu.hideout.service.impl import dev.usbharu.hideout.config.Config -import dev.usbharu.hideout.domain.model.User -import dev.usbharu.hideout.domain.model.UserAuthentication -import dev.usbharu.hideout.domain.model.UserAuthenticationEntity -import dev.usbharu.hideout.domain.model.UserEntity import dev.usbharu.hideout.exception.UserNotFoundException -import dev.usbharu.hideout.repository.IUserAuthRepository import dev.usbharu.hideout.repository.IUserRepository import dev.usbharu.hideout.service.IUserAuthService import io.ktor.util.* import java.security.* -import java.security.interfaces.RSAPrivateKey -import java.security.interfaces.RSAPublicKey import java.util.* class UserAuthService( - val userRepository: IUserRepository, - val userAuthRepository: IUserAuthRepository + val userRepository: IUserRepository ) : IUserAuthService { @@ -31,59 +23,15 @@ class UserAuthService( return true } - override suspend fun registerAccount(username: String, hash: String) { - val url = "${Config.configData.url}/users/$username" - val registerUser = User( - name = username, - domain = Config.configData.domain, - screenName = username, - description = "", - inbox = "$url/inbox", - outbox = "$url/outbox", - url = url - ) - val createdUser = userRepository.create(registerUser) - - val keyPair = generateKeyPair() - val privateKey = keyPair.private as RSAPrivateKey - val publicKey = keyPair.public as RSAPublicKey - - - val userAuthentication = UserAuthentication( - createdUser.id, - hash, - publicKey.toPem(), - privateKey.toPem() - ) - - userAuthRepository.create(userAuthentication) - } - override suspend fun verifyAccount(username: String, password: String): Boolean { - val userEntity = userRepository.findByName(username) + val userEntity = userRepository.findByNameAndDomain(username, Config.configData.domain) ?: throw UserNotFoundException("$username was not found") - val userAuthEntity = userAuthRepository.findByUserId(userEntity.id) - ?: throw UserNotFoundException("$username auth data was not found") - return userAuthEntity.hash == hash(password) + return userEntity.password == hash(password) } - override suspend fun findByUserId(userId: Long): UserAuthenticationEntity { - return userAuthRepository.findByUserId(userId) ?: throw UserNotFoundException("$userId was not found") - } - - override suspend fun findByUsername(username: String): UserAuthenticationEntity { - val userEntity = userRepository.findByName(username) ?: throw UserNotFoundException("$username was not found") - return userAuthRepository.findByUserId(userEntity.id) - ?: throw UserNotFoundException("$username auth data was not found") - } - - override suspend fun createAccount(userEntity: UserAuthentication): UserAuthenticationEntity { - return userAuthRepository.create(userEntity) - } - - private fun generateKeyPair(): KeyPair { + override suspend fun generateKeyPair(): KeyPair { val keyPairGenerator = KeyPairGenerator.getInstance("RSA") - keyPairGenerator.initialize(1024) + keyPairGenerator.initialize(2048) return keyPairGenerator.generateKeyPair() } @@ -93,14 +41,14 @@ class UserAuthService( } } -public fun PublicKey.toPem(): String { +fun PublicKey.toPem(): String { return "-----BEGIN PUBLIC KEY-----\n" + Base64.getEncoder().encodeToString(encoded).chunked(64).joinToString("\n") + "\n-----END PUBLIC KEY-----\n" } -public fun PrivateKey.toPem(): String { - return "-----BEGIN PRIVATE KEY-----" + +fun PrivateKey.toPem(): String { + return "-----BEGIN PRIVATE KEY-----\n" + Base64.getEncoder().encodeToString(encoded).chunked(64).joinToString("\n") + "\n-----END PRIVATE KEY-----\n" } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/impl/UserService.kt b/src/main/kotlin/dev/usbharu/hideout/service/impl/UserService.kt index a64ccd04..fcf30dcb 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/impl/UserService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/impl/UserService.kt @@ -1,15 +1,20 @@ package dev.usbharu.hideout.service.impl -import dev.usbharu.hideout.domain.model.User -import dev.usbharu.hideout.domain.model.UserEntity +import dev.usbharu.hideout.config.Config +import dev.usbharu.hideout.domain.model.hideout.dto.RemoteUserCreateDto +import dev.usbharu.hideout.domain.model.hideout.dto.UserCreateDto +import dev.usbharu.hideout.domain.model.hideout.entity.User import dev.usbharu.hideout.exception.UserNotFoundException import dev.usbharu.hideout.repository.IUserRepository +import dev.usbharu.hideout.service.IUserAuthService import java.lang.Integer.min +import java.time.Instant -class UserService(private val userRepository: IUserRepository) { +class UserService(private val userRepository: IUserRepository, private val userAuthService: IUserAuthService) : + IUserService { private val maxLimit = 100 - suspend fun findAll(limit: Int? = maxLimit, offset: Long? = 0): List<UserEntity> { + override suspend fun findAll(limit: Int?, offset: Long?): List<User> { return userRepository.findAllByLimitAndByOffset( min(limit ?: maxLimit, maxLimit), @@ -17,40 +22,83 @@ class UserService(private val userRepository: IUserRepository) { ) } - suspend fun findById(id: Long): UserEntity { + override suspend fun findById(id: Long): User { return userRepository.findById(id) ?: throw UserNotFoundException("$id was not found.") } - suspend fun findByIds(ids: List<Long>): List<UserEntity> { + override suspend fun findByIds(ids: List<Long>): List<User> { return userRepository.findByIds(ids) } - suspend fun findByName(name: String): UserEntity { + override suspend fun findByName(name: String): List<User> { return userRepository.findByName(name) + } + + override suspend fun findByNameLocalUser(name: String): User { + return userRepository.findByNameAndDomain(name, Config.configData.domain) ?: throw UserNotFoundException("$name was not found.") } - suspend fun findByNameAndDomains(names: List<Pair<String,String>>): List<UserEntity> { + override suspend fun findByNameAndDomains(names: List<Pair<String, String>>): List<User> { return userRepository.findByNameAndDomains(names) } - suspend fun findByUrl(url: String): UserEntity { + override suspend fun findByUrl(url: String): User { return userRepository.findByUrl(url) ?: throw UserNotFoundException("$url was not found.") } - suspend fun findByUrls(urls: List<String>): List<UserEntity> { + override suspend fun findByUrls(urls: List<String>): List<User> { return userRepository.findByUrls(urls) } - suspend fun create(user: User): UserEntity { - return userRepository.create(user) + override suspend fun usernameAlreadyUse(username: String): Boolean { + val findByNameAndDomain = userRepository.findByNameAndDomain(username, Config.configData.domain) + return findByNameAndDomain != null } - suspend fun findFollowersById(id: Long): List<UserEntity> { + override suspend fun createLocalUser(user: UserCreateDto): User { + val nextId = userRepository.nextId() + val HashedPassword = userAuthService.hash(user.password) + val keyPair = userAuthService.generateKeyPair() + val userEntity = User( + id = nextId, + name = user.name, + domain = Config.configData.domain, + screenName = user.screenName, + description = user.description, + password = HashedPassword, + inbox = "${Config.configData.url}/users/${user.name}/inbox", + outbox = "${Config.configData.url}/users/${user.name}/outbox", + url = "${Config.configData.url}/users/${user.name}", + publicKey = keyPair.public.toPem(), + privateKey = keyPair.private.toPem(), + Instant.now() + ) + return userRepository.save(userEntity) + } + + override suspend fun createRemoteUser(user: RemoteUserCreateDto): User { + val nextId = userRepository.nextId() + val userEntity = User( + id = nextId, + name = user.name, + domain = user.domain, + screenName = user.screenName, + description = user.description, + inbox = user.inbox, + outbox = user.outbox, + url = user.url, + publicKey = user.publicKey, + createdAt = Instant.now() + ) + return userRepository.save(userEntity) + } + + override suspend fun findFollowersById(id: Long): List<User> { return userRepository.findFollowersById(id) } - suspend fun addFollowers(id: Long, follower: Long) { + override suspend fun addFollowers(id: Long, follower: Long) { return userRepository.createFollower(id, follower) } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureVerifyServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureVerifyServiceImpl.kt index 74525981..5c74f11d 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureVerifyServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureVerifyServiceImpl.kt @@ -1,15 +1,16 @@ package dev.usbharu.hideout.service.signature import dev.usbharu.hideout.plugins.KtorKeyMap +import dev.usbharu.hideout.repository.IUserRepository import dev.usbharu.hideout.service.IUserAuthService import io.ktor.http.* import tech.barbero.http.message.signing.HttpMessage import tech.barbero.http.message.signing.SignatureHeaderVerifier -class HttpSignatureVerifyServiceImpl(private val userAuthService: IUserAuthService) : HttpSignatureVerifyService { +class HttpSignatureVerifyServiceImpl(private val userAuthService: IUserRepository) : HttpSignatureVerifyService { override fun verify(headers: Headers): Boolean { val build = SignatureHeaderVerifier.builder().keyMap(KtorKeyMap(userAuthService)).build() - return true; + return true build.verify(object : HttpMessage { override fun headerValues(name: String?): MutableList<String> { return name?.let { headers.getAll(it) }?.toMutableList() ?: mutableListOf() diff --git a/src/main/kotlin/dev/usbharu/hideout/util/HttpUtil.kt b/src/main/kotlin/dev/usbharu/hideout/util/HttpUtil.kt index 979b8302..90773182 100644 --- a/src/main/kotlin/dev/usbharu/hideout/util/HttpUtil.kt +++ b/src/main/kotlin/dev/usbharu/hideout/util/HttpUtil.kt @@ -30,6 +30,10 @@ object HttpUtil { get() = ContentType("application", "activity+json") val ContentType.Application.JsonLd: ContentType - get() = ContentType("application", "ld+json", listOf(HeaderValueParam("profile", "https://www.w3.org/ns/activitystreams"))) + get() = ContentType( + "application", + "ld+json", + listOf(HeaderValueParam("profile", "https://www.w3.org/ns/activitystreams")) + ) // fun } diff --git a/src/main/kotlin/dev/usbharu/kjob/exposed/ExposedJobRepository.kt b/src/main/kotlin/dev/usbharu/kjob/exposed/ExposedJobRepository.kt index 6533358a..0c7f66dd 100644 --- a/src/main/kotlin/dev/usbharu/kjob/exposed/ExposedJobRepository.kt +++ b/src/main/kotlin/dev/usbharu/kjob/exposed/ExposedJobRepository.kt @@ -14,7 +14,6 @@ import org.jetbrains.exposed.dao.id.LongIdTable import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList -import org.jetbrains.exposed.sql.SqlExpressionBuilder.isNull import org.jetbrains.exposed.sql.SqlExpressionBuilder.plus import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction import org.jetbrains.exposed.sql.transactions.transaction diff --git a/src/main/web/App.tsx b/src/main/web/App.tsx index 32a849b2..d1b57e50 100644 --- a/src/main/web/App.tsx +++ b/src/main/web/App.tsx @@ -1,5 +1,5 @@ import {Component} from "solid-js"; -export const App:Component = () => { +export const App: Component = () => { return (<p>aaa</p>) } diff --git a/src/main/web/index.html b/src/main/web/index.html index 856bb5e6..46837b9c 100644 --- a/src/main/web/index.html +++ b/src/main/web/index.html @@ -1,10 +1,10 @@ <!DOCTYPE html> <html lang="en"> <head> - <meta charset="utf-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1" /> - <meta name="theme-color" content="#000000" /> - <title>Solid App</title> + <meta charset="utf-8"/> + <meta content="width=device-width, initial-scale=1" name="viewport"/> + <meta content="#000000" name="theme-color"/> + <title>Solid App</title> </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> diff --git a/src/test/kotlin/dev/usbharu/hideout/plugins/ActivityPubKtTest.kt b/src/test/kotlin/dev/usbharu/hideout/plugins/ActivityPubKtTest.kt index b5690c99..63ff36cf 100644 --- a/src/test/kotlin/dev/usbharu/hideout/plugins/ActivityPubKtTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/plugins/ActivityPubKtTest.kt @@ -1,13 +1,8 @@ package dev.usbharu.hideout.plugins import dev.usbharu.hideout.domain.model.ap.JsonLd -import dev.usbharu.hideout.domain.model.User -import dev.usbharu.hideout.domain.model.UserAuthentication -import dev.usbharu.hideout.domain.model.UserAuthenticationEntity -import dev.usbharu.hideout.domain.model.UserEntity -import dev.usbharu.hideout.repository.IUserAuthRepository +import dev.usbharu.hideout.domain.model.hideout.entity.User import dev.usbharu.hideout.repository.IUserRepository -import dev.usbharu.hideout.service.impl.UserAuthService import dev.usbharu.hideout.service.impl.toPem import io.ktor.client.* import io.ktor.client.engine.mock.* @@ -15,43 +10,62 @@ import io.ktor.client.plugins.logging.* import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Test import java.security.KeyPairGenerator -import java.security.interfaces.RSAPrivateKey -import java.security.interfaces.RSAPublicKey +import java.time.Instant class ActivityPubKtTest { @Test fun HttpSignTest(): Unit = runBlocking { - val ktorKeyMap = KtorKeyMap(UserAuthService(object : IUserRepository { - override suspend fun create(user: User): UserEntity { + val ktorKeyMap = KtorKeyMap(object : IUserRepository { + override suspend fun save(user: User): User { TODO("Not yet implemented") } - override suspend fun findById(id: Long): UserEntity? { + override suspend fun findById(id: Long): User? { TODO("Not yet implemented") } - override suspend fun findByIds(ids: List<Long>): List<UserEntity> { + override suspend fun findByIds(ids: List<Long>): List<User> { TODO("Not yet implemented") } - override suspend fun findByName(name: String): UserEntity? { - return UserEntity(1, "test", "localhost", "test", "","","","") + override suspend fun findByName(name: String): List<User> { + TODO() } - override suspend fun findByNameAndDomains(names: List<Pair<String, String>>): List<UserEntity> { + override suspend fun findByNameAndDomain(name: String, domain: String): User? { + val keyPairGenerator = KeyPairGenerator.getInstance("RSA") + keyPairGenerator.initialize(1024) + val generateKeyPair = keyPairGenerator.generateKeyPair() + return User( + 1, + "test", + "localhost", + "test", + "", + "", + "", + "", + "", + "", + generateKeyPair.private.toPem(), + Instant.now() + ) + } + + override suspend fun findByDomain(domain: String): List<User> { TODO("Not yet implemented") } - override suspend fun findByUrl(url: String): UserEntity? { + override suspend fun findByNameAndDomains(names: List<Pair<String, String>>): List<User> { TODO("Not yet implemented") } - override suspend fun findByUrls(urls: List<String>): List<UserEntity> { + override suspend fun findByUrl(url: String): User? { TODO("Not yet implemented") } - override suspend fun update(userEntity: UserEntity) { + override suspend fun findByUrls(urls: List<String>): List<User> { TODO("Not yet implemented") } @@ -63,7 +77,7 @@ class ActivityPubKtTest { TODO("Not yet implemented") } - override suspend fun findAllByLimitAndByOffset(limit: Int, offset: Long): List<UserEntity> { + override suspend fun findAllByLimitAndByOffset(limit: Int, offset: Long): List<User> { TODO("Not yet implemented") } @@ -75,37 +89,15 @@ class ActivityPubKtTest { TODO("Not yet implemented") } - override suspend fun findFollowersById(id: Long): List<UserEntity> { + override suspend fun findFollowersById(id: Long): List<User> { TODO("Not yet implemented") } - }, object : IUserAuthRepository { - override suspend fun create(userAuthentication: UserAuthentication): UserAuthenticationEntity { + override suspend fun nextId(): Long { TODO("Not yet implemented") } - override suspend fun findById(id: Long): UserAuthenticationEntity? { - TODO("Not yet implemented") - } - - override suspend fun update(userAuthenticationEntity: UserAuthenticationEntity) { - TODO("Not yet implemented") - } - - override suspend fun delete(id: Long) { - TODO("Not yet implemented") - } - - override suspend fun findByUserId(id: Long): UserAuthenticationEntity? { - val keyPairGenerator = KeyPairGenerator.getInstance("RSA") - keyPairGenerator.initialize(1024) - val generateKeyPair = keyPairGenerator.generateKeyPair() - return UserAuthenticationEntity( - 1, 1, "test", (generateKeyPair.public as RSAPublicKey).toPem(), - (generateKeyPair.private as RSAPrivateKey).toPem() - ) - } - })) + }) val httpClient = HttpClient(MockEngine { httpRequestData -> respondOk() diff --git a/src/test/kotlin/dev/usbharu/hideout/plugins/KtorKeyMapTest.kt b/src/test/kotlin/dev/usbharu/hideout/plugins/KtorKeyMapTest.kt index 02cfb9b4..ad462b04 100644 --- a/src/test/kotlin/dev/usbharu/hideout/plugins/KtorKeyMapTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/plugins/KtorKeyMapTest.kt @@ -1,52 +1,67 @@ package dev.usbharu.hideout.plugins -import dev.usbharu.hideout.domain.model.User -import dev.usbharu.hideout.domain.model.UserAuthentication -import dev.usbharu.hideout.domain.model.UserAuthenticationEntity -import dev.usbharu.hideout.domain.model.UserEntity -import dev.usbharu.hideout.repository.IUserAuthRepository +import dev.usbharu.hideout.domain.model.hideout.entity.User import dev.usbharu.hideout.repository.IUserRepository -import dev.usbharu.hideout.service.impl.UserAuthService import dev.usbharu.hideout.service.impl.toPem import org.junit.jupiter.api.Test import java.security.KeyPairGenerator -import java.security.interfaces.RSAPrivateKey -import java.security.interfaces.RSAPublicKey +import java.time.Instant class KtorKeyMapTest { @Test fun getPrivateKey() { - val ktorKeyMap = KtorKeyMap(UserAuthService(object : IUserRepository { - override suspend fun create(user: User): UserEntity { + val ktorKeyMap = KtorKeyMap(object : IUserRepository { + override suspend fun save(user: User): User { TODO("Not yet implemented") } - override suspend fun findById(id: Long): UserEntity? { + override suspend fun findById(id: Long): User? { TODO("Not yet implemented") } - override suspend fun findByIds(ids: List<Long>): List<UserEntity> { + override suspend fun findByIds(ids: List<Long>): List<User> { TODO("Not yet implemented") } - override suspend fun findByName(name: String): UserEntity? { - return UserEntity(1, "test", "localhost", "test", "","","","") + override suspend fun findByName(name: String): List<User> { + TODO() } - override suspend fun findByNameAndDomains(names: List<Pair<String, String>>): List<UserEntity> { + override suspend fun findByNameAndDomain(name: String, domain: String): User? { + val keyPairGenerator = KeyPairGenerator.getInstance("RSA") + keyPairGenerator.initialize(1024) + val generateKeyPair = keyPairGenerator.generateKeyPair() + return User( + 1, + "test", + "localhost", + "test", + "", + "", + "", + "", + "", + "", + generateKeyPair.private.toPem(), + createdAt = Instant.now() + ) + + } + + override suspend fun findByDomain(domain: String): List<User> { TODO("Not yet implemented") } - override suspend fun findByUrl(url: String): UserEntity? { + override suspend fun findByNameAndDomains(names: List<Pair<String, String>>): List<User> { TODO("Not yet implemented") } - override suspend fun findByUrls(urls: List<String>): List<UserEntity> { + override suspend fun findByUrl(url: String): User? { TODO("Not yet implemented") } - override suspend fun update(userEntity: UserEntity) { + override suspend fun findByUrls(urls: List<String>): List<User> { TODO("Not yet implemented") } @@ -58,7 +73,7 @@ class KtorKeyMapTest { TODO("Not yet implemented") } - override suspend fun findAllByLimitAndByOffset(limit: Int, offset: Long): List<UserEntity> { + override suspend fun findAllByLimitAndByOffset(limit: Int, offset: Long): List<User> { TODO("Not yet implemented") } @@ -70,37 +85,15 @@ class KtorKeyMapTest { TODO("Not yet implemented") } - override suspend fun findFollowersById(id: Long): List<UserEntity> { + override suspend fun findFollowersById(id: Long): List<User> { TODO("Not yet implemented") } - }, object : IUserAuthRepository { - override suspend fun create(userAuthentication: UserAuthentication): UserAuthenticationEntity { + override suspend fun nextId(): Long { TODO("Not yet implemented") } - override suspend fun findById(id: Long): UserAuthenticationEntity? { - TODO("Not yet implemented") - } - - override suspend fun update(userAuthenticationEntity: UserAuthenticationEntity) { - TODO("Not yet implemented") - } - - override suspend fun delete(id: Long) { - TODO("Not yet implemented") - } - - override suspend fun findByUserId(id: Long): UserAuthenticationEntity? { - val keyPairGenerator = KeyPairGenerator.getInstance("RSA") - keyPairGenerator.initialize(1024) - val generateKeyPair = keyPairGenerator.generateKeyPair() - return UserAuthenticationEntity( - 1, 1, "test", (generateKeyPair.public as RSAPublicKey).toPem(), - (generateKeyPair.private as RSAPrivateKey).toPem() - ) - } - })) + }) ktorKeyMap.getPrivateKey("test") } diff --git a/src/test/kotlin/dev/usbharu/hideout/repository/UserRepositoryTest.kt b/src/test/kotlin/dev/usbharu/hideout/repository/UserRepositoryTest.kt index 740bc90e..36bbcf4d 100644 --- a/src/test/kotlin/dev/usbharu/hideout/repository/UserRepositoryTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/repository/UserRepositoryTest.kt @@ -2,9 +2,8 @@ package dev.usbharu.hideout.repository -import dev.usbharu.hideout.domain.model.User -import dev.usbharu.hideout.domain.model.Users -import dev.usbharu.hideout.domain.model.UsersFollowers +import dev.usbharu.hideout.domain.model.hideout.entity.User +import dev.usbharu.hideout.service.IdGenerateService import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.jetbrains.exposed.sql.Database @@ -15,6 +14,9 @@ import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertIterableEquals import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import java.time.Clock +import java.time.Instant +import java.time.ZoneId class UserRepositoryTest { @@ -41,38 +43,54 @@ class UserRepositoryTest { @Test fun `findFollowersById フォロワー一覧を取得`() = runTest { - val userRepository = UserRepository(db) - val user = userRepository.create( + val userRepository = UserRepository(db, object : IdGenerateService { + override suspend fun generateId(): Long { + TODO("Not yet implemented") + } + }) + val user = userRepository.save( User( - "test", - "example.com", - "testUser", - "This user is test user.", - "https://example.com/inbox", - "https://example.com/outbox", - "https://example.com" + id = 0L, + name = "test", + domain = "example.com", + screenName = "testUser", + description = "This user is test user.", + password = "https://example.com/inbox", + inbox = "", + outbox = "https://example.com/outbox", + url = "https://example.com", + publicKey = "", + createdAt = Instant.now(Clock.tickMillis(ZoneId.systemDefault())) ) ) - val follower = userRepository.create( + val follower = userRepository.save( User( - "follower", - "follower.example.com", - "followerUser", - "This user is follower user.", - "https://follower.example.com/inbox", - "https://follower.example.com/outbox", - "https://follower.example.com" + id = 1L, + name = "follower", + domain = "follower.example.com", + screenName = "followerUser", + description = "This user is follower user.", + password = "", + inbox = "https://follower.example.com/inbox", + outbox = "https://follower.example.com/outbox", + url = "https://follower.example.com", + publicKey = "", + createdAt = Instant.now(Clock.tickMillis(ZoneId.systemDefault())) ) ) - val follower2 = userRepository.create( + val follower2 = userRepository.save( User( - "follower2", - "follower2.example.com", - "followerUser2", - "This user is follower user 2.", - "https://follower2.example.com/inbox", - "https://follower2.example.com/outbox", - "https://follower2.example.com" + id = 3L, + name = "follower2", + domain = "follower2.example.com", + screenName = "followerUser2", + description = "This user is follower user 2.", + password = "", + inbox = "https://follower2.example.com/inbox", + outbox = "https://follower2.example.com/outbox", + url = "https://follower2.example.com", + publicKey = "", + createdAt = Instant.now(Clock.tickMillis(ZoneId.systemDefault())) ) ) userRepository.createFollower(user.id, follower.id) @@ -85,27 +103,37 @@ class UserRepositoryTest { @Test fun `createFollower フォロワー追加`() = runTest { - val userRepository = UserRepository(db) - val user = userRepository.create( - User( + val userRepository = UserRepository(db, object : IdGenerateService { + override suspend fun generateId(): Long { + TODO("Not yet implemented") + } + }) + val user = userRepository.save( + User(0L, "test", "example.com", "testUser", "This user is test user.", "https://example.com/inbox", + "", "https://example.com/outbox", - "https://example.com" + "https://example.com", + publicKey = "", + createdAt = Instant.now() ) ) - val follower = userRepository.create( - User( + val follower = userRepository.save( + User(1L, "follower", "follower.example.com", "followerUser", "This user is follower user.", + "", "https://follower.example.com/inbox", "https://follower.example.com/outbox", - "https://follower.example.com" + "https://follower.example.com", + publicKey = "", + createdAt = Instant.now() ) ) userRepository.createFollower(user.id, follower.id) diff --git a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRoutingKtTest.kt b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRoutingKtTest.kt index 5fa97051..94ee5880 100644 --- a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRoutingKtTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRoutingKtTest.kt @@ -6,6 +6,7 @@ import dev.usbharu.hideout.plugins.configureSerialization import dev.usbharu.hideout.plugins.configureStatusPages import dev.usbharu.hideout.service.activitypub.ActivityPubService import dev.usbharu.hideout.service.activitypub.ActivityPubUserService +import dev.usbharu.hideout.service.impl.IUserService import dev.usbharu.hideout.service.impl.UserService import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService import io.ktor.client.request.* @@ -45,7 +46,7 @@ class InboxRoutingKtTest { val activityPubService = mock<ActivityPubService>{ on { parseActivity(any()) } doThrow JsonParseException() } - val userService = mock<UserService>() + val userService = mock<IUserService>() val activityPubUserService = mock<ActivityPubUserService>() application { configureStatusPages() @@ -82,7 +83,7 @@ class InboxRoutingKtTest { val activityPubService = mock<ActivityPubService>{ on { parseActivity(any()) } doThrow JsonParseException() } - val userService = mock<UserService>() + val userService = mock<IUserService>() val activityPubUserService = mock<ActivityPubUserService>() application { configureStatusPages() diff --git a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt index fd537617..0e997f43 100644 --- a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt @@ -9,13 +9,15 @@ 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.configureRouting import dev.usbharu.hideout.plugins.configureSerialization import dev.usbharu.hideout.service.activitypub.ActivityPubService import dev.usbharu.hideout.service.activitypub.ActivityPubUserService -import dev.usbharu.hideout.service.impl.UserService +import dev.usbharu.hideout.service.impl.IUserService import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService 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.* @@ -24,8 +26,11 @@ 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 java.time.Instant import kotlin.test.assertEquals +import kotlin.test.assertTrue class UsersAPTest { @@ -62,7 +67,7 @@ class UsersAPTest { val httpSignatureVerifyService = mock<HttpSignatureVerifyService> {} val activityPubService = mock<ActivityPubService> {} - val userService = mock<UserService> {} + val userService = mock<IUserService> {} val activityPubUserService = mock<ActivityPubUserService> { onBlocking { getPersonByName(anyString()) } doReturn person @@ -70,7 +75,13 @@ class UsersAPTest { application { configureSerialization() - configureRouting(httpSignatureVerifyService, activityPubService, userService, activityPubUserService,mock()) + configureRouting( + httpSignatureVerifyService, + activityPubService, + userService, + activityPubUserService, + mock() + ) } client.get("/users/test") { accept(ContentType.Application.Activity) @@ -88,4 +99,121 @@ class UsersAPTest { assertEquals(person, readValue) } } + + @Test() +// @Disabled + 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 httpSignatureVerifyService = mock<HttpSignatureVerifyService> {} + val activityPubService = mock<ActivityPubService> {} + val userService = mock<IUserService> {} + + val activityPubUserService = mock<ActivityPubUserService> { + onBlocking { getPersonByName(anyString()) } doReturn person + } + + application { + configureSerialization() + configureRouting( + httpSignatureVerifyService, + activityPubService, + userService, + activityPubUserService, + mock() + ) + } + 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) + } + } + + @Test +// @Disabled + 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<IUserService> { + onBlocking { findByNameLocalUser(eq("test")) } doReturn User( + 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 { + configureRouting( + mock(), + mock(), + userService, + mock(), + mock() + ) + } + client.get("/users/test") { + accept(ContentType.Text.Html) + }.let { + assertEquals(HttpStatusCode.OK, it.status) + assertTrue(it.contentType()?.match(ContentType.Text.Plain) == true) + } + } } diff --git a/src/test/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImplTest.kt b/src/test/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImplTest.kt index c5a0bb5f..a8eae660 100644 --- a/src/test/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImplTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImplTest.kt @@ -6,10 +6,10 @@ package dev.usbharu.hideout.service.activitypub 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.UserEntity import dev.usbharu.hideout.domain.model.ap.* +import dev.usbharu.hideout.domain.model.hideout.entity.User import dev.usbharu.hideout.domain.model.job.ReceiveFollowJob -import dev.usbharu.hideout.service.impl.UserService +import dev.usbharu.hideout.service.impl.IUserService import dev.usbharu.hideout.service.job.JobQueueParentService import io.ktor.client.* import io.ktor.client.engine.mock.* @@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test import org.mockito.ArgumentMatchers.anyString import org.mockito.kotlin.* import utils.JsonObjectMapper +import java.time.Instant class ActivityPubFollowServiceImplTest { @Test @@ -49,8 +50,9 @@ class ActivityPubFollowServiceImplTest { val follow = scheduleContext.props.props[ReceiveFollowJob.follow.name] assertEquals("https://follower.example.com", actor) assertEquals("https://example.com", targetActor) + //language=JSON assertEquals( - """{"type":"Follow","name":"Follow","object":"https://example.com","actor":"https://follower.example.com","@context":null}""", + """{"type":"Follow","name":"Follow","actor":"https://follower.example.com","object":"https://example.com","@context":null}""", follow ) } @@ -84,11 +86,11 @@ class ActivityPubFollowServiceImplTest { ) val activityPubUserService = mock<ActivityPubUserService> { - onBlocking { fetchPerson(anyString()) } doReturn person + onBlocking { fetchPerson(anyString(), any()) } doReturn person } - val userService = mock<UserService> { + val userService = mock<IUserService> { onBlocking { findByUrls(any()) } doReturn listOf( - UserEntity( + User( id = 1L, name = "test", domain = "example.com", @@ -96,9 +98,11 @@ class ActivityPubFollowServiceImplTest { description = "This user is test user.", inbox = "https://example.com/inbox", outbox = "https://example.com/outbox", - url = "https://example.com" + url = "https://example.com", + publicKey = "", + createdAt = Instant.now() ), - UserEntity( + User( id = 2L, name = "follower", domain = "follower.example.com", @@ -106,7 +110,9 @@ class ActivityPubFollowServiceImplTest { description = "This user is test follower user.", inbox = "https://follower.example.com/inbox", outbox = "https://follower.example.com/outbox", - url = "https://follower.example.com" + url = "https://follower.example.com", + publicKey = "", + createdAt = Instant.now() ) ) onBlocking { addFollowers(any(), any()) } doReturn Unit diff --git a/src/test/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubNoteServiceImplTest.kt b/src/test/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubNoteServiceImplTest.kt index 8d820be6..bb15ffde 100644 --- a/src/test/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubNoteServiceImplTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubNoteServiceImplTest.kt @@ -6,9 +6,9 @@ package dev.usbharu.hideout.service.activitypub import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.config.ConfigData import dev.usbharu.hideout.domain.model.PostEntity -import dev.usbharu.hideout.domain.model.UserEntity +import dev.usbharu.hideout.domain.model.hideout.entity.User import dev.usbharu.hideout.domain.model.job.DeliverPostJob -import dev.usbharu.hideout.service.impl.UserService +import dev.usbharu.hideout.service.impl.IUserService import dev.usbharu.hideout.service.job.JobQueueParentService import io.ktor.client.* import io.ktor.client.engine.mock.* @@ -20,13 +20,14 @@ import org.junit.jupiter.api.Test import org.mockito.Mockito.eq import org.mockito.kotlin.* import utils.JsonObjectMapper +import java.time.Instant import kotlin.test.assertEquals class ActivityPubNoteServiceImplTest { @Test fun `createPost 新しい投稿`() = runTest { - val followers = listOf<UserEntity>( - UserEntity( + val followers = listOf<User>( + User( 2L, "follower", "follower.example.com", @@ -34,9 +35,12 @@ class ActivityPubNoteServiceImplTest { "test follower user", "https://follower.example.com/inbox", "https://follower.example.com/outbox", - "https://follower.example.com" + "https://follower.example.com", + "", + publicKey = "", + createdAt = Instant.now() ), - UserEntity( + User( 3L, "follower2", "follower2.example.com", @@ -44,11 +48,14 @@ class ActivityPubNoteServiceImplTest { "test follower2 user", "https://follower2.example.com/inbox", "https://follower2.example.com/outbox", - "https:.//follower2.example.com" + "https://follower2.example.com", + "", + publicKey = "", + createdAt = Instant.now() ) ) - val userService = mock<UserService> { - onBlocking { findById(eq(1L)) } doReturn UserEntity( + val userService = mock<IUserService> { + onBlocking { findById(eq(1L)) } doReturn User( 1L, "test", "example.com", @@ -56,7 +63,10 @@ class ActivityPubNoteServiceImplTest { "test user", "https://example.com/inbox", "https://example.com/outbox", - "https:.//example.com" + "https:.//example.com", + "", + publicKey = "", + createdAt = Instant.now() ) onBlocking { findFollowersById(eq(1L)) } doReturn followers } diff --git a/src/test/kotlin/dev/usbharu/hideout/service/impl/UserServiceTest.kt b/src/test/kotlin/dev/usbharu/hideout/service/impl/UserServiceTest.kt new file mode 100644 index 00000000..31967bbe --- /dev/null +++ b/src/test/kotlin/dev/usbharu/hideout/service/impl/UserServiceTest.kt @@ -0,0 +1,88 @@ +@file:OptIn(ExperimentalCoroutinesApi::class) + +package dev.usbharu.hideout.service.impl + +import dev.usbharu.hideout.config.Config +import dev.usbharu.hideout.config.ConfigData +import dev.usbharu.hideout.domain.model.hideout.dto.RemoteUserCreateDto +import dev.usbharu.hideout.domain.model.hideout.dto.UserCreateDto +import dev.usbharu.hideout.repository.IUserRepository +import dev.usbharu.hideout.service.IUserAuthService +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test +import org.mockito.ArgumentMatchers.anyString +import org.mockito.kotlin.* +import java.security.KeyPairGenerator +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class UserServiceTest{ + @Test + fun `createLocalUser ローカルユーザーを作成できる`() = runTest { + Config.configData = ConfigData(domain = "example.com", url = "https://example.com") + val userRepository = mock<IUserRepository> { + onBlocking { nextId() } doReturn 110001L + } + val generateKeyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair() + val userAuthService = mock<IUserAuthService> { + onBlocking { hash(anyString()) } doReturn "hashedPassword" + onBlocking { generateKeyPair() } doReturn generateKeyPair + } + val userService = UserService(userRepository, userAuthService) + userService.createLocalUser(UserCreateDto("test", "testUser", "XXXXXXXXXXXXX", "test")) + verify(userRepository, times(1)).save(any()) + argumentCaptor<dev.usbharu.hideout.domain.model.hideout.entity.User> { + verify(userRepository, times(1)).save(capture()) + assertEquals("test", firstValue.name) + assertEquals("testUser", firstValue.screenName) + assertEquals("XXXXXXXXXXXXX", firstValue.description) + assertEquals("hashedPassword", firstValue.password) + assertEquals(110001L, firstValue.id) + assertEquals("https://example.com/users/test", firstValue.url) + assertEquals("example.com", firstValue.domain) + assertEquals("https://example.com/users/test/inbox", firstValue.inbox) + assertEquals("https://example.com/users/test/outbox", firstValue.outbox) + assertEquals(generateKeyPair.public.toPem(),firstValue.publicKey) + assertEquals(generateKeyPair.private.toPem(),firstValue.privateKey) + } + } + + @Test + fun `createRemoteUser リモートユーザーを作成できる`() = runTest { + + Config.configData = ConfigData(domain = "example.com", url = "https://example.com") + + + val userRepository = mock<IUserRepository>{ + onBlocking { nextId() } doReturn 113345L + } + val userService = UserService(userRepository,mock()) + val user = RemoteUserCreateDto( + "test", + "example.com", + "testUser", + "test user", + "https://example.com/inbox", + "https://example.com/outbox", + "https://example.com", + "-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----" + ) + userService.createRemoteUser(user) + verify(userRepository, times(1)).save(any()) + argumentCaptor<dev.usbharu.hideout.domain.model.hideout.entity.User> { + verify(userRepository, times(1)).save(capture()) + assertEquals("test", firstValue.name) + assertEquals("testUser", firstValue.screenName) + assertEquals("test user", firstValue.description) + assertNull(firstValue.password) + assertEquals(113345L, firstValue.id) + assertEquals("https://example.com", firstValue.url) + assertEquals("example.com", firstValue.domain) + assertEquals("https://example.com/inbox", firstValue.inbox) + assertEquals("https://example.com/outbox", firstValue.outbox) + assertEquals("-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----",firstValue.publicKey) + assertNull(firstValue.privateKey) + } + } +}