mirror of
https://github.com/gotson/komga.git
synced 2025-12-06 08:32:25 +01:00
fix(api): empty content when x-api-key is sent alongside session
Closes: #2099
This commit is contained in:
parent
bdca990e82
commit
5a5f8d701e
5 changed files with 22 additions and 8 deletions
|
|
@ -20,6 +20,8 @@ class Hasher {
|
||||||
return computeHash(path.inputStream())
|
return computeHash(path.inputStream())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun computeHash(string: String): String = computeHash(string.byteInputStream())
|
||||||
|
|
||||||
fun computeHash(stream: InputStream): String {
|
fun computeHash(stream: InputStream): String {
|
||||||
val hash = Algorithm.XXH3_128.Seeded(SEED.toLong()).createDigest()
|
val hash = Algorithm.XXH3_128.Seeded(SEED.toLong()).createDigest()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package org.gotson.komga.infrastructure.security
|
||||||
import jakarta.servlet.Filter
|
import jakarta.servlet.Filter
|
||||||
import org.gotson.komga.domain.model.UserRoles
|
import org.gotson.komga.domain.model.UserRoles
|
||||||
import org.gotson.komga.infrastructure.configuration.KomgaSettingsProvider
|
import org.gotson.komga.infrastructure.configuration.KomgaSettingsProvider
|
||||||
|
import org.gotson.komga.infrastructure.hash.Hasher
|
||||||
import org.gotson.komga.infrastructure.security.apikey.ApiKeyAuthenticationFilter
|
import org.gotson.komga.infrastructure.security.apikey.ApiKeyAuthenticationFilter
|
||||||
import org.gotson.komga.infrastructure.security.apikey.ApiKeyAuthenticationProvider
|
import org.gotson.komga.infrastructure.security.apikey.ApiKeyAuthenticationProvider
|
||||||
import org.gotson.komga.infrastructure.security.apikey.HeaderApiKeyAuthenticationConverter
|
import org.gotson.komga.infrastructure.security.apikey.HeaderApiKeyAuthenticationConverter
|
||||||
|
|
@ -34,6 +35,7 @@ import org.springframework.security.web.authentication.AnonymousAuthenticationFi
|
||||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler
|
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler
|
||||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource
|
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource
|
||||||
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices
|
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices
|
||||||
|
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter
|
||||||
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher
|
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
|
|
@ -51,6 +53,7 @@ class SecurityConfiguration(
|
||||||
private val opdsAuthenticationEntryPoint: OpdsAuthenticationEntryPoint,
|
private val opdsAuthenticationEntryPoint: OpdsAuthenticationEntryPoint,
|
||||||
private val authenticationEventPublisher: AuthenticationEventPublisher,
|
private val authenticationEventPublisher: AuthenticationEventPublisher,
|
||||||
private val tokenEncoder: TokenEncoder,
|
private val tokenEncoder: TokenEncoder,
|
||||||
|
private val hasher: Hasher,
|
||||||
clientRegistrationRepository: InMemoryClientRegistrationRepository?,
|
clientRegistrationRepository: InMemoryClientRegistrationRepository?,
|
||||||
) {
|
) {
|
||||||
private val oauth2Enabled = clientRegistrationRepository != null
|
private val oauth2Enabled = clientRegistrationRepository != null
|
||||||
|
|
@ -158,7 +161,7 @@ class SecurityConfiguration(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
http.addFilterBefore(restAuthenticationFilter(), AnonymousAuthenticationFilter::class.java)
|
http.addFilterAfter(restAuthenticationFilter(), BasicAuthenticationFilter::class.java)
|
||||||
|
|
||||||
return http.build()
|
return http.build()
|
||||||
}
|
}
|
||||||
|
|
@ -239,19 +242,19 @@ class SecurityConfiguration(
|
||||||
fun koboAuthenticationFilter(): Filter =
|
fun koboAuthenticationFilter(): Filter =
|
||||||
ApiKeyAuthenticationFilter(
|
ApiKeyAuthenticationFilter(
|
||||||
apiKeyAuthenticationProvider(),
|
apiKeyAuthenticationProvider(),
|
||||||
UriRegexApiKeyAuthenticationConverter(Regex("""/kobo/([\w-]+)"""), tokenEncoder, userAgentWebAuthenticationDetailsSource),
|
UriRegexApiKeyAuthenticationConverter(Regex("""/kobo/([\w-]+)"""), hasher, tokenEncoder, userAgentWebAuthenticationDetailsSource),
|
||||||
)
|
)
|
||||||
|
|
||||||
fun kosyncAuthenticationFilter(): Filter =
|
fun kosyncAuthenticationFilter(): Filter =
|
||||||
ApiKeyAuthenticationFilter(
|
ApiKeyAuthenticationFilter(
|
||||||
apiKeyAuthenticationProvider(),
|
apiKeyAuthenticationProvider(),
|
||||||
HeaderApiKeyAuthenticationConverter("X-Auth-User", tokenEncoder, userAgentWebAuthenticationDetailsSource),
|
HeaderApiKeyAuthenticationConverter("X-Auth-User", hasher, tokenEncoder, userAgentWebAuthenticationDetailsSource),
|
||||||
)
|
)
|
||||||
|
|
||||||
fun restAuthenticationFilter(): Filter =
|
fun restAuthenticationFilter(): Filter =
|
||||||
ApiKeyAuthenticationFilter(
|
ApiKeyAuthenticationFilter(
|
||||||
apiKeyAuthenticationProvider(),
|
apiKeyAuthenticationProvider(),
|
||||||
HeaderApiKeyAuthenticationConverter("X-API-Key", tokenEncoder, userAgentWebAuthenticationDetailsSource),
|
HeaderApiKeyAuthenticationConverter("X-API-Key", hasher, tokenEncoder, userAgentWebAuthenticationDetailsSource),
|
||||||
)
|
)
|
||||||
|
|
||||||
fun apiKeyAuthenticationProvider(): AuthenticationManager =
|
fun apiKeyAuthenticationProvider(): AuthenticationManager =
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,8 @@ class ApiKeyAuthenticationFilter(
|
||||||
} catch (ex: AuthenticationException) {
|
} catch (ex: AuthenticationException) {
|
||||||
unsuccessfulAuthentication(request, response, ex)
|
unsuccessfulAuthentication(request, response, ex)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filterChain.doFilter(request, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun unsuccessfulAuthentication(
|
private fun unsuccessfulAuthentication(
|
||||||
|
|
@ -78,7 +80,6 @@ class ApiKeyAuthenticationFilter(
|
||||||
}
|
}
|
||||||
securityContextHolderStrategy.context = context
|
securityContextHolderStrategy.context = context
|
||||||
securityContextRepository.saveContext(context, request, response)
|
securityContextRepository.saveContext(context, request, response)
|
||||||
filterChain.doFilter(request, response)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun authenticationIsRequired(username: String): Boolean {
|
private fun authenticationIsRequired(username: String): Boolean {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package org.gotson.komga.infrastructure.security.apikey
|
package org.gotson.komga.infrastructure.security.apikey
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
|
import org.gotson.komga.infrastructure.hash.Hasher
|
||||||
import org.gotson.komga.infrastructure.security.TokenEncoder
|
import org.gotson.komga.infrastructure.security.TokenEncoder
|
||||||
import org.springframework.security.authentication.AuthenticationDetailsSource
|
import org.springframework.security.authentication.AuthenticationDetailsSource
|
||||||
import org.springframework.security.core.Authentication
|
import org.springframework.security.core.Authentication
|
||||||
|
|
@ -11,11 +12,13 @@ import org.springframework.security.web.authentication.AuthenticationConverter
|
||||||
* and convert it to an [ApiKeyAuthenticationToken]
|
* and convert it to an [ApiKeyAuthenticationToken]
|
||||||
*
|
*
|
||||||
* @property headerName the header name from which to retrieve the API key
|
* @property headerName the header name from which to retrieve the API key
|
||||||
|
* @property hasher the hasher to use to encode the API key as username in the [Authentication] object
|
||||||
* @property tokenEncoder the encoder to use to encode the API key in the [Authentication] object
|
* @property tokenEncoder the encoder to use to encode the API key in the [Authentication] object
|
||||||
* @property authenticationDetailsSource the [AuthenticationDetailsSource] to enrich the [Authentication] details
|
* @property authenticationDetailsSource the [AuthenticationDetailsSource] to enrich the [Authentication] details
|
||||||
*/
|
*/
|
||||||
class HeaderApiKeyAuthenticationConverter(
|
class HeaderApiKeyAuthenticationConverter(
|
||||||
private val headerName: String,
|
private val headerName: String,
|
||||||
|
private val hasher: Hasher,
|
||||||
private val tokenEncoder: TokenEncoder,
|
private val tokenEncoder: TokenEncoder,
|
||||||
private val authenticationDetailsSource: AuthenticationDetailsSource<HttpServletRequest, *>,
|
private val authenticationDetailsSource: AuthenticationDetailsSource<HttpServletRequest, *>,
|
||||||
) : AuthenticationConverter {
|
) : AuthenticationConverter {
|
||||||
|
|
@ -23,7 +26,8 @@ class HeaderApiKeyAuthenticationConverter(
|
||||||
request
|
request
|
||||||
.getHeader(headerName)
|
.getHeader(headerName)
|
||||||
?.let {
|
?.let {
|
||||||
val (maskedToken, hashedToken) = it.take(6) + "*".repeat(6) to tokenEncoder.encode(it)
|
val maskedToken = hasher.computeHash(it)
|
||||||
|
val hashedToken = tokenEncoder.encode(it)
|
||||||
ApiKeyAuthenticationToken
|
ApiKeyAuthenticationToken
|
||||||
.unauthenticated(maskedToken, hashedToken)
|
.unauthenticated(maskedToken, hashedToken)
|
||||||
.apply { details = authenticationDetailsSource.buildDetails(request) }
|
.apply { details = authenticationDetailsSource.buildDetails(request) }
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package org.gotson.komga.infrastructure.security.apikey
|
package org.gotson.komga.infrastructure.security.apikey
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
|
import org.gotson.komga.infrastructure.hash.Hasher
|
||||||
import org.gotson.komga.infrastructure.security.TokenEncoder
|
import org.gotson.komga.infrastructure.security.TokenEncoder
|
||||||
import org.springframework.security.authentication.AuthenticationDetailsSource
|
import org.springframework.security.authentication.AuthenticationDetailsSource
|
||||||
import org.springframework.security.core.Authentication
|
import org.springframework.security.core.Authentication
|
||||||
|
|
@ -11,11 +12,13 @@ import org.springframework.security.web.authentication.AuthenticationConverter
|
||||||
* request URI, and convert it to an [ApiKeyAuthenticationToken]
|
* request URI, and convert it to an [ApiKeyAuthenticationToken]
|
||||||
*
|
*
|
||||||
* @property tokenRegex the regex used to extract the API key
|
* @property tokenRegex the regex used to extract the API key
|
||||||
* @property tokenEncoder the encoder to use to encode the API key in the [Authentication] object
|
* @property hasher the hasher to use to encode the API key as username in the [Authentication] object
|
||||||
|
* @property tokenEncoder the encoder to use to encode the API key as credentials in the [Authentication] object
|
||||||
* @property authenticationDetailsSource the [AuthenticationDetailsSource] to enrich the [Authentication] details
|
* @property authenticationDetailsSource the [AuthenticationDetailsSource] to enrich the [Authentication] details
|
||||||
*/
|
*/
|
||||||
class UriRegexApiKeyAuthenticationConverter(
|
class UriRegexApiKeyAuthenticationConverter(
|
||||||
private val tokenRegex: Regex,
|
private val tokenRegex: Regex,
|
||||||
|
private val hasher: Hasher,
|
||||||
private val tokenEncoder: TokenEncoder,
|
private val tokenEncoder: TokenEncoder,
|
||||||
private val authenticationDetailsSource: AuthenticationDetailsSource<HttpServletRequest, *>,
|
private val authenticationDetailsSource: AuthenticationDetailsSource<HttpServletRequest, *>,
|
||||||
) : AuthenticationConverter {
|
) : AuthenticationConverter {
|
||||||
|
|
@ -24,7 +27,8 @@ class UriRegexApiKeyAuthenticationConverter(
|
||||||
?.let {
|
?.let {
|
||||||
tokenRegex.find(it)?.groupValues?.lastOrNull()
|
tokenRegex.find(it)?.groupValues?.lastOrNull()
|
||||||
}?.let {
|
}?.let {
|
||||||
val (maskedToken, hashedToken) = it.take(6) + "*".repeat(6) to tokenEncoder.encode(it)
|
val maskedToken = hasher.computeHash(it)
|
||||||
|
val hashedToken = tokenEncoder.encode(it)
|
||||||
ApiKeyAuthenticationToken
|
ApiKeyAuthenticationToken
|
||||||
.unauthenticated(maskedToken, hashedToken)
|
.unauthenticated(maskedToken, hashedToken)
|
||||||
.apply { details = authenticationDetailsSource.buildDetails(request) }
|
.apply { details = authenticationDetailsSource.buildDetails(request) }
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue