Skip to content

Commit

Permalink
feat(core): add more options for sign-in uri generation
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaoyijun committed Sep 5, 2024
1 parent 952b796 commit 71816a4
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 6 deletions.
27 changes: 25 additions & 2 deletions kotlin-sdk/kotlin/src/main/kotlin/io/logto/sdk/core/Core.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,15 @@ object Core {
addQueryParameter(QueryKey.REDIRECT_URI, options.redirectUri)
addQueryParameter(QueryKey.RESPONSE_TYPE, ResponseType.CODE)

val usedScopes = ScopeUtils.withDefaultScopes(options.scopes)
addQueryParameter(QueryKey.SCOPE, usedScopes.joinToString(" "))
val usedScopes = if (options.includeReservedScopes == true) {
ScopeUtils.withDefaultScopes(options.scopes)
} else {
options.scopes.orEmpty()
}

if (usedScopes.isNotEmpty()) {
addQueryParameter(QueryKey.SCOPE, usedScopes.joinToString(" "))
}

val usedResources = options.resources.orEmpty()
for (value in usedResources) { addQueryParameter(QueryKey.RESOURCE, value) }
Expand All @@ -48,6 +55,22 @@ object Core {
}

addQueryParameter(QueryKey.PROMPT, options.prompt ?: PromptValue.CONSENT)

options.loginHint?.let {
addQueryParameter(QueryKey.LOGIN_HINT, it)
}

options.firstScreen?.let {
addQueryParameter(QueryKey.FIRST_SCREEN, it)
}

options.identifiers?.let {
addQueryParameter(QueryKey.IDENTIFIER, it.joinToString(" "))
}

options.extraParams?.forEach { (key, value) ->
addQueryParameter(key, value)
}
}.build().toString()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.logto.sdk.core.constant

object FirstScreen {
const val SIGN_IN = "sign_in"
const val REGISTER = "register"
const val RESET_PASSWORD = "reset_password"
const val IDENTIFIER_SIGN_IN = "identifier:sign_in"
const val IDENTIFIER_REGISTER = "identifier:register"
const val SINGLE_SIGN_ON = "single_sign_on"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.logto.sdk.core.constant

object Identifier {
const val USERNAME = "username"
const val EMAIL = "email"
const val PHONE = "phone"
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,8 @@ object QueryKey {
const val STATE = "state"
const val TOKEN = "token"
const val ORGANIZATION_ID = "organization_id"
const val LOGIN_HINT = "login_hint"
const val FIRST_SCREEN = "first_screen"
const val IDENTIFIER = "identifier"
const val DIRECT_SIGN_IN = "direct_sign_in"
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,9 @@ class GenerateSignInUriOptions(
val scopes: List<String>? = null,
val resources: List<String>? = null,
val prompt: String? = null,
val loginHint: String? = null,
val firstScreen: String? = null,
val identifiers: List<String>? = null,
val extraParams: Map<String, String>? = null,
val includeReservedScopes: Boolean? = true,
)
113 changes: 109 additions & 4 deletions kotlin-sdk/kotlin/src/test/kotlin/io/logto/sdk/core/CoreTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,6 @@ class CoreTest {

@Test
fun `generateSignInUri should always contain reserved scopes if only the reserved OPENID is provided`() {

val signInUri = Core.generateSignInUri(
GenerateSignInUriOptions(
authorizationEndpoint = testAuthorizationEndpoint,
Expand All @@ -214,7 +213,6 @@ class CoreTest {

@Test
fun `generateSignInUri should always contain reserved scopes if only the reserved PROFILE is provided`() {

val signInUri = Core.generateSignInUri(
GenerateSignInUriOptions(
authorizationEndpoint = testAuthorizationEndpoint,
Expand All @@ -237,13 +235,117 @@ class CoreTest {
}
}

@Test
fun `generateSignInUri should not contain reserved scopes if includeReserved options is false`() {
val signInUri = Core.generateSignInUri(
GenerateSignInUriOptions(
authorizationEndpoint = testAuthorizationEndpoint,
clientId = testClientId,
redirectUri = testRedirectUri,
codeChallenge = testCodeChallenge,
state = testState,
scopes = listOf(UserScope.CUSTOM_DATA),
includeReservedScopes = false,
),
)

signInUri.toHttpUrl().apply {
assertThat(queryParameter(QueryKey.SCOPE)).isEqualTo(UserScope.CUSTOM_DATA)
}
}

@Test
fun `generateSignInUri should contain login_hint if provided`() {
val loginHint = "abc@logto.io"

val signInUri = Core.generateSignInUri(
GenerateSignInUriOptions(
authorizationEndpoint = testAuthorizationEndpoint,
clientId = testClientId,
redirectUri = testRedirectUri,
codeChallenge = testCodeChallenge,
state = testState,
loginHint = loginHint,
),
)

signInUri.toHttpUrl().apply {
assertThat(queryParameter(QueryKey.LOGIN_HINT)).isEqualTo(loginHint)
}
}

@Test
fun `generateSignInUri should contain first_screen if provided`() {
val firstScreen = FirstScreen.SIGN_IN

val signInUri = Core.generateSignInUri(
GenerateSignInUriOptions(
authorizationEndpoint = testAuthorizationEndpoint,
clientId = testClientId,
redirectUri = testRedirectUri,
codeChallenge = testCodeChallenge,
state = testState,
firstScreen = firstScreen,
)
)

signInUri.toHttpUrl().apply {
assertThat(queryParameter(QueryKey.FIRST_SCREEN)).contains(firstScreen)
}
}

@Test
fun `generateSignInUri should contain identifier if provided`() {
val identifiers = listOf(Identifier.EMAIL, Identifier.PHONE);
val signInUri = Core.generateSignInUri(
GenerateSignInUriOptions(
authorizationEndpoint = testAuthorizationEndpoint,
clientId = testClientId,
redirectUri = testRedirectUri,
codeChallenge = testCodeChallenge,
state = testState,
identifiers = identifiers,
)
)

signInUri.toHttpUrl().apply {
assertThat(queryParameterValues(QueryKey.IDENTIFIER)).contains(
identifiers.joinToString(
" "
)
)
}
}

@Test
fun `generateSignInUri should contain extra params if provided`() {
val extraParamKey = "tenant_id"
val extraParamValue = "abced"

val signInUri = Core.generateSignInUri(
GenerateSignInUriOptions(
authorizationEndpoint = testAuthorizationEndpoint,
clientId = testClientId,
redirectUri = testRedirectUri,
codeChallenge = testCodeChallenge,
state = testState,
extraParams = mapOf(extraParamKey to extraParamValue),
)
)

signInUri.toHttpUrl().apply {
assertThat(queryParameter(extraParamKey)).contains(extraParamValue)
}
}

@Test
fun `generateSignOutUri should contain expected queries`() {
val endSessionEndpoint = "https://logto.dev/oidc/endSession"
val clientId = "clientId"
val postLogoutRedirectUri = "https://myapp.com/logout_callback"

val resultUri = Core.generateSignOutUri(endSessionEndpoint, clientId, postLogoutRedirectUri)
val resultUri =
Core.generateSignOutUri(endSessionEndpoint, clientId, postLogoutRedirectUri)

val constructedUri = resultUri.toHttpUrl()
assertThat(constructedUri.scheme).isEqualTo(endSessionEndpoint.toHttpUrl().scheme)
Expand All @@ -267,7 +369,9 @@ class CoreTest {
assertThat(constructedUri.host).isEqualTo(endSessionEndpoint.toHttpUrl().host)
assertThat(constructedUri.pathSegments).isEqualTo(endSessionEndpoint.toHttpUrl().pathSegments)
assertThat(constructedUri.queryParameter(QueryKey.CLIENT_ID)).isEqualTo(clientId)
assertThat(constructedUri.queryParameter(QueryKey.POST_LOGOUT_REDIRECT_URI)).isEqualTo(null)
assertThat(constructedUri.queryParameter(QueryKey.POST_LOGOUT_REDIRECT_URI)).isEqualTo(
null
)
}

@Test
Expand All @@ -283,3 +387,4 @@ class CoreTest {
.contains(UriConstructionException.Type.INVALID_ENDPOINT.name)
}
}

0 comments on commit 71816a4

Please sign in to comment.