mirror of https://github.com/usbharu/Hideout.git
feat: OAuth2が動くように
This commit is contained in:
parent
0e00e9526d
commit
834e40894b
|
@ -39,3 +39,4 @@ out/
|
|||
/node_modules/
|
||||
/src/main/web/generated/
|
||||
/stats.html
|
||||
/tomcat/
|
||||
|
|
|
@ -172,6 +172,8 @@ dependencies {
|
|||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
|
||||
testImplementation("org.springframework.boot:spring-boot-test-autoconfigure")
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
|
||||
|
||||
implementation("io.ktor:ktor-client-logging-jvm:$ktor_version")
|
||||
implementation("io.ktor:ktor-server-host-common-jvm:$ktor_version")
|
||||
|
|
|
@ -6,11 +6,11 @@ import com.nimbusds.jose.jwk.source.ImmutableJWKSet
|
|||
import com.nimbusds.jose.jwk.source.JWKSource
|
||||
import com.nimbusds.jose.proc.SecurityContext
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
||||
import org.springframework.boot.autoconfigure.security.servlet.PathRequest
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.core.annotation.Order
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.security.config.Customizer
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
|
@ -21,7 +21,8 @@ import org.springframework.security.oauth2.server.authorization.config.annotatio
|
|||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings
|
||||
import org.springframework.security.web.SecurityFilterChain
|
||||
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint
|
||||
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher
|
||||
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher
|
||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.interfaces.RSAPrivateKey
|
||||
import java.security.interfaces.RSAPublicKey
|
||||
|
@ -37,10 +38,7 @@ class SecurityConfig {
|
|||
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http)
|
||||
http
|
||||
.exceptionHandling {
|
||||
it.defaultAuthenticationEntryPointFor(
|
||||
LoginUrlAuthenticationEntryPoint("/login"),
|
||||
MediaTypeRequestMatcher(MediaType.TEXT_HTML)
|
||||
)
|
||||
it.authenticationEntryPoint(LoginUrlAuthenticationEntryPoint("/login"))
|
||||
}
|
||||
.oauth2ResourceServer {
|
||||
it.jwt(Customizer.withDefaults())
|
||||
|
@ -53,23 +51,33 @@ class SecurityConfig {
|
|||
|
||||
@Bean
|
||||
@Order(2)
|
||||
fun defaultSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
fun defaultSecurityFilterChain(http: HttpSecurity, introspector: HandlerMappingIntrospector): SecurityFilterChain {
|
||||
val builder = MvcRequestMatcher.Builder(introspector)
|
||||
|
||||
|
||||
http
|
||||
.authorizeHttpRequests {
|
||||
it.requestMatchers(
|
||||
"/inbox",
|
||||
"/users/*/inbox",
|
||||
"/outbox",
|
||||
"/users/*/outbox"
|
||||
)
|
||||
.permitAll()
|
||||
builder.pattern("/inbox"),
|
||||
builder.pattern("/api/v1/apps"),
|
||||
builder.pattern("/api/v1/instance/**")
|
||||
).permitAll()
|
||||
}
|
||||
.authorizeHttpRequests {
|
||||
it.requestMatchers(PathRequest.toH2Console()).permitAll()
|
||||
}
|
||||
.authorizeHttpRequests {
|
||||
it.anyRequest().authenticated()
|
||||
}
|
||||
.formLogin(Customizer.withDefaults())
|
||||
.csrf {
|
||||
it.disable()
|
||||
it.ignoringRequestMatchers(builder.pattern("/api/**"))
|
||||
it.ignoringRequestMatchers(PathRequest.toH2Console())
|
||||
}
|
||||
.headers {
|
||||
it.frameOptions {
|
||||
it.sameOrigin()
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
package dev.usbharu.hideout.controller.mastodon
|
||||
|
||||
import dev.usbharu.hideout.controller.mastodon.generated.AppApi
|
||||
import dev.usbharu.hideout.domain.mastodon.model.generated.Application
|
||||
import dev.usbharu.hideout.domain.mastodon.model.generated.AppsRequest
|
||||
import dev.usbharu.hideout.service.api.mastodon.AppApiService
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestMethod
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
|
||||
@Controller
|
||||
class MastodonAppsApiController(private val appApiService: AppApiService) : AppApi {
|
||||
override suspend fun apiV1AppsPost(appsRequest: AppsRequest): ResponseEntity<Application> {
|
||||
println(appsRequest)
|
||||
return ResponseEntity(
|
||||
appApiService.createApp(appsRequest),
|
||||
HttpStatus.OK
|
||||
)
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
method = [RequestMethod.POST],
|
||||
value = ["/api/v1/apps"],
|
||||
produces = ["application/json"],
|
||||
consumes = ["application/x-www-form-urlencoded"]
|
||||
)
|
||||
suspend fun apiV1AppsPost(@RequestParam map: Map<String, String>): ResponseEntity<Application> {
|
||||
val appsRequest =
|
||||
AppsRequest(map.getValue("client_name"), map.getValue("redirect_uris"), map["scopes"], map["website"])
|
||||
return ResponseEntity(
|
||||
appApiService.createApp(appsRequest),
|
||||
HttpStatus.OK
|
||||
)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package dev.usbharu.hideout.controller.mastodon
|
||||
|
||||
import dev.usbharu.hideout.controller.mastodon.generated.InstanceApi
|
||||
import dev.usbharu.hideout.domain.mastodon.model.generated.V1Instance
|
||||
import dev.usbharu.hideout.service.api.mastodon.InstanceApiService
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.stereotype.Controller
|
||||
|
||||
@Controller
|
||||
class MastodonInstanceApiController(private val instanceApiService: InstanceApiService) : InstanceApi {
|
||||
override suspend fun apiV1InstanceGet(): ResponseEntity<V1Instance> {
|
||||
return ResponseEntity(instanceApiService.v1Instance(), HttpStatus.OK)
|
||||
}
|
||||
}
|
|
@ -1,27 +1,17 @@
|
|||
package dev.usbharu.hideout.controller.mastodon
|
||||
|
||||
import dev.usbharu.hideout.controller.mastodon.generated.DefaultApi
|
||||
import dev.usbharu.hideout.controller.mastodon.generated.StatusApi
|
||||
import dev.usbharu.hideout.domain.mastodon.model.generated.Status
|
||||
import dev.usbharu.hideout.domain.mastodon.model.generated.StatusesRequest
|
||||
import dev.usbharu.hideout.domain.mastodon.model.generated.V1Instance
|
||||
import dev.usbharu.hideout.domain.model.UserDetailsImpl
|
||||
import dev.usbharu.hideout.service.api.mastodon.InstanceApiService
|
||||
import dev.usbharu.hideout.service.api.mastodon.StatusesApiService
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.stereotype.Controller
|
||||
|
||||
|
||||
@Controller
|
||||
class MastodonApiController(
|
||||
private val instanceApiService: InstanceApiService,
|
||||
private val statusesApiService: StatusesApiService
|
||||
) : DefaultApi {
|
||||
override suspend fun apiV1InstanceGet(): ResponseEntity<V1Instance> {
|
||||
return ResponseEntity(instanceApiService.v1Instance(), HttpStatus.OK)
|
||||
}
|
||||
|
||||
class MastodonStatusesApiContoller(private val statusesApiService: StatusesApiService) : StatusApi {
|
||||
override suspend fun apiV1StatusesPost(statusesRequest: StatusesRequest): ResponseEntity<Status> {
|
||||
val principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal()
|
||||
require(principal is UserDetailsImpl)
|
|
@ -1,16 +1,22 @@
|
|||
package dev.usbharu.hideout.repository
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import dev.usbharu.hideout.repository.RegisteredClient.clientId
|
||||
import dev.usbharu.hideout.repository.RegisteredClient.clientSettings
|
||||
import dev.usbharu.hideout.repository.RegisteredClient.tokenSettings
|
||||
import dev.usbharu.hideout.util.JsonUtil
|
||||
import dev.usbharu.hideout.service.auth.ExposedOAuth2AuthorizationService
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.javatime.CurrentTimestamp
|
||||
import org.jetbrains.exposed.sql.javatime.timestamp
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.security.jackson2.SecurityJackson2Modules
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository
|
||||
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module
|
||||
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings
|
||||
import org.springframework.security.oauth2.server.authorization.settings.ConfigurationSettingNames
|
||||
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat
|
||||
|
@ -41,13 +47,15 @@ class RegisteredClientRepositoryImpl(private val database: Database) : Registere
|
|||
it[clientSecret] = registeredClient.clientSecret
|
||||
it[clientSecretExpiresAt] = registeredClient.clientSecretExpiresAt
|
||||
it[clientName] = registeredClient.clientName
|
||||
it[clientAuthenticationMethods] = registeredClient.clientAuthenticationMethods.joinToString(",")
|
||||
it[authorizationGrantTypes] = registeredClient.authorizationGrantTypes.joinToString(",")
|
||||
it[clientAuthenticationMethods] =
|
||||
registeredClient.clientAuthenticationMethods.map { it.value }.joinToString(",")
|
||||
it[authorizationGrantTypes] =
|
||||
registeredClient.authorizationGrantTypes.map { it.value }.joinToString(",")
|
||||
it[redirectUris] = registeredClient.redirectUris.joinToString(",")
|
||||
it[postLogoutRedirectUris] = registeredClient.postLogoutRedirectUris.joinToString(",")
|
||||
it[scopes] = registeredClient.scopes.joinToString(",")
|
||||
it[clientSettings] = JsonUtil.mapToJson(registeredClient.clientSettings.settings)
|
||||
it[tokenSettings] = JsonUtil.mapToJson(registeredClient.tokenSettings.settings)
|
||||
it[clientSettings] = mapToJson(registeredClient.clientSettings.settings)
|
||||
it[tokenSettings] = mapToJson(registeredClient.tokenSettings.settings)
|
||||
}
|
||||
} else {
|
||||
RegisteredClient.update({ RegisteredClient.id eq registeredClient.id }) {
|
||||
|
@ -61,8 +69,8 @@ class RegisteredClientRepositoryImpl(private val database: Database) : Registere
|
|||
it[redirectUris] = registeredClient.redirectUris.joinToString(",")
|
||||
it[postLogoutRedirectUris] = registeredClient.postLogoutRedirectUris.joinToString(",")
|
||||
it[scopes] = registeredClient.scopes.joinToString(",")
|
||||
it[clientSettings] = JsonUtil.mapToJson(registeredClient.clientSettings.settings)
|
||||
it[tokenSettings] = JsonUtil.mapToJson(registeredClient.tokenSettings.settings)
|
||||
it[clientSettings] = mapToJson(registeredClient.clientSettings.settings)
|
||||
it[tokenSettings] = mapToJson(registeredClient.tokenSettings.settings)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -81,29 +89,29 @@ class RegisteredClientRepositoryImpl(private val database: Database) : Registere
|
|||
if (clientId == null) {
|
||||
return null
|
||||
}
|
||||
return RegisteredClient.select {
|
||||
val toRegisteredClient = RegisteredClient.select {
|
||||
RegisteredClient.clientId eq clientId
|
||||
}.singleOrNull()?.toRegisteredClient()
|
||||
}
|
||||
LOGGER.trace("findByClientId: $toRegisteredClient")
|
||||
return toRegisteredClient
|
||||
}
|
||||
|
||||
// org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql
|
||||
object RegisteredClient : Table("registered_client") {
|
||||
val id = varchar("id", 100)
|
||||
val clientId = varchar("client_id", 100)
|
||||
val clientIdIssuedAt = timestamp("client_id_issued_at").defaultExpression(CurrentTimestamp())
|
||||
val clientSecret = varchar("client_secret", 200).nullable().default(null)
|
||||
val clientSecretExpiresAt = timestamp("client_secret_expires_at").nullable().default(null)
|
||||
val clientName = varchar("client_name", 200)
|
||||
val clientAuthenticationMethods = varchar("client_authentication_methods", 1000)
|
||||
val authorizationGrantTypes = varchar("authorization_grant_types", 1000)
|
||||
val redirectUris = varchar("redirect_uris", 1000).nullable().default(null)
|
||||
val postLogoutRedirectUris = varchar("post_logout_redirect_uris", 1000).nullable().default(null)
|
||||
val scopes = varchar("scopes", 1000)
|
||||
val clientSettings = varchar("client_settings", 2000)
|
||||
val tokenSettings = varchar("token_settings", 2000)
|
||||
private fun mapToJson(map: Map<*, *>): String = objectMapper.writeValueAsString(map)
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
private fun <T, U> jsonToMap(json: String): Map<T, U> = objectMapper.readValue(json)
|
||||
|
||||
companion object {
|
||||
val objectMapper: ObjectMapper = ObjectMapper()
|
||||
val LOGGER = LoggerFactory.getLogger(RegisteredClientRepositoryImpl::class.java)
|
||||
|
||||
init {
|
||||
|
||||
val classLoader = ExposedOAuth2AuthorizationService::class.java.classLoader
|
||||
val modules = SecurityJackson2Modules.getModules(classLoader)
|
||||
this.objectMapper.registerModules(JavaTimeModule())
|
||||
this.objectMapper.registerModules(modules)
|
||||
this.objectMapper.registerModules(OAuth2AuthorizationServerJackson2Module())
|
||||
}
|
||||
}
|
||||
|
||||
fun ResultRow.toRegisteredClient(): SpringRegisteredClient {
|
||||
|
@ -156,9 +164,9 @@ fun ResultRow.toRegisteredClient(): SpringRegisteredClient {
|
|||
.redirectUris { it.addAll(redirectUris) }
|
||||
.postLogoutRedirectUris { it.addAll(postLogoutRedirectUris) }
|
||||
.scopes { it.addAll(clientScopes) }
|
||||
.clientSettings(ClientSettings.withSettings(JsonUtil.jsonToMap(this[clientSettings])).build())
|
||||
.clientSettings(ClientSettings.withSettings(jsonToMap(this[clientSettings])).build())
|
||||
|
||||
val tokenSettingsMap = JsonUtil.jsonToMap<String, Any>(this[tokenSettings])
|
||||
val tokenSettingsMap = jsonToMap<String, Any>(this[tokenSettings])
|
||||
val withSettings = TokenSettings.withSettings(tokenSettingsMap)
|
||||
if (tokenSettingsMap.containsKey(ConfigurationSettingNames.Token.ACCESS_TOKEN_FORMAT)) {
|
||||
withSettings.accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED)
|
||||
|
@ -167,3 +175,24 @@ fun ResultRow.toRegisteredClient(): SpringRegisteredClient {
|
|||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql
|
||||
object RegisteredClient : Table("registered_client") {
|
||||
val id = varchar("id", 100)
|
||||
val clientId = varchar("client_id", 100)
|
||||
val clientIdIssuedAt = timestamp("client_id_issued_at").defaultExpression(CurrentTimestamp())
|
||||
val clientSecret = varchar("client_secret", 200).nullable().default(null)
|
||||
val clientSecretExpiresAt = timestamp("client_secret_expires_at").nullable().default(null)
|
||||
val clientName = varchar("client_name", 200)
|
||||
val clientAuthenticationMethods = varchar("client_authentication_methods", 1000)
|
||||
val authorizationGrantTypes = varchar("authorization_grant_types", 1000)
|
||||
val redirectUris = varchar("redirect_uris", 1000).nullable().default(null)
|
||||
val postLogoutRedirectUris = varchar("post_logout_redirect_uris", 1000).nullable().default(null)
|
||||
val scopes = varchar("scopes", 1000)
|
||||
val clientSettings = varchar("client_settings", 2000)
|
||||
val tokenSettings = varchar("token_settings", 2000)
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
package dev.usbharu.hideout.service.api.mastodon
|
||||
|
||||
import dev.usbharu.hideout.domain.mastodon.model.generated.Application
|
||||
import dev.usbharu.hideout.domain.mastodon.model.generated.AppsRequest
|
||||
import dev.usbharu.hideout.service.auth.SecureTokenGenerator
|
||||
import dev.usbharu.hideout.service.core.Transaction
|
||||
import org.springframework.security.crypto.password.PasswordEncoder
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository
|
||||
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings
|
||||
import org.springframework.stereotype.Service
|
||||
import java.util.*
|
||||
|
||||
@Service
|
||||
interface AppApiService {
|
||||
suspend fun createApp(appsRequest: AppsRequest): Application
|
||||
}
|
||||
|
||||
@Service
|
||||
class AppApiServiceImpl(
|
||||
private val registeredClientRepository: RegisteredClientRepository,
|
||||
private val secureTokenGenerator: SecureTokenGenerator,
|
||||
private val passwordEncoder: PasswordEncoder,
|
||||
private val transaction: Transaction
|
||||
) : AppApiService {
|
||||
override suspend fun createApp(appsRequest: AppsRequest): Application {
|
||||
return transaction.transaction {
|
||||
val id = UUID.randomUUID().toString()
|
||||
val clientSecret = secureTokenGenerator.generate()
|
||||
val registeredClient = RegisteredClient.withId(id)
|
||||
.clientId(id)
|
||||
.clientSecret(passwordEncoder.encode(clientSecret))
|
||||
.clientName(appsRequest.clientName)
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
|
||||
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
|
||||
.redirectUri(appsRequest.redirectUris)
|
||||
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
|
||||
.scopes { it.addAll(parseScope(appsRequest.scopes.orEmpty())) }
|
||||
.build()
|
||||
registeredClientRepository.save(registeredClient)
|
||||
|
||||
Application(
|
||||
appsRequest.clientName,
|
||||
"invalid-vapid-key",
|
||||
appsRequest.website,
|
||||
id,
|
||||
clientSecret
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseScope(string: String): Set<String> {
|
||||
return string.split(" ").toSet()
|
||||
}
|
||||
}
|
|
@ -1,7 +1,10 @@
|
|||
package dev.usbharu.hideout.service.auth
|
||||
|
||||
import dev.usbharu.hideout.service.core.Transaction
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository
|
||||
|
@ -9,10 +12,25 @@ import org.springframework.stereotype.Service
|
|||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent as AuthorizationConsent
|
||||
|
||||
@Service
|
||||
class ExposedOAuth2AuthorizationConsentService(private val registeredClientRepository: RegisteredClientRepository) :
|
||||
class ExposedOAuth2AuthorizationConsentService(
|
||||
private val registeredClientRepository: RegisteredClientRepository,
|
||||
private val transaction: Transaction,
|
||||
private val database: Database
|
||||
) :
|
||||
OAuth2AuthorizationConsentService {
|
||||
override fun save(authorizationConsent: AuthorizationConsent?) {
|
||||
|
||||
init {
|
||||
transaction(database) {
|
||||
SchemaUtils.create(OAuth2AuthorizationConsent)
|
||||
SchemaUtils.createMissingTablesAndColumns(OAuth2AuthorizationConsent)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun save(authorizationConsent: AuthorizationConsent?) = runBlocking {
|
||||
requireNotNull(authorizationConsent)
|
||||
transaction.transaction {
|
||||
|
||||
val singleOrNull =
|
||||
OAuth2AuthorizationConsent.select {
|
||||
OAuth2AuthorizationConsent.registeredClientId
|
||||
|
@ -28,6 +46,7 @@ class ExposedOAuth2AuthorizationConsentService(private val registeredClientRepos
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun remove(authorizationConsent: AuthorizationConsent?) {
|
||||
if (authorizationConsent == null) {
|
||||
|
@ -38,16 +57,18 @@ class ExposedOAuth2AuthorizationConsentService(private val registeredClientRepos
|
|||
}
|
||||
}
|
||||
|
||||
override fun findById(registeredClientId: String?, principalName: String?): AuthorizationConsent? {
|
||||
override fun findById(registeredClientId: String?, principalName: String?): AuthorizationConsent? = runBlocking {
|
||||
requireNotNull(registeredClientId)
|
||||
requireNotNull(principalName)
|
||||
transaction.transaction {
|
||||
|
||||
return OAuth2AuthorizationConsent.select {
|
||||
OAuth2AuthorizationConsent.select {
|
||||
(OAuth2AuthorizationConsent.registeredClientId eq registeredClientId)
|
||||
.and(OAuth2AuthorizationConsent.principalName eq principalName)
|
||||
}
|
||||
.singleOrNull()?.toAuthorizationConsent()
|
||||
}
|
||||
}
|
||||
|
||||
fun ResultRow.toAuthorizationConsent(): AuthorizationConsent {
|
||||
val registeredClientId = this[OAuth2AuthorizationConsent.registeredClientId]
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
package dev.usbharu.hideout.service.auth
|
||||
|
||||
import dev.usbharu.hideout.util.JsonUtil
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import dev.usbharu.hideout.service.core.Transaction
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.javatime.timestamp
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.springframework.security.jackson2.SecurityJackson2Modules
|
||||
import org.springframework.security.oauth2.core.*
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames
|
||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken
|
||||
|
@ -13,13 +19,27 @@ import org.springframework.security.oauth2.server.authorization.OAuth2Authorizat
|
|||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository
|
||||
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
class ExposedOAuth2AuthorizationService(private val registeredClientRepository: RegisteredClientRepository) :
|
||||
class ExposedOAuth2AuthorizationService(
|
||||
private val registeredClientRepository: RegisteredClientRepository,
|
||||
private val transaction: Transaction,
|
||||
private val database: Database
|
||||
) :
|
||||
OAuth2AuthorizationService {
|
||||
override fun save(authorization: OAuth2Authorization?) {
|
||||
|
||||
init {
|
||||
transaction(database) {
|
||||
SchemaUtils.create(Authorization)
|
||||
SchemaUtils.createMissingTablesAndColumns(Authorization)
|
||||
}
|
||||
}
|
||||
|
||||
override fun save(authorization: OAuth2Authorization?): Unit = runBlocking {
|
||||
requireNotNull(authorization)
|
||||
transaction.transaction {
|
||||
val singleOrNull = Authorization.select { Authorization.id eq authorization.id }.singleOrNull()
|
||||
if (singleOrNull == null) {
|
||||
val authorizationCodeToken = authorization.getToken(OAuth2AuthorizationCode::class.java)
|
||||
|
@ -34,34 +54,35 @@ class ExposedOAuth2AuthorizationService(private val registeredClientRepository:
|
|||
it[principalName] = authorization.principalName
|
||||
it[authorizationGrantType] = authorization.authorizationGrantType.value
|
||||
it[authorizedScopes] = authorization.authorizedScopes.joinToString(",").takeIf { it.isEmpty() }
|
||||
it[attributes] = JsonUtil.mapToJson(authorization.attributes)
|
||||
it[attributes] = mapToJson(authorization.attributes)
|
||||
it[state] = authorization.getAttribute(OAuth2ParameterNames.STATE)
|
||||
it[authorizationCodeValue] = authorizationCodeToken?.token?.tokenValue
|
||||
it[authorizationCodeIssuedAt] = authorizationCodeToken?.token?.issuedAt
|
||||
it[authorizationCodeExpiresAt] = authorizationCodeToken?.token?.expiresAt
|
||||
it[authorizationCodeMetadata] = authorizationCodeToken?.metadata?.let { it1 -> JsonUtil.mapToJson(it1) }
|
||||
it[authorizationCodeMetadata] =
|
||||
authorizationCodeToken?.metadata?.let { it1 -> mapToJson(it1) }
|
||||
it[accessTokenValue] = accessToken?.token?.tokenValue
|
||||
it[accessTokenIssuedAt] = accessToken?.token?.issuedAt
|
||||
it[accessTokenExpiresAt] = accessToken?.token?.expiresAt
|
||||
it[accessTokenMetadata] = accessToken?.metadata?.let { it1 -> JsonUtil.mapToJson(it1) }
|
||||
it[accessTokenMetadata] = accessToken?.metadata?.let { it1 -> mapToJson(it1) }
|
||||
it[accessTokenType] = accessToken?.token?.tokenType?.value
|
||||
it[accessTokenScopes] = accessToken?.run { token.scopes.joinToString(",").takeIf { it.isEmpty() } }
|
||||
it[refreshTokenValue] = refreshToken?.token?.tokenValue
|
||||
it[refreshTokenIssuedAt] = refreshToken?.token?.issuedAt
|
||||
it[refreshTokenExpiresAt] = refreshToken?.token?.expiresAt
|
||||
it[refreshTokenMetadata] = refreshToken?.metadata?.let { it1 -> JsonUtil.mapToJson(it1) }
|
||||
it[refreshTokenMetadata] = refreshToken?.metadata?.let { it1 -> mapToJson(it1) }
|
||||
it[oidcIdTokenValue] = oidcIdToken?.token?.tokenValue
|
||||
it[oidcIdTokenIssuedAt] = oidcIdToken?.token?.issuedAt
|
||||
it[oidcIdTokenExpiresAt] = oidcIdToken?.token?.expiresAt
|
||||
it[oidcIdTokenMetadata] = oidcIdToken?.metadata?.let { it1 -> JsonUtil.mapToJson(it1) }
|
||||
it[oidcIdTokenMetadata] = oidcIdToken?.metadata?.let { it1 -> mapToJson(it1) }
|
||||
it[userCodeValue] = userCode?.token?.tokenValue
|
||||
it[userCodeIssuedAt] = userCode?.token?.issuedAt
|
||||
it[userCodeExpiresAt] = userCode?.token?.expiresAt
|
||||
it[userCodeMetadata] = userCode?.metadata?.let { it1 -> JsonUtil.mapToJson(it1) }
|
||||
it[userCodeMetadata] = userCode?.metadata?.let { it1 -> mapToJson(it1) }
|
||||
it[deviceCodeValue] = deviceCode?.token?.tokenValue
|
||||
it[deviceCodeIssuedAt] = deviceCode?.token?.issuedAt
|
||||
it[deviceCodeExpiresAt] = deviceCode?.token?.expiresAt
|
||||
it[deviceCodeMetadata] = deviceCode?.metadata?.let { it1 -> JsonUtil.mapToJson(it1) }
|
||||
it[deviceCodeMetadata] = deviceCode?.metadata?.let { it1 -> mapToJson(it1) }
|
||||
}
|
||||
} else {
|
||||
val authorizationCodeToken = authorization.getToken(OAuth2AuthorizationCode::class.java)
|
||||
|
@ -75,34 +96,36 @@ class ExposedOAuth2AuthorizationService(private val registeredClientRepository:
|
|||
it[principalName] = authorization.principalName
|
||||
it[authorizationGrantType] = authorization.authorizationGrantType.value
|
||||
it[authorizedScopes] = authorization.authorizedScopes.joinToString(",").takeIf { it.isEmpty() }
|
||||
it[attributes] = JsonUtil.mapToJson(authorization.attributes)
|
||||
it[attributes] = mapToJson(authorization.attributes)
|
||||
it[state] = authorization.getAttribute(OAuth2ParameterNames.STATE)
|
||||
it[authorizationCodeValue] = authorizationCodeToken?.token?.tokenValue
|
||||
it[authorizationCodeIssuedAt] = authorizationCodeToken?.token?.issuedAt
|
||||
it[authorizationCodeExpiresAt] = authorizationCodeToken?.token?.expiresAt
|
||||
it[authorizationCodeMetadata] = authorizationCodeToken?.metadata?.let { it1 -> JsonUtil.mapToJson(it1) }
|
||||
it[authorizationCodeMetadata] =
|
||||
authorizationCodeToken?.metadata?.let { it1 -> mapToJson(it1) }
|
||||
it[accessTokenValue] = accessToken?.token?.tokenValue
|
||||
it[accessTokenIssuedAt] = accessToken?.token?.issuedAt
|
||||
it[accessTokenExpiresAt] = accessToken?.token?.expiresAt
|
||||
it[accessTokenMetadata] = accessToken?.metadata?.let { it1 -> JsonUtil.mapToJson(it1) }
|
||||
it[accessTokenMetadata] = accessToken?.metadata?.let { it1 -> mapToJson(it1) }
|
||||
it[accessTokenType] = accessToken?.token?.tokenType?.value
|
||||
it[accessTokenScopes] = accessToken?.token?.scopes?.joinToString(",")?.takeIf { it.isEmpty() }
|
||||
it[refreshTokenValue] = refreshToken?.token?.tokenValue
|
||||
it[refreshTokenIssuedAt] = refreshToken?.token?.issuedAt
|
||||
it[refreshTokenExpiresAt] = refreshToken?.token?.expiresAt
|
||||
it[refreshTokenMetadata] = refreshToken?.metadata?.let { it1 -> JsonUtil.mapToJson(it1) }
|
||||
it[refreshTokenMetadata] = refreshToken?.metadata?.let { it1 -> mapToJson(it1) }
|
||||
it[oidcIdTokenValue] = oidcIdToken?.token?.tokenValue
|
||||
it[oidcIdTokenIssuedAt] = oidcIdToken?.token?.issuedAt
|
||||
it[oidcIdTokenExpiresAt] = oidcIdToken?.token?.expiresAt
|
||||
it[oidcIdTokenMetadata] = oidcIdToken?.metadata?.let { it1 -> JsonUtil.mapToJson(it1) }
|
||||
it[oidcIdTokenMetadata] = oidcIdToken?.metadata?.let { it1 -> mapToJson(it1) }
|
||||
it[userCodeValue] = userCode?.token?.tokenValue
|
||||
it[userCodeIssuedAt] = userCode?.token?.issuedAt
|
||||
it[userCodeExpiresAt] = userCode?.token?.expiresAt
|
||||
it[userCodeMetadata] = userCode?.metadata?.let { it1 -> JsonUtil.mapToJson(it1) }
|
||||
it[userCodeMetadata] = userCode?.metadata?.let { it1 -> mapToJson(it1) }
|
||||
it[deviceCodeValue] = deviceCode?.token?.tokenValue
|
||||
it[deviceCodeIssuedAt] = deviceCode?.token?.issuedAt
|
||||
it[deviceCodeExpiresAt] = deviceCode?.token?.expiresAt
|
||||
it[deviceCodeMetadata] = deviceCode?.metadata?.let { it1 -> JsonUtil.mapToJson(it1) }
|
||||
it[deviceCodeMetadata] = deviceCode?.metadata?.let { it1 -> mapToJson(it1) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -121,9 +144,12 @@ class ExposedOAuth2AuthorizationService(private val registeredClientRepository:
|
|||
return Authorization.select { Authorization.id eq id }.singleOrNull()?.toAuthorization()
|
||||
}
|
||||
|
||||
override fun findByToken(token: String?, tokenType: OAuth2TokenType?): OAuth2Authorization? {
|
||||
override fun findByToken(token: String?, tokenType: OAuth2TokenType?): OAuth2Authorization? = runBlocking {
|
||||
requireNotNull(token)
|
||||
return when (tokenType?.value) {
|
||||
transaction.transaction {
|
||||
|
||||
|
||||
when (tokenType?.value) {
|
||||
null -> {
|
||||
Authorization.select {
|
||||
Authorization.authorizationCodeValue eq token
|
||||
|
@ -173,6 +199,7 @@ class ExposedOAuth2AuthorizationService(private val registeredClientRepository:
|
|||
}
|
||||
}?.singleOrNull()?.toAuthorization()
|
||||
}
|
||||
}
|
||||
|
||||
fun ResultRow.toAuthorization(): OAuth2Authorization {
|
||||
val registeredClientId = this[Authorization.registeredClientId]
|
||||
|
@ -184,7 +211,7 @@ class ExposedOAuth2AuthorizationService(private val registeredClientRepository:
|
|||
val principalName = this[Authorization.principalName]
|
||||
val authorizationGrantType = this[Authorization.authorizationGrantType]
|
||||
val authorizedScopes = this[Authorization.authorizedScopes]?.split(",").orEmpty().toSet()
|
||||
val attributes = this[Authorization.attributes]?.let { JsonUtil.jsonToMap<String, Any>(it) }.orEmpty()
|
||||
val attributes = this[Authorization.attributes]?.let { jsonToMap<String, Any>(it) }.orEmpty()
|
||||
|
||||
builder.id(id).principalName(principalName)
|
||||
.authorizationGrantType(AuthorizationGrantType(authorizationGrantType)).authorizedScopes(authorizedScopes)
|
||||
|
@ -195,12 +222,12 @@ class ExposedOAuth2AuthorizationService(private val registeredClientRepository:
|
|||
builder.attribute(OAuth2ParameterNames.STATE, state)
|
||||
}
|
||||
|
||||
val authorizationCodeValue = this[Authorization.authorizationCodeValue]
|
||||
if (authorizationCodeValue.isNullOrBlank()) {
|
||||
val authorizationCodeValue = this[Authorization.authorizationCodeValue].orEmpty()
|
||||
if (authorizationCodeValue.isNotBlank()) {
|
||||
val authorizationCodeIssuedAt = this[Authorization.authorizationCodeIssuedAt]
|
||||
val authorizationCodeExpiresAt = this[Authorization.authorizationCodeExpiresAt]
|
||||
val authorizationCodeMetadata = this[Authorization.authorizationCodeMetadata]?.let {
|
||||
JsonUtil.jsonToMap<String, Any>(
|
||||
jsonToMap<String, Any>(
|
||||
it
|
||||
)
|
||||
}.orEmpty()
|
||||
|
@ -216,7 +243,7 @@ class ExposedOAuth2AuthorizationService(private val registeredClientRepository:
|
|||
val accessTokenIssuedAt = this[Authorization.accessTokenIssuedAt]
|
||||
val accessTokenExpiresAt = this[Authorization.accessTokenExpiresAt]
|
||||
val accessTokenMetadata =
|
||||
this[Authorization.accessTokenMetadata]?.let { JsonUtil.jsonToMap<String, Any>(it) }.orEmpty()
|
||||
this[Authorization.accessTokenMetadata]?.let { jsonToMap<String, Any>(it) }.orEmpty()
|
||||
val accessTokenType =
|
||||
if (this[Authorization.accessTokenType].equals(OAuth2AccessToken.TokenType.BEARER.value, true)) {
|
||||
OAuth2AccessToken.TokenType.BEARER
|
||||
|
@ -242,7 +269,7 @@ class ExposedOAuth2AuthorizationService(private val registeredClientRepository:
|
|||
val oidcTokenIssuedAt = this[Authorization.oidcIdTokenIssuedAt]
|
||||
val oidcTokenExpiresAt = this[Authorization.oidcIdTokenExpiresAt]
|
||||
val oidcTokenMetadata =
|
||||
this[Authorization.oidcIdTokenMetadata]?.let { JsonUtil.jsonToMap<String, Any>(it) }.orEmpty()
|
||||
this[Authorization.oidcIdTokenMetadata]?.let { jsonToMap<String, Any>(it) }.orEmpty()
|
||||
|
||||
val oidcIdToken = OidcIdToken(
|
||||
oidcIdTokenValue,
|
||||
|
@ -259,7 +286,7 @@ class ExposedOAuth2AuthorizationService(private val registeredClientRepository:
|
|||
val refreshTokenIssuedAt = this[Authorization.refreshTokenIssuedAt]
|
||||
val refreshTokenExpiresAt = this[Authorization.refreshTokenExpiresAt]
|
||||
val refreshTokenMetadata =
|
||||
this[Authorization.refreshTokenMetadata]?.let { JsonUtil.jsonToMap<String, Any>(it) }.orEmpty()
|
||||
this[Authorization.refreshTokenMetadata]?.let { jsonToMap<String, Any>(it) }.orEmpty()
|
||||
|
||||
val oAuth2RefreshToken = OAuth2RefreshToken(refreshTokenValue, refreshTokenIssuedAt, refreshTokenExpiresAt)
|
||||
|
||||
|
@ -271,7 +298,7 @@ class ExposedOAuth2AuthorizationService(private val registeredClientRepository:
|
|||
val userCodeIssuedAt = this[Authorization.userCodeIssuedAt]
|
||||
val userCodeExpiresAt = this[Authorization.userCodeExpiresAt]
|
||||
val userCodeMetadata =
|
||||
this[Authorization.userCodeMetadata]?.let { JsonUtil.jsonToMap<String, Any>(it) }.orEmpty()
|
||||
this[Authorization.userCodeMetadata]?.let { jsonToMap<String, Any>(it) }.orEmpty()
|
||||
val oAuth2UserCode = OAuth2UserCode(userCodeValue, userCodeIssuedAt, userCodeExpiresAt)
|
||||
builder.token(oAuth2UserCode) { it.putAll(userCodeMetadata) }
|
||||
}
|
||||
|
@ -281,7 +308,7 @@ class ExposedOAuth2AuthorizationService(private val registeredClientRepository:
|
|||
val deviceCodeIssuedAt = this[Authorization.deviceCodeIssuedAt]
|
||||
val deviceCodeExpiresAt = this[Authorization.deviceCodeExpiresAt]
|
||||
val deviceCodeMetadata =
|
||||
this[Authorization.deviceCodeMetadata]?.let { JsonUtil.jsonToMap<String, Any>(it) }.orEmpty()
|
||||
this[Authorization.deviceCodeMetadata]?.let { jsonToMap<String, Any>(it) }.orEmpty()
|
||||
|
||||
val oAuth2DeviceCode = OAuth2DeviceCode(deviceCodeValue, deviceCodeIssuedAt, deviceCodeExpiresAt)
|
||||
builder.token(oAuth2DeviceCode) { it.putAll(deviceCodeMetadata) }
|
||||
|
@ -289,9 +316,26 @@ class ExposedOAuth2AuthorizationService(private val registeredClientRepository:
|
|||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
private fun mapToJson(map: Map<*, *>): String = objectMapper.writeValueAsString(map)
|
||||
|
||||
private fun <T, U> jsonToMap(json: String): Map<T, U> = objectMapper.readValue(json)
|
||||
|
||||
companion object {
|
||||
val objectMapper: ObjectMapper = ObjectMapper()
|
||||
|
||||
init {
|
||||
|
||||
val classLoader = ExposedOAuth2AuthorizationService::class.java.classLoader
|
||||
val modules = SecurityJackson2Modules.getModules(classLoader)
|
||||
this.objectMapper.registerModules(JavaTimeModule())
|
||||
this.objectMapper.registerModules(modules)
|
||||
this.objectMapper.registerModules(OAuth2AuthorizationServerJackson2Module())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object Authorization : Table("authorization") {
|
||||
object Authorization : Table("application_authorization") {
|
||||
val id = varchar("id", 255)
|
||||
val registeredClientId = varchar("registered_client_id", 255)
|
||||
val principalName = varchar("principal_name", 255)
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package dev.usbharu.hideout.service.auth
|
||||
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
@Component
|
||||
interface SecureTokenGenerator {
|
||||
fun generate(): String
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package dev.usbharu.hideout.service.auth
|
||||
|
||||
import org.springframework.stereotype.Component
|
||||
import java.security.SecureRandom
|
||||
import java.util.*
|
||||
|
||||
@Component
|
||||
class SecureTokenGeneratorImpl : SecureTokenGenerator {
|
||||
override fun generate(): String {
|
||||
|
||||
val byteArray = ByteArray(16)
|
||||
val secureRandom = SecureRandom()
|
||||
secureRandom.nextBytes(byteArray)
|
||||
|
||||
|
||||
return Base64.getUrlEncoder().encodeToString(byteArray)
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package dev.usbharu.hideout.service.auth
|
||||
|
||||
import dev.usbharu.hideout.config.ApplicationConfig
|
||||
import dev.usbharu.hideout.query.UserQueryService
|
||||
import dev.usbharu.hideout.service.core.Transaction
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
@ -8,16 +9,21 @@ import org.springframework.security.core.userdetails.UserDetails
|
|||
import org.springframework.security.core.userdetails.UserDetailsService
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException
|
||||
import org.springframework.stereotype.Service
|
||||
import java.net.URL
|
||||
|
||||
@Service
|
||||
class UserDetailsServiceImpl(private val userQueryService: UserQueryService, private val transaction: Transaction) :
|
||||
class UserDetailsServiceImpl(
|
||||
private val userQueryService: UserQueryService,
|
||||
private val applicationConfig: ApplicationConfig,
|
||||
private val transaction: Transaction
|
||||
) :
|
||||
UserDetailsService {
|
||||
override fun loadUserByUsername(username: String?): UserDetails = runBlocking {
|
||||
if (username == null) {
|
||||
throw UsernameNotFoundException("$username not found")
|
||||
}
|
||||
transaction.transaction {
|
||||
val findById = userQueryService.findByNameAndDomain(username, "")
|
||||
val findById = userQueryService.findByNameAndDomain(username, URL(applicationConfig.url).host)
|
||||
User(
|
||||
findById.name,
|
||||
findById.password,
|
||||
|
|
|
@ -2,8 +2,8 @@ package dev.usbharu.hideout.service.user
|
|||
|
||||
import dev.usbharu.hideout.config.Config
|
||||
import dev.usbharu.hideout.query.UserQueryService
|
||||
import io.ktor.util.*
|
||||
import org.koin.core.annotation.Single
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
||||
import org.springframework.stereotype.Service
|
||||
import java.security.*
|
||||
import java.util.*
|
||||
|
@ -15,8 +15,7 @@ class UserAuthServiceImpl(
|
|||
) : UserAuthService {
|
||||
|
||||
override fun hash(password: String): String {
|
||||
val digest = sha256.digest(password.toByteArray(Charsets.UTF_8))
|
||||
return hex(digest)
|
||||
return BCryptPasswordEncoder().encode(password)
|
||||
}
|
||||
|
||||
override suspend fun usernameAlreadyUse(username: String): Boolean {
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
package dev.usbharu.hideout.util
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
|
||||
object JsonUtil {
|
||||
val objectMapper = jacksonObjectMapper()
|
||||
val objectMapper = jacksonObjectMapper().registerModule(JavaTimeModule())
|
||||
|
||||
fun mapToJson(map: Map<*, *>, objectMapper: ObjectMapper = this.objectMapper): String =
|
||||
objectMapper.writeValueAsString(map)
|
||||
|
|
|
@ -1,13 +1,28 @@
|
|||
hideout:
|
||||
url: "http://localhost:8080"
|
||||
url: "https://test-hideout.usbharu.dev"
|
||||
database:
|
||||
url: "jdbc:h2:./test;MODE=POSTGRESQL"
|
||||
url: "jdbc:h2:./test-dev2;MODE=POSTGRESQL"
|
||||
driver: "org.h2.Driver"
|
||||
user: ""
|
||||
password: ""
|
||||
spring:
|
||||
jackson:
|
||||
serialization:
|
||||
WRITE_DATES_AS_TIMESTAMPS: false
|
||||
datasource:
|
||||
driver-class-name: org.h2.Driver
|
||||
url: "jdbc:h2:./test;MODE=POSTGRESQL"
|
||||
url: "jdbc:h2:./test-dev2;MODE=POSTGRESQL"
|
||||
username: ""
|
||||
password: ""
|
||||
|
||||
h2:
|
||||
console:
|
||||
enabled: true
|
||||
server:
|
||||
|
||||
tomcat:
|
||||
basedir: tomcat
|
||||
accesslog:
|
||||
enabled: true
|
||||
|
||||
port: 8081
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<root level="DEBUG">
|
||||
<root level="TRACE">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
<logger name="org.eclipse.jetty" level="INFO"/>
|
||||
|
|
|
@ -5,9 +5,22 @@ info:
|
|||
version: 1.0.0
|
||||
servers:
|
||||
- url: 'https://test-hideout.usbharu.dev'
|
||||
|
||||
tags:
|
||||
- name: status
|
||||
description: status
|
||||
- name: account
|
||||
description: account
|
||||
- name: app
|
||||
description: app
|
||||
- name: instance
|
||||
description: instance
|
||||
|
||||
paths:
|
||||
/api/v2/instance:
|
||||
get:
|
||||
tags:
|
||||
- instance
|
||||
security:
|
||||
- { }
|
||||
responses:
|
||||
|
@ -20,6 +33,8 @@ paths:
|
|||
|
||||
/api/v1/instance/peers:
|
||||
get:
|
||||
tags:
|
||||
- instance
|
||||
security:
|
||||
- { }
|
||||
responses:
|
||||
|
@ -34,6 +49,8 @@ paths:
|
|||
|
||||
/api/v1/instance/activity:
|
||||
get:
|
||||
tags:
|
||||
- instance
|
||||
security:
|
||||
- { }
|
||||
responses:
|
||||
|
@ -48,6 +65,8 @@ paths:
|
|||
|
||||
/api/v1/instance/rules:
|
||||
get:
|
||||
tags:
|
||||
- instance
|
||||
security:
|
||||
- { }
|
||||
responses:
|
||||
|
@ -62,6 +81,8 @@ paths:
|
|||
|
||||
/api/v1/instance/domain_blocks:
|
||||
get:
|
||||
tags:
|
||||
- instance
|
||||
security:
|
||||
- { }
|
||||
responses:
|
||||
|
@ -76,6 +97,8 @@ paths:
|
|||
|
||||
/api/v1/instance/extended_description:
|
||||
get:
|
||||
tags:
|
||||
- instance
|
||||
security:
|
||||
- { }
|
||||
responses:
|
||||
|
@ -88,6 +111,8 @@ paths:
|
|||
|
||||
/api/v1/instance:
|
||||
get:
|
||||
tags:
|
||||
- instance
|
||||
security:
|
||||
- { }
|
||||
responses:
|
||||
|
@ -100,6 +125,8 @@ paths:
|
|||
|
||||
/api/v1/statuses:
|
||||
post:
|
||||
tags:
|
||||
- status
|
||||
security:
|
||||
- OAuth2:
|
||||
- "write:statuses"
|
||||
|
@ -118,6 +145,28 @@ paths:
|
|||
schema:
|
||||
$ref: "#/components/schemas/Status"
|
||||
|
||||
/api/v1/apps:
|
||||
post:
|
||||
tags:
|
||||
- app
|
||||
security:
|
||||
- { }
|
||||
requestBody:
|
||||
description: 作成するApp
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/AppsRequest"
|
||||
|
||||
responses:
|
||||
200:
|
||||
description: 成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Application"
|
||||
|
||||
components:
|
||||
schemas:
|
||||
Account:
|
||||
|
@ -931,6 +980,39 @@ components:
|
|||
hide_totals:
|
||||
type: boolean
|
||||
|
||||
Application:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
website:
|
||||
type: string
|
||||
nullable: true
|
||||
vapid_key:
|
||||
type: string
|
||||
client_id:
|
||||
type: string
|
||||
client_secret:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- vapid_key
|
||||
|
||||
AppsRequest:
|
||||
type: object
|
||||
properties:
|
||||
client_name:
|
||||
type: string
|
||||
redirect_uris:
|
||||
type: string
|
||||
scopes:
|
||||
type: string
|
||||
website:
|
||||
type: string
|
||||
required:
|
||||
- client_name
|
||||
- redirect_uris
|
||||
|
||||
securitySchemes:
|
||||
OAuth2:
|
||||
type: oauth2
|
||||
|
|
Loading…
Reference in New Issue