Skip to content

Commit

Permalink
Implement support for Client Side Token Generation
Browse files Browse the repository at this point in the history
  • Loading branch information
Ian Bird committed Feb 23, 2024
1 parent ac1fae2 commit 506071f
Show file tree
Hide file tree
Showing 29 changed files with 1,291 additions and 163 deletions.
10 changes: 9 additions & 1 deletion dev-app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,16 @@
android:supportsRtl="true"
tools:targetApi="31">

<!-- The metadata consumed by the Dev App + SDK -->
<!-- Optional override for the default API URL. -->
<meta-data android:name="uid2_api_url" android:value="https://operator-integ.uidapi.com"/>

<!-- Metadata Required for Client Side Integration -->
<!-- This information will be provided when you register your app with the UID2 operator. -->
<meta-data android:name="uid2_api_public_key" android:value="UID2-X-L-MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEtXJdTSZAYHvoRDWiehMHoWF1BNPuqLs5w2ZHiAZ1IJc7O4/z0ojPTB0V+KYX/wxQK0hxx6kxCvHj335eI/ZQsQ=="/>
<meta-data android:name="uid2_api_subscription_id" android:value="4WvryDGbR5"/>

<!-- Metadata Required for Server Side Integration -->
<!-- This information is only consumed by the DevApp (not the SDK) to simulate a server side integration. -->
<meta-data android:name="uid2_api_key" android:value=""/>
<meta-data android:name="uid2_api_secret" android:value=""/>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ class AppUID2Client(
}

private fun decryptResponse(key: String, data: String) =
DataEnvelope.decrypt(key, data, false)?.toString(Charsets.UTF_8)
DataEnvelope().decrypt(key, data, true)?.toString(Charsets.UTF_8)

private fun Long.toByteArray() = ByteBuffer.allocate(Long.SIZE_BYTES).apply {
order(ByteOrder.BIG_ENDIAN)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.uid2.dev.network

import com.uid2.UID2Exception

/**
* The exception thrown when an error occurred in the Development Application's UID2 Client.
*/
class AppUID2ClientException(message: String? = null, cause: Throwable? = null) : Exception(message, cause)
class AppUID2ClientException(message: String? = null, cause: Throwable? = null) : UID2Exception(message, cause)
63 changes: 61 additions & 2 deletions dev-app/src/main/java/com/uid2/dev/ui/MainScreen.kt
Original file line number Diff line number Diff line change
@@ -1,25 +1,44 @@
package com.uid2.dev.ui

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Checkbox
import androidx.compose.material.Divider
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.LocalMinimumInteractiveComponentEnforcement
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Email
import androidx.compose.material.icons.filled.Phone
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.uid2.dev.ui.MainScreenAction.EmailChanged
import com.uid2.dev.ui.MainScreenAction.PhoneChanged
import com.uid2.dev.ui.MainScreenAction.RefreshButtonPressed
import com.uid2.dev.ui.MainScreenAction.ResetButtonPressed
import com.uid2.dev.ui.MainScreenState.ErrorState
import com.uid2.dev.ui.MainScreenState.LoadingState
import com.uid2.dev.ui.MainScreenState.UserUpdatedState
import com.uid2.dev.ui.views.ActionButtonView
import com.uid2.dev.ui.views.EmailInputView
import com.uid2.dev.ui.views.ErrorView
import com.uid2.dev.ui.views.IdentityInputView
import com.uid2.dev.ui.views.LoadingView
import com.uid2.dev.ui.views.UserIdentityView
import com.uid2.devapp.R

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun MainScreen(viewModel: MainScreenViewModel) {
val viewState by viewModel.viewState.collectAsState()
Expand All @@ -33,9 +52,49 @@ fun MainScreen(viewModel: MainScreenViewModel) {
)
},
) { padding ->
val checkedState = remember { mutableStateOf(true) }

Column(modifier = Modifier.padding(10.dp, 10.dp, 10.dp, 10.dp + padding.calculateBottomPadding())) {
// The top of the View provides a way for the Email Address to be entered.
EmailInputView(Modifier, onEmailEntered = { viewModel.processAction(EmailChanged(it)) })
IdentityInputView(
modifier = Modifier.padding(bottom = 6.dp),
label = stringResource(R.string.email),
icon = Icons.Default.Email,
onEntered = { viewModel.processAction(EmailChanged(it, checkedState.value)) },
)

IdentityInputView(
label = stringResource(R.string.phone),
icon = Icons.Default.Phone,
onEntered = { viewModel.processAction(PhoneChanged(it, checkedState.value)) },
)

Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) {
Checkbox(
modifier = Modifier
.padding(vertical = 4.dp)
.padding(end = 4.dp),
checked = checkedState.value,
onCheckedChange = { checkedState.value = it },
)
}

Text(text = stringResource(id = R.string.generate_client_side))
}

Divider(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 10.dp),
thickness = 2.dp,
color = Color.Black,
)

// Depending on the state of the View Model, we will switch in different content view.
when (val state = viewState) {
Expand Down
46 changes: 35 additions & 11 deletions dev-app/src/main/java/com/uid2/dev/ui/MainScreenViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.uid2.UID2Exception
import com.uid2.UID2Manager
import com.uid2.UID2ManagerState.Established
import com.uid2.UID2ManagerState.Expired
import com.uid2.UID2ManagerState.NoIdentity
import com.uid2.UID2ManagerState.OptOut
import com.uid2.UID2ManagerState.RefreshExpired
import com.uid2.UID2ManagerState.Refreshed
import com.uid2.data.IdentityRequest
import com.uid2.data.IdentityStatus
import com.uid2.data.IdentityStatus.ESTABLISHED
import com.uid2.data.IdentityStatus.EXPIRED
Expand All @@ -21,8 +23,8 @@ import com.uid2.data.IdentityStatus.REFRESHED
import com.uid2.data.IdentityStatus.REFRESH_EXPIRED
import com.uid2.data.UID2Identity
import com.uid2.dev.network.AppUID2Client
import com.uid2.dev.network.AppUID2ClientException
import com.uid2.dev.network.RequestType.EMAIL
import com.uid2.dev.network.RequestType.PHONE
import com.uid2.dev.ui.MainScreenState.ErrorState
import com.uid2.dev.ui.MainScreenState.LoadingState
import com.uid2.dev.ui.MainScreenState.UserUpdatedState
Expand All @@ -32,13 +34,14 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch

sealed interface MainScreenAction : ViewModelAction {
data class EmailChanged(val address: String) : MainScreenAction
object ResetButtonPressed : MainScreenAction
object RefreshButtonPressed : MainScreenAction
data class EmailChanged(val address: String, val clientSide: Boolean) : MainScreenAction
data class PhoneChanged(val number: String, val clientSide: Boolean) : MainScreenAction
data object ResetButtonPressed : MainScreenAction
data object RefreshButtonPressed : MainScreenAction
}

sealed interface MainScreenState : ViewState {
object LoadingState : MainScreenState
data object LoadingState : MainScreenState
data class UserUpdatedState(val identity: UID2Identity?, val status: IdentityStatus) : MainScreenState
data class ErrorState(val error: Throwable) : MainScreenState
}
Expand Down Expand Up @@ -77,18 +80,39 @@ class MainScreenViewModel(
viewModelScope.launch {
when (action) {
is MainScreenAction.EmailChanged -> {
_viewState.emit(LoadingState)

try {
// For Development purposes, we are required to generate the initial Identity before then
// passing it onto the SDK to be managed.
_viewState.emit(LoadingState)
api.generateIdentity(action.address, EMAIL)?.let {
manager.setIdentity(it)
if (action.clientSide) {
// Generate the identity via Client Side Integration (client side token generation).
manager.generateIdentity(IdentityRequest.Email(action.address))
} else {
// We're going to generate the identity as if we've obtained it via a backend service.
api.generateIdentity(action.address, EMAIL)?.let {
manager.setIdentity(it)
}
}
} catch (ex: AppUID2ClientException) {
} catch (ex: UID2Exception) {
_viewState.emit(ErrorState(ex))
}
}
is MainScreenAction.PhoneChanged -> {
_viewState.emit(LoadingState)

try {
if (action.clientSide) {
// Generate the identity via Client Side Integration (client side token generation).
manager.generateIdentity(IdentityRequest.Phone(action.number))
} else {
// We're going to generate the identity as if we've obtained it via a backend service.
api.generateIdentity(action.number, PHONE)?.let {
manager.setIdentity(it)
}
}
} catch (ex: UID2Exception) {
_viewState.emit(ErrorState(ex))
}
}
MainScreenAction.RefreshButtonPressed -> {
manager.currentIdentity?.let { _viewState.emit(LoadingState) }
manager.refreshIdentity()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ fun ActionButtonView(modifier: Modifier, onResetClick: () -> Unit, onRefreshClic
}

Button(onClick = onRefreshClick) {
Text("Manual Refresh")
Text("Refresh")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,47 +11,50 @@ import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowForward
import androidx.compose.material.icons.filled.Email
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
import com.uid2.devapp.R

@Composable
fun EmailInputView(modifier: Modifier, onEmailEntered: (String) -> Unit) {
fun IdentityInputView(
modifier: Modifier = Modifier,
label: String,
icon: ImageVector,
onEntered: (String) -> Unit,
) {
Row(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(10.dp),
) {
val emailAddress = remember { mutableStateOf(TextFieldValue()) }
val identityData = remember { mutableStateOf(TextFieldValue()) }

TextField(
value = emailAddress.value,
onValueChange = { emailAddress.value = it },
label = { Text(stringResource(R.string.email)) },
value = identityData.value,
onValueChange = { identityData.value = it },
label = { Text(label) },
singleLine = true,
modifier = Modifier.weight(1f),
leadingIcon = {
Icon(
imageVector = Icons.Default.Email,
contentDescription = stringResource(R.string.email_icon_content_description),
imageVector = icon,
contentDescription = null,
)
},
)

FloatingActionButton(
onClick = { onEmailEntered(emailAddress.value.text) },
onClick = { onEntered(identityData.value.text) },
shape = CircleShape,
backgroundColor = MaterialTheme.colors.primary,
) {
Icon(
imageVector = Icons.Default.ArrowForward,
contentDescription = stringResource(R.string.email_submit_content_description),
contentDescription = null,
tint = Color.White,
)
}
Expand Down
9 changes: 9 additions & 0 deletions dev-app/src/main/java/com/uid2/dev/utils/ByteArrayEx.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.uid2.dev.utils

import android.util.Base64

/**
* Extension method to encode a ByteArray to a String. This uses the android.util version of Base64 to keep our minimum
* SDK low.
*/
fun ByteArray.encodeBase64(): String = Base64.encodeToString(this, Base64.NO_WRAP)
4 changes: 2 additions & 2 deletions dev-app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
<string name="app_name">UID2 SDK Dev App</string>

<string name="email">Email</string>
<string name="email_icon_content_description">Email Icon</string>
<string name="email_submit_content_description">Submit Email</string>
<string name="phone">Phone Number</string>
<string name="generate_client_side">Client Side</string>

<string name="identity_advertising_token">Advertising Token</string>
<string name="identity_refresh_token">Refresh Token</string>
Expand Down
Loading

0 comments on commit 506071f

Please sign in to comment.