Skip to content

Commit

Permalink
Merge pull request #179 from okta/rn_email-magic-link
Browse files Browse the repository at this point in the history
Add email magic link support and update sample
  • Loading branch information
rajdeepnanua-okta authored Feb 7, 2023
2 parents 23389cf + 591582e commit f643c17
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 6 deletions.
4 changes: 3 additions & 1 deletion dynamic-app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ android {
buildConfigField "String", 'REDIRECT_URI', "\"${oktaProperties.getProperty('signInRedirectUri')}\""

manifestPlaceholders = [
"oktaIdxRedirectScheme": parseScheme(oktaProperties.getProperty('signInRedirectUri'))
"oktaIdxRedirectScheme": parseScheme(oktaProperties.getProperty('signInRedirectUri')),
"oktaIdxEmailHost": oktaProperties.getProperty('emailRedirectHost'),
"oktaIdxEmailPrefix": oktaProperties.getProperty('emailRedirectPrefix')
]

testInstrumentationRunner 'io.cucumber.android.runner.CucumberAndroidJUnitRunner'
Expand Down
17 changes: 17 additions & 0 deletions dynamic-app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,22 @@
<data android:scheme="${oktaIdxRedirectScheme}" />
</intent-filter>
</activity>

<activity
android:name=".EmailMagicLinkRedirectActivity"
android:autoRemoveFromRecents="true"
android:exported="true"
android:launchMode="singleInstance">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data android:scheme="https" />
<data android:host="${oktaIdxEmailHost}" />
<data android:pathPrefix="${oktaIdxEmailPrefix}" />
</intent-filter>
</activity>
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2023-Present Okta, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.okta.idx.android.dynamic

import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

class EmailMagicLinkRedirectActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val intent = Intent(this, MainActivity::class.java)
intent.action = MainActivity.EMAIL_REDIRECT_ACTION
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
intent.data = getIntent().data
startActivity(intent)

finish()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2023-Present Okta, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.okta.idx.android.dynamic

import android.content.Context
import android.net.Uri

object EmailRedirectCoordinator {
var listener: EmailRedirectListener? = null
}

typealias EmailRedirectListener = ((Uri, Context) -> Unit)
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ package com.okta.idx.android.dynamic
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import timber.log.Timber

class MainActivity : AppCompatActivity() {
companion object {
const val SOCIAL_REDIRECT_ACTION = "SocialRedirect"
const val EMAIL_REDIRECT_ACTION = "EmailRedirect"
}

override fun onCreate(savedInstanceState: Bundle?) {
Expand All @@ -33,6 +35,22 @@ class MainActivity : AppCompatActivity() {
public override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)

when (intent?.action) {
SOCIAL_REDIRECT_ACTION -> {
intent.data?.let {
SocialRedirectCoordinator.listener?.invoke(it)
} ?: run {
Timber.d("SocialRedirect intent data missing")
}
}
EMAIL_REDIRECT_ACTION -> {
intent.data?.let {
EmailRedirectCoordinator.listener?.invoke(it, this)
} ?: run {
Timber.d("EmailRedirect intent data missing")
}
}
}
if (intent?.action == SOCIAL_REDIRECT_ACTION) {
intent.data?.let {
SocialRedirectCoordinator.listener?.invoke(it)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import androidx.lifecycle.viewModelScope
import com.okta.authfoundation.client.OidcClientResult
import com.okta.authfoundationbootstrap.CredentialBootstrap
import com.okta.idx.android.dynamic.BuildConfig
import com.okta.idx.android.dynamic.EmailRedirectCoordinator
import com.okta.idx.android.dynamic.SocialRedirectCoordinator
import com.okta.idx.kotlin.client.IdxRedirectResult
import com.okta.idx.kotlin.client.InteractionCodeFlow
Expand Down Expand Up @@ -56,11 +57,13 @@ internal class DynamicAuthViewModel(private val recoveryToken: String) : ViewMod

init {
createClient()
SocialRedirectCoordinator.listener = ::handleRedirect
SocialRedirectCoordinator.listener = ::handleSocialRedirect
EmailRedirectCoordinator.listener = ::handleEmailRedirect
}

override fun onCleared() {
SocialRedirectCoordinator.listener = null
EmailRedirectCoordinator.listener = null
}

private fun createClient() {
Expand Down Expand Up @@ -118,7 +121,7 @@ internal class DynamicAuthViewModel(private val recoveryToken: String) : ViewMod
}
}

private fun handleRedirect(uri: Uri) {
private fun handleSocialRedirect(uri: Uri) {
viewModelScope.launch {
when (val redirectResult = flow?.evaluateRedirectUri(uri)) {
is IdxRedirectResult.Error -> {
Expand All @@ -139,6 +142,23 @@ internal class DynamicAuthViewModel(private val recoveryToken: String) : ViewMod
}
}

private fun handleEmailRedirect(uri: Uri, context: Context) {
viewModelScope.launch {
val idxForm = _state.value as DynamicAuthState.Form
idxForm.idxResponse.remediations.firstOrNull {
it.type == IdxRemediation.Type.CHALLENGE_AUTHENTICATOR
}?.let {
val otpCode = uri.getQueryParameter("otp")
if (otpCode != null) {
it.form["credentials.passcode"]?.apply {
value = otpCode
proceed(it, context)
}
}
}
}
}

private suspend fun handleResponse(response: IdxResponse) {
// If a response is successful, immediately exchange it for a token and exit.
if (response.isLoginSuccessful) {
Expand Down Expand Up @@ -224,17 +244,17 @@ internal class DynamicAuthViewModel(private val recoveryToken: String) : ViewMod
* Get text fields, checkboxes, radio buttons and radio button groups from `IdxRemediation.form.visibleFields`.
*/
private fun IdxRemediation.Form.Field.asDynamicAuthFields(): List<DynamicAuthField> {
return when (true) {
return when {
// Nested form inside a field.
form?.visibleFields?.isNullOrEmpty() == false -> {
!form?.visibleFields.isNullOrEmpty() -> {
val result = mutableListOf<DynamicAuthField>()
form?.visibleFields?.forEach {
result += it.asDynamicAuthFields()
}
result
}
// Options represent multiple choice items like authenticators and can be nested.
options?.isNullOrEmpty() == false -> {
!options.isNullOrEmpty() -> {
options?.let { options ->
val transformed = options.map {
val fields =
Expand Down
2 changes: 2 additions & 0 deletions okta.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@
issuer=https://this.does.not.exist.com/oauth2/default
clientId=test-client-id
signInRedirectUri=com.okta.sample.android:/login
emailRedirectHost=example.domain.com
emailRedirectPrefix=/example/path/prefix/

0 comments on commit f643c17

Please sign in to comment.