Auth.kt (6153B)
1 /* 2 * This file is part of GNU Taler 3 * (C) 2026 Taler Systems S.A. 4 * 5 * GNU Taler is free software; you can redistribute it and/or modify it under the 6 * terms of the GNU General Public License as published by the Free Software 7 * Foundation; either version 3, or (at your option) any later version. 8 * 9 * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY 10 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public License along with 14 * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 17 package net.taler.common 18 19 import kotlinx.serialization.KSerializer 20 import kotlinx.serialization.SerialName 21 import kotlinx.serialization.Serializable 22 import kotlinx.serialization.SerializationException 23 import kotlinx.serialization.descriptors.SerialDescriptor 24 import kotlinx.serialization.descriptors.buildClassSerialDescriptor 25 import kotlinx.serialization.descriptors.element 26 import kotlinx.serialization.encoding.Decoder 27 import kotlinx.serialization.encoding.Encoder 28 import kotlinx.serialization.json.JsonDecoder 29 import kotlinx.serialization.json.JsonElement 30 import kotlinx.serialization.json.JsonEncoder 31 import kotlinx.serialization.json.JsonObject 32 import kotlinx.serialization.json.JsonPrimitive 33 import kotlinx.serialization.json.buildJsonObject 34 import kotlinx.serialization.json.long 35 import kotlinx.serialization.json.longOrNull 36 37 @Serializable 38 data class TokenSuccessResponse( 39 val expiration: Timestamp, 40 41 @SerialName("access_token") 42 val accessToken: String, 43 ) 44 45 @Serializable 46 data class ChallengesResponse( 47 val challenges: List<Challenge>, 48 49 @SerialName("combi_and") 50 val combiAnd: Boolean, 51 ) 52 53 @Serializable 54 enum class TanChannel { 55 @SerialName("sms") 56 SMS, 57 58 @SerialName("email") 59 EMAIL, 60 } 61 62 @Serializable 63 data class Challenge( 64 @SerialName("challenge_id") 65 val challengeId: String, 66 67 @SerialName("tan_channel") 68 val tanChannel: TanChannel, 69 70 @SerialName("tan_info") 71 val tanInfo: String, 72 ) 73 74 @Serializable 75 data class ChallengeConfirmRequest( 76 val tan: String, 77 ) 78 79 @Serializable 80 data class TokenRequest( 81 val scope: String, 82 val duration: TokenDuration 83 ) 84 85 @Serializable(with = TokenDuration.Serializer::class) 86 sealed class TokenDuration { 87 data object Forever : TokenDuration() 88 data class Micros(val us: Long) : TokenDuration() 89 90 object Serializer : KSerializer<TokenDuration> { 91 override val descriptor: SerialDescriptor = buildClassSerialDescriptor("TokenDuration") { 92 element<JsonElement>("d_us") 93 } 94 95 override fun serialize(encoder: Encoder, value: TokenDuration) { 96 val jsonEncoder = encoder as? JsonEncoder 97 ?: throw SerializationException("Can be serialized only by JSON") 98 val obj = when (value) { 99 is Forever -> buildJsonObject { 100 put("d_us", JsonPrimitive("forever")) 101 } 102 is Micros -> buildJsonObject { 103 put("d_us", JsonPrimitive(value.us)) 104 } 105 } 106 jsonEncoder.encodeJsonElement(obj) 107 } 108 109 override fun deserialize(decoder: Decoder): TokenDuration { 110 val jsonDecoder = decoder as? JsonDecoder 111 ?: throw SerializationException("Can be deserialized only by JSON") 112 val element = jsonDecoder.decodeJsonElement() 113 if (element !is JsonObject) { 114 throw SerializationException("Expected JSON object for TokenDuration, got: $element") 115 } 116 val field = element["d_us"] 117 ?: throw SerializationException("Missing 'd_us' field in $element") 118 return when { 119 field is JsonPrimitive && field.longOrNull != null -> 120 Micros(field.long) 121 field is JsonPrimitive && field.isString && field.content == "forever" -> 122 Forever 123 else -> 124 throw SerializationException("Invalid 'd_us' value: $field") 125 } 126 } 127 } 128 } 129 130 @Serializable(with = TokenExpiration.Serializer::class) 131 sealed class TokenExpiration { 132 data class Seconds(val t_s: Long) : TokenExpiration() 133 data object Never : TokenExpiration() 134 135 object Serializer : KSerializer<TokenExpiration> { 136 override val descriptor: SerialDescriptor = 137 buildClassSerialDescriptor("TokenExpiration") { 138 element<JsonElement>("t_s") 139 } 140 141 override fun serialize(encoder: Encoder, value: TokenExpiration) { 142 val jsonEncoder = encoder as? JsonEncoder 143 ?: throw SerializationException("TokenExpiration can be serialized only by JSON") 144 val obj = when (value) { 145 is Seconds -> 146 buildJsonObject { put("t_s", JsonPrimitive(value.t_s)) } 147 Never -> 148 buildJsonObject { put("t_s", JsonPrimitive("never")) } 149 } 150 jsonEncoder.encodeJsonElement(obj) 151 } 152 153 override fun deserialize(decoder: Decoder): TokenExpiration { 154 val jsonDecoder = decoder as? JsonDecoder 155 ?: throw SerializationException("TokenExpiration can be deserialized only by JSON") 156 val element = jsonDecoder.decodeJsonElement() 157 if (element !is JsonObject) { 158 throw SerializationException("Expected JSON object for TokenExpiration, got: $element") 159 } 160 val field = element["t_s"] 161 ?: throw SerializationException("Missing 't_s' in TokenExpiration: $element") 162 163 return when { 164 field is JsonPrimitive && field.longOrNull != null -> 165 Seconds(field.long) 166 field is JsonPrimitive && field.isString && field.content == "never" -> 167 Never 168 else -> 169 throw SerializationException("Invalid 't_s' value in TokenExpiration: $field") 170 } 171 } 172 } 173 }