Skip to content

Commit

Permalink
fix: try get hashing key everytime for http
Browse files Browse the repository at this point in the history
  • Loading branch information
jimirocks committed Dec 6, 2024
1 parent 40c5f03 commit 1130222
Show file tree
Hide file tree
Showing 4 changed files with 25 additions and 30 deletions.
9 changes: 2 additions & 7 deletions src/commonMain/kotlin/LoxoneCrypto.kt
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,8 @@ internal object LoxoneCrypto {
}
}

@OptIn(ExperimentalStdlibApi::class)
fun loxoneHmac(token: Token, operation: String): String =
token.withTokenAndKey { tokenVal, key ->
bytesToHex(HmacSHA256(key).doFinal(tokenVal.encodeToByteArray())).also { finalHash ->
logger.trace { "$operation final hash: $finalHash" }
}
}
fun loxoneHmac(token: Token, hashing: Hashing, operation: String): String =
loxoneHmac(checkNotNull(token.token) { "Can't hash non-filled token" }, hashing, operation)

@OptIn(ExperimentalStdlibApi::class)
private fun loxoneDigest(secret: String, hashing: Hashing): String {
Expand Down
25 changes: 12 additions & 13 deletions src/commonMain/kotlin/LoxoneTokenAuthenticator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ class LoxoneTokenAuthenticator @JvmOverloads constructor(

private val mutex = Mutex()

private var hashing: Hashing? = null

private var token: Token? by Delegates.observable(repository.getToken(profile)) { _, _, newValue ->
if (newValue != null) {
logger.info {
Expand All @@ -45,21 +43,17 @@ class LoxoneTokenAuthenticator @JvmOverloads constructor(

suspend fun ensureAuthenticated(client: LoxoneClient) =
execConditionalWithMutex({
// Token is not usable or websocket client is not authenticated
!TokenState(token).isUsable || (client is WebsocketLoxoneClient && !authWebsockets.contains(client))
!TokenState(token).isUsable
|| (client is WebsocketLoxoneClient && !authWebsockets.contains(client))
}) {
if (hashing == null) {
hashing = client.callForMsg(commandForUser(user))
}

val hashing = client.callForMsg(commandForUser(user))
val state = TokenState(token)

when {
state.isExpired -> {
logger.debug { "Token expired, requesting new one" }
token = client.callForMsg(
Tokens.get(
loxoneHashing(profile.credentials!!.password, checkNotNull(hashing), "getttoken", user),
loxoneHashing(profile.credentials!!.password, hashing, "getttoken", user),
user,
settings.tokenPermission,
settings.clientId,
Expand All @@ -79,7 +73,7 @@ class LoxoneTokenAuthenticator @JvmOverloads constructor(
else -> {
if (client is WebsocketLoxoneClient) {
logger.debug { "Authenticating websocket with token $token" }
val authResponse = client.callForMsg(Tokens.auth(tokenHash("authenticate"), user))
val authResponse = client.callForMsg(Tokens.auth(tokenHash(client, "authenticate"), user))
token = token!!.merge(authResponse)
authWebsockets.add(client)
}
Expand All @@ -89,7 +83,7 @@ class LoxoneTokenAuthenticator @JvmOverloads constructor(

suspend fun killToken(client: LoxoneClient) = execConditionalWithMutex({ TokenState(token).isUsable }) {
logger.debug { "Going to kill token $token" }
client.callForMsg(Tokens.kill(tokenHash("killtoken"), user))
client.callForMsg(Tokens.kill(tokenHash(client, "killtoken"), user))
logger.info { "Token killed" }
token = null
authWebsockets.remove(client)
Expand All @@ -101,7 +95,12 @@ class LoxoneTokenAuthenticator @JvmOverloads constructor(
}
}

fun tokenHash(operation: String) = LoxoneCrypto.loxoneHmac(checkNotNull(token), operation)
suspend fun tokenHash(client: LoxoneClient, operation: String) =
LoxoneCrypto.loxoneHmac(
checkNotNull(token) { "Token must be present for hashing" },
client.callForMsg(commandForUser(user)),
operation
)

private suspend fun execConditionalWithMutex(condition: () -> Boolean, block: suspend () -> Unit) {
if (condition()) {
Expand Down
16 changes: 11 additions & 5 deletions src/commonMain/kotlin/ktor/KtorHttpLoxoneClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -108,17 +108,23 @@ class KtorHttpLoxoneClient internal constructor(
httpClient.close()
}

private fun HttpRequestBuilder.commandRequest(addAuth: Boolean = true, pathBuilder: URLBuilder.() -> Unit) {
private suspend fun HttpRequestBuilder.commandRequest(addAuth: Boolean = true, pathBuilder: URLBuilder.() -> Unit) {
val userAndHash = if (addAuth && authentication is LoxoneAuth.Token) {
val authenticator = authentication.authenticator
authenticator.user to authenticator.tokenHash(this@KtorHttpLoxoneClient, "http-autht")
} else {
null
}

url {
protocol = if (endpoint.useSsl) URLProtocol.HTTPS else URLProtocol.HTTP
host = endpoint.host
port = endpoint.port
appendEncodedPathSegments(endpoint.path)
pathBuilder()
if (addAuth && authentication is LoxoneAuth.Token) {
val authenticator = authentication.authenticator
parameters.append("autht", authenticator.tokenHash("http-autht"))
parameters.append("user", authenticator.user)
if (userAndHash != null) {
parameters.append("user", userAndHash.first)
parameters.append("autht", userAndHash.second)
}
}
}
Expand Down
5 changes: 0 additions & 5 deletions src/commonMain/kotlin/message/Token.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,6 @@ data class Token(
*/
fun secondsToExpireFromNow() = LoxoneTime.getUnixEpochSeconds(validUntil) - Clock.System.now().epochSeconds

fun <T> withTokenAndKey(block: (String, ByteArray) -> T): T {
check(filled) { "Can't invoke block(token, key) on nonfilled token" }
return block(token!!, key!!)
}

/**
* Merges the given token to this one and returns the merged token. The [Token.token] and [Token.key] are taken
* from given token only if they are not null, otherwise the values from this token are used. Other properties are
Expand Down

0 comments on commit 1130222

Please sign in to comment.