mirror of https://github.com/usbharu/Hideout.git
test: JWTでログインのテストを追加
This commit is contained in:
parent
a053a17924
commit
c50f8a0594
|
@ -1,5 +1,7 @@
|
||||||
package dev.usbharu.hideout
|
package dev.usbharu.hideout
|
||||||
|
|
||||||
|
import com.auth0.jwk.JwkProvider
|
||||||
|
import com.auth0.jwk.JwkProviderBuilder
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude
|
import com.fasterxml.jackson.annotation.JsonInclude
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
|
@ -29,6 +31,7 @@ import kjob.core.kjob
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.jetbrains.exposed.sql.Database
|
import org.jetbrains.exposed.sql.Database
|
||||||
import org.koin.ktor.ext.inject
|
import org.koin.ktor.ext.inject
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
fun main(args: Array<String>): Unit = io.ktor.server.cio.EngineMain.main(args)
|
fun main(args: Array<String>): Unit = io.ktor.server.cio.EngineMain.main(args)
|
||||||
|
|
||||||
|
@ -89,6 +92,14 @@ fun Application.parent() {
|
||||||
single<IJwtRefreshTokenRepository> { JwtRefreshTokenRepositoryImpl(get(), get()) }
|
single<IJwtRefreshTokenRepository> { JwtRefreshTokenRepositoryImpl(get(), get()) }
|
||||||
single<IMetaService> { MetaServiceImpl(get()) }
|
single<IMetaService> { MetaServiceImpl(get()) }
|
||||||
single<IJwtService> { JwtServiceImpl(get(), get(), get()) }
|
single<IJwtService> { JwtServiceImpl(get(), get(), get()) }
|
||||||
|
single<JwkProvider> {
|
||||||
|
JwkProviderBuilder(Config.configData.url).cached(
|
||||||
|
10,
|
||||||
|
24,
|
||||||
|
TimeUnit.HOURS
|
||||||
|
)
|
||||||
|
.rateLimited(10, 1, TimeUnit.MINUTES).build()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
configureKoin(module)
|
configureKoin(module)
|
||||||
runBlocking {
|
runBlocking {
|
||||||
|
@ -103,7 +114,8 @@ fun Application.parent() {
|
||||||
inject<IUserAuthService>().value,
|
inject<IUserAuthService>().value,
|
||||||
inject<IMetaService>().value,
|
inject<IMetaService>().value,
|
||||||
inject<IUserRepository>().value,
|
inject<IUserRepository>().value,
|
||||||
inject<IJwtService>().value
|
inject<IJwtService>().value,
|
||||||
|
inject<JwkProvider>().value,
|
||||||
)
|
)
|
||||||
configureRouting(
|
configureRouting(
|
||||||
inject<HttpSignatureVerifyService>().value,
|
inject<HttpSignatureVerifyService>().value,
|
||||||
|
|
|
@ -4,7 +4,6 @@ import dev.usbharu.hideout.routing.activitypub.inbox
|
||||||
import dev.usbharu.hideout.routing.activitypub.outbox
|
import dev.usbharu.hideout.routing.activitypub.outbox
|
||||||
import dev.usbharu.hideout.routing.activitypub.usersAP
|
import dev.usbharu.hideout.routing.activitypub.usersAP
|
||||||
import dev.usbharu.hideout.routing.api.v1.statuses
|
import dev.usbharu.hideout.routing.api.v1.statuses
|
||||||
import dev.usbharu.hideout.routing.authTestRouting
|
|
||||||
import dev.usbharu.hideout.routing.wellknown.webfinger
|
import dev.usbharu.hideout.routing.wellknown.webfinger
|
||||||
import dev.usbharu.hideout.service.IPostService
|
import dev.usbharu.hideout.service.IPostService
|
||||||
import dev.usbharu.hideout.service.activitypub.ActivityPubService
|
import dev.usbharu.hideout.service.activitypub.ActivityPubService
|
||||||
|
@ -32,7 +31,5 @@ fun Application.configureRouting(
|
||||||
route("/api/v1") {
|
route("/api/v1") {
|
||||||
statuses(postService)
|
statuses(postService)
|
||||||
}
|
}
|
||||||
|
|
||||||
authTestRouting()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
@file:Suppress("UnusedPrivateMember")
|
|
||||||
|
|
||||||
package dev.usbharu.hideout.plugins
|
package dev.usbharu.hideout.plugins
|
||||||
|
|
||||||
import com.auth0.jwk.JwkProviderBuilder
|
import com.auth0.jwk.JwkProvider
|
||||||
import dev.usbharu.hideout.config.Config
|
import dev.usbharu.hideout.config.Config
|
||||||
import dev.usbharu.hideout.domain.model.hideout.form.RefreshToken
|
import dev.usbharu.hideout.domain.model.hideout.form.RefreshToken
|
||||||
import dev.usbharu.hideout.domain.model.hideout.form.UserLogin
|
import dev.usbharu.hideout.domain.model.hideout.form.UserLogin
|
||||||
import dev.usbharu.hideout.exception.UserNotFoundException
|
import dev.usbharu.hideout.exception.UserNotFoundException
|
||||||
import dev.usbharu.hideout.property
|
|
||||||
import dev.usbharu.hideout.repository.IUserRepository
|
import dev.usbharu.hideout.repository.IUserRepository
|
||||||
import dev.usbharu.hideout.service.IJwtService
|
import dev.usbharu.hideout.service.IJwtService
|
||||||
import dev.usbharu.hideout.service.IMetaService
|
import dev.usbharu.hideout.service.IMetaService
|
||||||
|
@ -20,7 +17,6 @@ import io.ktor.server.auth.jwt.*
|
||||||
import io.ktor.server.request.*
|
import io.ktor.server.request.*
|
||||||
import io.ktor.server.response.*
|
import io.ktor.server.response.*
|
||||||
import io.ktor.server.routing.*
|
import io.ktor.server.routing.*
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
const val TOKEN_AUTH = "jwt-auth"
|
const val TOKEN_AUTH = "jwt-auth"
|
||||||
|
|
||||||
|
@ -29,13 +25,10 @@ fun Application.configureSecurity(
|
||||||
userAuthService: IUserAuthService,
|
userAuthService: IUserAuthService,
|
||||||
metaService: IMetaService,
|
metaService: IMetaService,
|
||||||
userRepository: IUserRepository,
|
userRepository: IUserRepository,
|
||||||
jwtService: IJwtService
|
jwtService: IJwtService,
|
||||||
|
jwkProvider: JwkProvider
|
||||||
) {
|
) {
|
||||||
val issuer = property("hideout.url")
|
val issuer = Config.configData.url
|
||||||
val jwkProvider = JwkProviderBuilder(issuer)
|
|
||||||
.cached(10, 24, TimeUnit.HOURS)
|
|
||||||
.rateLimited(10, 1, TimeUnit.MINUTES)
|
|
||||||
.build()
|
|
||||||
install(Authentication) {
|
install(Authentication) {
|
||||||
jwt(TOKEN_AUTH) {
|
jwt(TOKEN_AUTH) {
|
||||||
verifier(jwkProvider, issuer) {
|
verifier(jwkProvider, issuer) {
|
||||||
|
@ -78,5 +71,12 @@ fun Application.configureSecurity(
|
||||||
text = JsonWebKeyUtil.publicKeyToJwk(jwt.publicKey, jwt.kid.toString())
|
text = JsonWebKeyUtil.publicKeyToJwk(jwt.publicKey, jwt.kid.toString())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
authenticate(TOKEN_AUTH) {
|
||||||
|
get("/auth-check") {
|
||||||
|
val principal = call.principal<JWTPrincipal>()
|
||||||
|
val username = principal!!.payload.getClaim("username")
|
||||||
|
call.respondText("Hello $username")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
package dev.usbharu.hideout.routing
|
|
||||||
|
|
||||||
import dev.usbharu.hideout.plugins.TOKEN_AUTH
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
import io.ktor.server.auth.*
|
|
||||||
import io.ktor.server.auth.jwt.*
|
|
||||||
import io.ktor.server.response.*
|
|
||||||
import io.ktor.server.routing.*
|
|
||||||
|
|
||||||
fun Routing.authTestRouting() {
|
|
||||||
authenticate(TOKEN_AUTH) {
|
|
||||||
get("/auth-check") {
|
|
||||||
val principal = call.principal<JWTPrincipal>()
|
|
||||||
val username = principal!!.payload.getClaim("username")
|
|
||||||
call.respondText("Hello $username")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,282 @@
|
||||||
|
package dev.usbharu.hideout.plugins
|
||||||
|
|
||||||
|
import com.auth0.jwk.Jwk
|
||||||
|
import com.auth0.jwk.JwkProvider
|
||||||
|
import com.auth0.jwt.JWT
|
||||||
|
import com.auth0.jwt.algorithms.Algorithm
|
||||||
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
|
import dev.usbharu.hideout.config.Config
|
||||||
|
import dev.usbharu.hideout.config.ConfigData
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.dto.JwtToken
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.entity.Jwt
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.entity.User
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.form.UserLogin
|
||||||
|
import dev.usbharu.hideout.repository.IUserRepository
|
||||||
|
import dev.usbharu.hideout.service.IJwtService
|
||||||
|
import dev.usbharu.hideout.service.IMetaService
|
||||||
|
import dev.usbharu.hideout.service.IUserAuthService
|
||||||
|
import dev.usbharu.hideout.util.Base64Util
|
||||||
|
import dev.usbharu.hideout.util.JsonWebKeyUtil
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.client.statement.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.config.*
|
||||||
|
import io.ktor.server.response.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
import io.ktor.server.testing.*
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.ArgumentMatchers.anyString
|
||||||
|
import org.mockito.kotlin.any
|
||||||
|
import org.mockito.kotlin.doReturn
|
||||||
|
import org.mockito.kotlin.eq
|
||||||
|
import org.mockito.kotlin.mock
|
||||||
|
import java.security.KeyPairGenerator
|
||||||
|
import java.security.interfaces.RSAPrivateKey
|
||||||
|
import java.security.interfaces.RSAPublicKey
|
||||||
|
import java.time.Instant
|
||||||
|
import java.time.temporal.ChronoUnit
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class SecurityKtTest {
|
||||||
|
@Test
|
||||||
|
fun `login ログイン出来るか`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
Config.configData = ConfigData(url = "http://example.com", objectMapper = jacksonObjectMapper())
|
||||||
|
val userAuthService = mock<IUserAuthService> {
|
||||||
|
onBlocking { verifyAccount(eq("testUser"), eq("password")) } doReturn true
|
||||||
|
}
|
||||||
|
val metaService = mock<IMetaService>()
|
||||||
|
val userRepository = mock<IUserRepository> {
|
||||||
|
onBlocking { findByNameAndDomain(eq("testUser"), eq("example.com")) } doReturn User(
|
||||||
|
id = 1L,
|
||||||
|
name = "testUser",
|
||||||
|
domain = "example.com",
|
||||||
|
screenName = "test",
|
||||||
|
description = "",
|
||||||
|
password = "hashedPassword",
|
||||||
|
inbox = "https://example.com/inbox",
|
||||||
|
outbox = "https://example.com/outbox",
|
||||||
|
url = "https://example.com/profile",
|
||||||
|
publicKey = "",
|
||||||
|
privateKey = "",
|
||||||
|
createdAt = Instant.now()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val jwtToken = JwtToken("Token", "RefreshToken")
|
||||||
|
val jwtService = mock<IJwtService> {
|
||||||
|
onBlocking { createToken(any()) } doReturn jwtToken
|
||||||
|
}
|
||||||
|
val jwkProvider = mock<JwkProvider>()
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(userAuthService, metaService, userRepository, jwtService, jwkProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
client.post("/login") {
|
||||||
|
contentType(ContentType.Application.Json)
|
||||||
|
setBody(Config.configData.objectMapper.writeValueAsString(UserLogin("testUser", "password")))
|
||||||
|
}.apply {
|
||||||
|
assertEquals(HttpStatusCode.OK, call.response.status)
|
||||||
|
assertEquals(jwtToken, Config.configData.objectMapper.readValue(call.response.bodyAsText()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `login 存在しないユーザーのログインに失敗する`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
Config.configData = ConfigData(url = "http://example.com", objectMapper = jacksonObjectMapper())
|
||||||
|
val userAuthService = mock<IUserAuthService> {
|
||||||
|
onBlocking { verifyAccount(anyString(), anyString()) }.doReturn(false)
|
||||||
|
}
|
||||||
|
val metaService = mock<IMetaService>()
|
||||||
|
val userRepository = mock<IUserRepository>()
|
||||||
|
val jwtService = mock<IJwtService>()
|
||||||
|
val jwkProvider = mock<JwkProvider>()
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(userAuthService, metaService, userRepository, jwtService, jwkProvider)
|
||||||
|
}
|
||||||
|
client.post("/login") {
|
||||||
|
contentType(ContentType.Application.Json)
|
||||||
|
setBody(Config.configData.objectMapper.writeValueAsString(UserLogin("InvalidTtestUser", "password")))
|
||||||
|
}.apply {
|
||||||
|
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `login 不正なパスワードのログインに失敗する`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
Config.configData = ConfigData(url = "http://example.com", objectMapper = jacksonObjectMapper())
|
||||||
|
val userAuthService = mock<IUserAuthService> {
|
||||||
|
onBlocking { verifyAccount(anyString(), eq("InvalidPassword")) } doReturn false
|
||||||
|
}
|
||||||
|
val metaService = mock<IMetaService>()
|
||||||
|
val userRepository = mock<IUserRepository>()
|
||||||
|
val jwtService = mock<IJwtService>()
|
||||||
|
val jwkProvider = mock<JwkProvider>()
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(userAuthService, metaService, userRepository, jwtService, jwkProvider)
|
||||||
|
}
|
||||||
|
client.post("/login") {
|
||||||
|
contentType(ContentType.Application.Json)
|
||||||
|
setBody(Config.configData.objectMapper.writeValueAsString(UserLogin("TestUser", "InvalidPassword")))
|
||||||
|
}.apply {
|
||||||
|
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `auth-check Authorizedヘッダーが無いと401が帰ってくる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
Config.configData = ConfigData(url = "http://example.com", objectMapper = jacksonObjectMapper())
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(mock(), mock(), mock(), mock(), mock())
|
||||||
|
}
|
||||||
|
client.get("/auth-check").apply {
|
||||||
|
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `auth-check Authorizedヘッダーの形式が間違っていると401が帰ってくる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
Config.configData = ConfigData(url = "http://example.com", objectMapper = jacksonObjectMapper())
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(mock(), mock(), mock(), mock(), mock())
|
||||||
|
}
|
||||||
|
client.get("/auth-check") {
|
||||||
|
header("Authorization", "Digest dsfjjhogalkjdfmlhaog")
|
||||||
|
}.apply {
|
||||||
|
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `auth-check Authorizedヘッダーが空だと401が帰ってくる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
Config.configData = ConfigData(url = "http://example.com", objectMapper = jacksonObjectMapper())
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(mock(), mock(), mock(), mock(), mock())
|
||||||
|
}
|
||||||
|
client.get("/auth-check") {
|
||||||
|
header("Authorization", "")
|
||||||
|
}.apply {
|
||||||
|
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `auth-check AuthorizedヘッダーがBeararで空だと401が帰ってくる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
Config.configData = ConfigData(url = "http://example.com", objectMapper = jacksonObjectMapper())
|
||||||
|
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(mock(), mock(), mock(), mock(), mock())
|
||||||
|
}
|
||||||
|
client.get("/auth-check") {
|
||||||
|
header("Authorization", "Bearer ")
|
||||||
|
}.apply {
|
||||||
|
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `auth-check 正当なJWTだとアクセスできる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
|
||||||
|
keyPairGenerator.initialize(2048)
|
||||||
|
val keyPair = keyPairGenerator.generateKeyPair()
|
||||||
|
val rsaPublicKey = keyPair.public as RSAPublicKey
|
||||||
|
|
||||||
|
Config.configData = ConfigData(url = "https://localhost", objectMapper = jacksonObjectMapper())
|
||||||
|
|
||||||
|
|
||||||
|
val now = Instant.now()
|
||||||
|
val kid = UUID.randomUUID()
|
||||||
|
val token = JWT.create()
|
||||||
|
.withAudience("${Config.configData.url}/users/test")
|
||||||
|
.withIssuer(Config.configData.url)
|
||||||
|
.withKeyId(kid.toString())
|
||||||
|
.withClaim("username", "test")
|
||||||
|
.withExpiresAt(now.plus(30, ChronoUnit.MINUTES))
|
||||||
|
.sign(Algorithm.RSA256(rsaPublicKey, keyPair.private as RSAPrivateKey))
|
||||||
|
val metaService = mock<IMetaService> {
|
||||||
|
onBlocking { getJwtMeta() }.doReturn(
|
||||||
|
Jwt(
|
||||||
|
kid,
|
||||||
|
Base64Util.encode(keyPair.private.encoded),
|
||||||
|
Base64Util.encode(rsaPublicKey.encoded)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val readValue = Config.configData.objectMapper.readerFor(Map::class.java)
|
||||||
|
.readValue<MutableMap<String, Any>?>(
|
||||||
|
JsonWebKeyUtil.publicKeyToJwk(
|
||||||
|
rsaPublicKey,
|
||||||
|
kid.toString()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val jwkProvider = mock<JwkProvider> {
|
||||||
|
onBlocking { get(anyString()) }.doReturn(
|
||||||
|
Jwk.fromValues(
|
||||||
|
(readValue["keys"] as List<Map<String,Any>>)[0]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val userRepository = mock<IUserRepository>()
|
||||||
|
val jwtService = mock<IJwtService>()
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(mock(), metaService, userRepository, jwtService, jwkProvider)
|
||||||
|
}
|
||||||
|
externalServices {
|
||||||
|
hosts("http://localhost:8080") {
|
||||||
|
routing {
|
||||||
|
get("/.well-known/jwks.json") {
|
||||||
|
call.application.log.info("aaaaaaaaaaaaaa")
|
||||||
|
println("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
|
||||||
|
call.respondText(
|
||||||
|
contentType = ContentType.Application.Json,
|
||||||
|
text = JsonWebKeyUtil.publicKeyToJwk(rsaPublicKey, kid.toString())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
client.get("/auth-check") {
|
||||||
|
header("Authorization", "Bearer $token")
|
||||||
|
}.apply {
|
||||||
|
assertEquals(HttpStatusCode.OK, call.response.status)
|
||||||
|
assertEquals("Hello \"test\"",call.response.bodyAsText())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,8 +10,7 @@ ktor {
|
||||||
}
|
}
|
||||||
|
|
||||||
hideout {
|
hideout {
|
||||||
hostname = "https://localhost:8080"
|
url = "http://localhost:8080"
|
||||||
hostname = ${?HOSTNAME}
|
|
||||||
database {
|
database {
|
||||||
url = "jdbc:h2:./test;MODE=POSTGRESQL"
|
url = "jdbc:h2:./test;MODE=POSTGRESQL"
|
||||||
driver = "org.h2.Driver"
|
driver = "org.h2.Driver"
|
||||||
|
|
Loading…
Reference in New Issue