Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for tokens via cookies #48

Merged
merged 3 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.descope.sdk.DescopeSdk
import com.descope.types.DeliveryMethod
import com.descope.types.OAuthProvider
import com.descope.types.SignUpDetails
import java.net.HttpCookie

internal class DescopeClient(internal val config: DescopeConfig) : HttpClient(config.baseUrl, config.logger) {

Expand Down Expand Up @@ -357,4 +358,4 @@ private fun DeliveryMethod.route() = this.name.lowercase()

// Utilities

private val emptyResponse: (String) -> Unit = {}
private val emptyResponse: (String, List<HttpCookie>) -> Unit = {_, _ ->}
24 changes: 19 additions & 5 deletions descopesdk/src/main/java/com/descope/internal/http/HttpClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.descope.types.DescopeException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONObject
import java.net.HttpCookie
import java.net.URL
import javax.net.ssl.HttpsURLConnection

Expand All @@ -22,14 +23,14 @@ internal open class HttpClient(

suspend fun <T> get(
route: String,
decoder: (String) -> T,
decoder: (String, List<HttpCookie>) -> T,
headers: Map<String, String> = emptyMap(),
params: Map<String, String?> = emptyMap(),
) = call(route, "GET", decoder, headers = headers, params = params)

suspend fun <T> post(
route: String,
decoder: (String) -> T,
decoder: (String, List<HttpCookie>) -> T,
body: Map<String, Any?> = emptyMap(),
headers: Map<String, String> = emptyMap(),
params: Map<String, String?> = emptyMap(),
Expand All @@ -40,15 +41,15 @@ internal open class HttpClient(
open val basePath = "/"

open val defaultHeaders: Map<String, String> = emptyMap()

open fun exceptionFromResponse(response: String): Exception? = null

// Internal

private suspend fun <T> call(
route: String,
method: String,
decoder: (String) -> T,
decoder: (String, List<HttpCookie>) -> T,
body: Map<String, Any?>? = null,
headers: Map<String, String>,
params: Map<String, String?>,
Expand Down Expand Up @@ -83,7 +84,7 @@ internal open class HttpClient(
if (responseCode == HttpsURLConnection.HTTP_OK) {
val response = connection.inputStream.bufferedReader().use { it.readText() }
logger?.log(Debug, "Received response body", response)
decoder(response)
decoder(response, connection.cookies)
} else {
val response = connection.errorStream.bufferedReader().use { it.readText() }
exceptionFromResponse(response)?.run {
Expand Down Expand Up @@ -117,3 +118,16 @@ internal open class HttpClient(
return URL(urlString)
}
}

private val HttpsURLConnection.cookies: List<HttpCookie>
get() {
val cookies = mutableListOf<HttpCookie>()
headerFields.keys.find { it?.lowercase() == "set-cookie" }?.let { key ->
headerFields[key]?.forEach {
try {
cookies.addAll(HttpCookie.parse(it))
} catch (ignored: Exception) {}
}
}
return cookies.toList()
}
44 changes: 33 additions & 11 deletions descopesdk/src/main/java/com/descope/internal/http/Responses.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,33 @@ import com.descope.internal.others.secToMs
import com.descope.internal.others.stringOrEmptyAsNull
import com.descope.internal.others.toStringList
import org.json.JSONObject
import java.net.HttpCookie

private const val SESSION_COOKIE_NAME = "DS"
private const val REFRESH_COOKIE_NAME = "DSR"

internal data class JwtServerResponse(
val sessionJwt: String,
val sessionJwt: String?,
val refreshJwt: String?,
val user: UserResponse?,
val firstSeen: Boolean,
) {
companion object {
fun fromJson(json: String) = JSONObject(json).run {
fun fromJson(json: String, cookies: List<HttpCookie>) = JSONObject(json).run {
var sessionJwt: String? = null
var refreshJwt: String? = null

// check cookies for tokens
cookies.forEach {
when (it.name) {
SESSION_COOKIE_NAME -> sessionJwt = it.value
REFRESH_COOKIE_NAME -> refreshJwt = it.value
}
}

JwtServerResponse(
sessionJwt = getString("sessionJwt"),
refreshJwt = stringOrEmptyAsNull("refreshJwt"),
sessionJwt = stringOrEmptyAsNull("sessionJwt") ?: sessionJwt,
refreshJwt = stringOrEmptyAsNull("refreshJwt") ?: refreshJwt,
user = optJSONObject("user")?.run { UserResponse.fromJson(this) },
firstSeen = optBoolean("firstSeen"),
)
Expand All @@ -37,7 +52,8 @@ internal data class UserResponse(
val customAttributes: Map<String, Any>,
) {
companion object {
fun fromJson(json: String) = fromJson(JSONObject(json))
@Suppress("UNUSED_PARAMETER")
fun fromJson(json: String, cookies: List<HttpCookie>) = fromJson(JSONObject(json))

fun fromJson(json: JSONObject) = json.run {
UserResponse(
Expand All @@ -61,7 +77,8 @@ internal data class MaskedAddressServerResponse(
val maskedPhone: String? = null,
) {
companion object {
fun fromJson(json: String) = JSONObject(json).run {
@Suppress("UNUSED_PARAMETER")
fun fromJson(json: String, cookies: List<HttpCookie>) = JSONObject(json).run {
MaskedAddressServerResponse(
maskedEmail = stringOrEmptyAsNull("maskedEmail"),
maskedPhone = stringOrEmptyAsNull("maskedPhone"),
Expand All @@ -78,7 +95,8 @@ internal data class PasswordPolicyServerResponse(
val nonAlphanumeric: Boolean,
) {
companion object {
fun fromJson(json: String) = JSONObject(json).run {
@Suppress("UNUSED_PARAMETER")
fun fromJson(json: String, cookies: List<HttpCookie>) = JSONObject(json).run {
PasswordPolicyServerResponse(
minLength = getInt("minLength"),
lowercase = optBoolean("lowercase"),
Expand All @@ -96,7 +114,8 @@ internal data class EnchantedLinkServerResponse(
val maskedEmail: String,
) {
companion object {
fun fromJson(json: String) = JSONObject(json).run {
@Suppress("UNUSED_PARAMETER")
fun fromJson(json: String, cookies: List<HttpCookie>) = JSONObject(json).run {
EnchantedLinkServerResponse(
linkId = getString("linkId"),
pendingRef = getString("pendingRef"),
Expand All @@ -112,7 +131,8 @@ internal data class TotpServerResponse(
val key: String,
) {
companion object {
fun fromJson(json: String) = JSONObject(json).run {
@Suppress("UNUSED_PARAMETER")
fun fromJson(json: String, cookies: List<HttpCookie>) = JSONObject(json).run {
TotpServerResponse(
provisioningUrl = getString("provisioningUrl"),
image = getString("image").toByteArray(),
Expand Down Expand Up @@ -146,7 +166,8 @@ internal data class OAuthServerResponse(
val url: String,
) {
companion object {
fun fromJson(json: String) = JSONObject(json).run {
@Suppress("UNUSED_PARAMETER")
fun fromJson(json: String, cookies: List<HttpCookie>) = JSONObject(json).run {
OAuthServerResponse(
url = getString("url")
)
Expand All @@ -158,7 +179,8 @@ internal data class SsoServerResponse(
val url: String,
) {
companion object {
fun fromJson(json: String) = JSONObject(json).run {
@Suppress("UNUSED_PARAMETER")
fun fromJson(json: String, cookies: List<HttpCookie>) = JSONObject(json).run {
SsoServerResponse(
url = getString("url")
)
Expand Down
12 changes: 8 additions & 4 deletions descopesdk/src/main/java/com/descope/internal/routes/Shared.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ internal fun UserResponse.convert(): DescopeUser = DescopeUser(
)

internal fun JwtServerResponse.convert(): AuthenticationResponse {
val sessionJwt = sessionJwt ?: throw DescopeException.decodeError.with(message = "Missing session JWT")
val refreshJwt = refreshJwt ?: throw DescopeException.decodeError.with(message = "Missing refresh JWT")
val user = user ?: throw DescopeException.decodeError.with(message = "Missing user details")
return AuthenticationResponse(
Expand All @@ -50,10 +51,13 @@ internal fun JwtServerResponse.convert(): AuthenticationResponse {
)
}

internal fun JwtServerResponse.toRefreshResponse(): RefreshResponse = RefreshResponse(
refreshToken = refreshJwt?.run { Token(this) },
sessionToken = Token(sessionJwt),
)
internal fun JwtServerResponse.toRefreshResponse(): RefreshResponse {
val sessionJwt = sessionJwt ?: throw DescopeException.decodeError.with(message = "Missing session JWT")
return RefreshResponse(
refreshToken = refreshJwt?.run { Token(this) },
sessionToken = Token(sessionJwt),
)
}

internal fun MaskedAddressServerResponse.convert(method: DeliveryMethod) = when (method) {
DeliveryMethod.Email -> maskedEmail ?: throw DescopeException.decodeError.with(message = "masked email not received")
Expand Down
Loading