From 85b46868dc0e9f849c60de63eac97d42da87b9ee Mon Sep 17 00:00:00 2001 From: Amal Graafstra Date: Sat, 3 Jan 2026 15:18:24 -0800 Subject: [PATCH 1/3] Rename namespace from com.hoker.intra to com.vivokey.intra Breaking Changes: - Package namespace changed from com.hoker.intra to com.vivokey.intra - All imports must be updated in consuming applications - Library version bumped to 2.0.0 API Changes: - Replace deprecated /session endpoint with /authenticate - getVivokeyJwt() now requires devId parameter - Remove SessionRequest/SessionResponse (deprecated) - Add AuthenticateRequest/AuthenticateResponse for new JWE flow - NetworkModule now uses api.vivokey.com base URL - Remove hardcoded API key from NetworkModule (not needed for mobile) The new /authenticate flow returns encrypted JWEs that must be decrypted server-side using the paired API key. Mobile apps only need the developer ID, not the API key. Updated example app to use new namespace and devId parameter. --- app/build.gradle.kts | 4 ++ .../presentation/IntraExampleActivity.kt | 2 +- .../presentation/IntraExampleViewModel.kt | 11 +++-- intra/build.gradle.kts | 6 +-- .../com/hoker/intra/domain/AuthApiService.kt | 18 ------- .../intra/domain/request/SessionRequest.kt | 8 ---- .../intra/domain/response/SessionResponse.kt | 5 -- .../intra/data/IsodepControllerImpl.kt | 47 ++++++++++--------- .../intra/data/NfcAControllerImpl.kt | 13 ++--- .../intra/data/NfcVControllerImpl.kt | 45 +++++++++--------- .../intra/di/NetworkModule.kt | 19 ++------ .../{hoker => vivokey}/intra/di/NfcModule.kt | 16 +++---- .../intra/domain/ApduUtils.kt | 4 +- .../vivokey/intra/domain/AuthApiService.kt | 23 +++++++++ .../{hoker => vivokey}/intra/domain/Consts.kt | 4 +- .../intra/domain/NfcActivity.kt | 6 +-- .../intra/domain/NfcAdapterController.kt | 6 +-- .../intra/domain/NfcController.kt | 16 +++++-- .../intra/domain/NfcViewModel.kt | 4 +- .../intra/domain/OperationResult.kt | 4 +- .../{hoker => vivokey}/intra/domain/Timer.kt | 4 +- .../domain/request/AuthenticateRequest.kt | 8 ++++ .../intra/domain/request/ChallengeRequest.kt | 2 +- .../domain/response/AuthenticateResponse.kt | 10 ++++ .../domain/response/ChallengeResponse.kt | 4 +- 25 files changed, 156 insertions(+), 133 deletions(-) delete mode 100644 intra/src/main/java/com/hoker/intra/domain/AuthApiService.kt delete mode 100644 intra/src/main/java/com/hoker/intra/domain/request/SessionRequest.kt delete mode 100644 intra/src/main/java/com/hoker/intra/domain/response/SessionResponse.kt rename intra/src/main/java/com/{hoker => vivokey}/intra/data/IsodepControllerImpl.kt (92%) rename intra/src/main/java/com/{hoker => vivokey}/intra/data/NfcAControllerImpl.kt (97%) rename intra/src/main/java/com/{hoker => vivokey}/intra/data/NfcVControllerImpl.kt (89%) rename intra/src/main/java/com/{hoker => vivokey}/intra/di/NetworkModule.kt (68%) rename intra/src/main/java/com/{hoker => vivokey}/intra/di/NfcModule.kt (90%) rename intra/src/main/java/com/{hoker => vivokey}/intra/domain/ApduUtils.kt (97%) create mode 100644 intra/src/main/java/com/vivokey/intra/domain/AuthApiService.kt rename intra/src/main/java/com/{hoker => vivokey}/intra/domain/Consts.kt (98%) rename intra/src/main/java/com/{hoker => vivokey}/intra/domain/NfcActivity.kt (93%) rename intra/src/main/java/com/{hoker => vivokey}/intra/domain/NfcAdapterController.kt (97%) rename intra/src/main/java/com/{hoker => vivokey}/intra/domain/NfcController.kt (86%) rename intra/src/main/java/com/{hoker => vivokey}/intra/domain/NfcViewModel.kt (96%) rename intra/src/main/java/com/{hoker => vivokey}/intra/domain/OperationResult.kt (84%) rename intra/src/main/java/com/{hoker => vivokey}/intra/domain/Timer.kt (97%) create mode 100644 intra/src/main/java/com/vivokey/intra/domain/request/AuthenticateRequest.kt rename intra/src/main/java/com/{hoker => vivokey}/intra/domain/request/ChallengeRequest.kt (73%) create mode 100644 intra/src/main/java/com/vivokey/intra/domain/response/AuthenticateResponse.kt rename intra/src/main/java/com/{hoker => vivokey}/intra/domain/response/ChallengeResponse.kt (63%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ebc1ec5..984e195 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -21,6 +21,9 @@ android { vectorDrawables { useSupportLibrary = true } + + // Developer ID for VivoKey verify API (example/test ID) + buildConfigField("String", "VIVOKEY_DEV_ID", "\"YOUR_DEVELOPER_ID_HERE\"") } buildTypes { @@ -41,6 +44,7 @@ android { } buildFeatures { compose = true + buildConfig = true } composeOptions { kotlinCompilerExtensionVersion = "1.5.15" diff --git a/app/src/main/java/com/hoker/intra_example/presentation/IntraExampleActivity.kt b/app/src/main/java/com/hoker/intra_example/presentation/IntraExampleActivity.kt index f68cdda..718f909 100644 --- a/app/src/main/java/com/hoker/intra_example/presentation/IntraExampleActivity.kt +++ b/app/src/main/java/com/hoker/intra_example/presentation/IntraExampleActivity.kt @@ -15,7 +15,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel -import com.hoker.intra.domain.NfcActivity +import com.vivokey.intra.domain.NfcActivity import com.hoker.intra_example.presentation.components.OperationTypeBottomBar import com.hoker.supra.presentation.scaffolds.SupraGyroScaffold import com.hoker.supra.presentation.scaffolds.SupraScaffold diff --git a/app/src/main/java/com/hoker/intra_example/presentation/IntraExampleViewModel.kt b/app/src/main/java/com/hoker/intra_example/presentation/IntraExampleViewModel.kt index 0eb95e1..02a16de 100644 --- a/app/src/main/java/com/hoker/intra_example/presentation/IntraExampleViewModel.kt +++ b/app/src/main/java/com/hoker/intra_example/presentation/IntraExampleViewModel.kt @@ -4,10 +4,11 @@ import android.nfc.Tag import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.viewModelScope -import com.hoker.intra.domain.NfcAdapterController -import com.hoker.intra.domain.NfcController -import com.hoker.intra.domain.NfcViewModel -import com.hoker.intra.domain.OperationResult +import com.vivokey.intra.domain.NfcAdapterController +import com.vivokey.intra.domain.NfcController +import com.vivokey.intra.domain.NfcViewModel +import com.vivokey.intra.domain.OperationResult +import com.hoker.intra_example.BuildConfig import com.hoker.intra_example.domain.models.OperationType import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers @@ -45,7 +46,7 @@ class IntraExampleViewModel @Inject constructor( nfcController.withConnection(tag) { when (_selectedCommand.value) { OperationType.JWT -> { - when (val result = nfcController.getVivokeyJwt(tag)) { + when (val result = nfcController.getVivokeyJwt(tag, BuildConfig.VIVOKEY_DEV_ID)) { is OperationResult.Success -> { _output.value = result.data } diff --git a/intra/build.gradle.kts b/intra/build.gradle.kts index 9f0cafb..7c61a66 100644 --- a/intra/build.gradle.kts +++ b/intra/build.gradle.kts @@ -7,7 +7,7 @@ plugins { } android { - namespace = "com.carbidecowboy.intra" + namespace = "com.vivokey.intra" compileSdk = 36 defaultConfig { @@ -59,9 +59,9 @@ afterEvaluate { publications { create("release") { from(components["release"]) - groupId = "com.github.h0ker" + groupId = "com.github.vivokey" artifactId = "Intra" - version = "1.3.9" + version = "2.0.0" } } } diff --git a/intra/src/main/java/com/hoker/intra/domain/AuthApiService.kt b/intra/src/main/java/com/hoker/intra/domain/AuthApiService.kt deleted file mode 100644 index e13e722..0000000 --- a/intra/src/main/java/com/hoker/intra/domain/AuthApiService.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.hoker.intra.domain - -import com.hoker.intra.domain.request.ChallengeRequest -import com.hoker.intra.domain.request.SessionRequest -import com.hoker.intra.domain.response.ChallengeResponse -import com.hoker.intra.domain.response.SessionResponse -import retrofit2.Response -import retrofit2.http.Body -import retrofit2.http.POST - -interface AuthApiService { - - @POST("challenge") - suspend fun postChallenge(@Body challengeRequest: ChallengeRequest): Response - - @POST("session") - suspend fun postSession(@Body sessionRequest: SessionRequest): Response -} \ No newline at end of file diff --git a/intra/src/main/java/com/hoker/intra/domain/request/SessionRequest.kt b/intra/src/main/java/com/hoker/intra/domain/request/SessionRequest.kt deleted file mode 100644 index dd2b023..0000000 --- a/intra/src/main/java/com/hoker/intra/domain/request/SessionRequest.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.hoker.intra.domain.request - -data class SessionRequest( - val uid: String, - val response: String, - val token: String, - val cld: String? = null -) diff --git a/intra/src/main/java/com/hoker/intra/domain/response/SessionResponse.kt b/intra/src/main/java/com/hoker/intra/domain/response/SessionResponse.kt deleted file mode 100644 index 3d68561..0000000 --- a/intra/src/main/java/com/hoker/intra/domain/response/SessionResponse.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.hoker.intra.domain.response - -data class SessionResponse ( - val token: String -) \ No newline at end of file diff --git a/intra/src/main/java/com/hoker/intra/data/IsodepControllerImpl.kt b/intra/src/main/java/com/vivokey/intra/data/IsodepControllerImpl.kt similarity index 92% rename from intra/src/main/java/com/hoker/intra/data/IsodepControllerImpl.kt rename to intra/src/main/java/com/vivokey/intra/data/IsodepControllerImpl.kt index ddbe148..9563dbf 100644 --- a/intra/src/main/java/com/hoker/intra/data/IsodepControllerImpl.kt +++ b/intra/src/main/java/com/vivokey/intra/data/IsodepControllerImpl.kt @@ -1,19 +1,19 @@ -package com.hoker.intra.data +package com.vivokey.intra.data import android.nfc.NdefMessage import android.nfc.Tag import android.nfc.tech.IsoDep import android.nfc.tech.Ndef import android.util.Log -import com.hoker.intra.di.IntraAuthApiService -import com.hoker.intra.domain.ApduUtils -import com.hoker.intra.domain.AuthApiService -import com.hoker.intra.domain.Consts -import com.hoker.intra.domain.NfcController -import com.hoker.intra.domain.OperationResult -import com.hoker.intra.domain.Timer -import com.hoker.intra.domain.request.ChallengeRequest -import com.hoker.intra.domain.request.SessionRequest +import com.vivokey.intra.di.IntraAuthApiService +import com.vivokey.intra.domain.ApduUtils +import com.vivokey.intra.domain.AuthApiService +import com.vivokey.intra.domain.Consts +import com.vivokey.intra.domain.NfcController +import com.vivokey.intra.domain.OperationResult +import com.vivokey.intra.domain.Timer +import com.vivokey.intra.domain.request.AuthenticateRequest +import com.vivokey.intra.domain.request.ChallengeRequest import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.async @@ -138,6 +138,7 @@ class IsodepControllerImpl @Inject constructor( override suspend fun getVivokeyJwt( tag: Tag, + devId: String, cld: String? ): OperationResult { return try { @@ -239,25 +240,27 @@ class IsodepControllerImpl @Inject constructor( } responseString?.let { - val sessionRequest = SessionRequest( - uid = uid, - response = responseString, + // Use /authenticate endpoint with developer ID + // Returns encrypted JWE that must be sent to server for decryption + val authenticateRequest = AuthenticateRequest( token = challengeResponse.token, - cld = cld + response = responseString, + uid = uid, + dev_id = devId ) - val sessionResponse = async { - authApiService.postSession(sessionRequest) + val authenticateResponse = async { + authApiService.postAuthenticate(authenticateRequest) }.await() - if (!sessionResponse.isSuccessful) { + if (!authenticateResponse.isSuccessful) { OperationResult.Failure() } - val result = sessionResponse.body() - result?.token?.let { jwt -> - Log.i("JWT:", jwt) - return@withContext OperationResult.Success(jwt) + val result = authenticateResponse.body() + result?.token?.let { jwe -> + Log.i("JWE:", jwe) // This is encrypted - cannot be decoded by mobile app + return@withContext OperationResult.Success(jwe) } } @@ -385,4 +388,4 @@ class IsodepControllerImpl @Inject constructor( private fun stopConnectionCheckJob() { timerJob?.cancel() } -} \ No newline at end of file +} diff --git a/intra/src/main/java/com/hoker/intra/data/NfcAControllerImpl.kt b/intra/src/main/java/com/vivokey/intra/data/NfcAControllerImpl.kt similarity index 97% rename from intra/src/main/java/com/hoker/intra/data/NfcAControllerImpl.kt rename to intra/src/main/java/com/vivokey/intra/data/NfcAControllerImpl.kt index 8375192..39d5a20 100644 --- a/intra/src/main/java/com/hoker/intra/data/NfcAControllerImpl.kt +++ b/intra/src/main/java/com/vivokey/intra/data/NfcAControllerImpl.kt @@ -1,14 +1,14 @@ -package com.hoker.intra.data +package com.vivokey.intra.data import android.nfc.NdefMessage import android.nfc.Tag import android.nfc.tech.Ndef import android.nfc.tech.NfcA import android.util.Log -import com.hoker.intra.domain.ApduUtils -import com.hoker.intra.domain.NfcController -import com.hoker.intra.domain.OperationResult -import com.hoker.intra.domain.Timer +import com.vivokey.intra.domain.ApduUtils +import com.vivokey.intra.domain.NfcController +import com.vivokey.intra.domain.OperationResult +import com.vivokey.intra.domain.Timer import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -219,6 +219,7 @@ class NfcAControllerImpl @Inject constructor( override suspend fun getVivokeyJwt( tag: Tag, + devId: String, cld: String? ): OperationResult { return OperationResult.Failure(Exception("This operation is not currently supported with NfcA")) @@ -261,4 +262,4 @@ class NfcAControllerImpl @Inject constructor( OperationResult.Failure(e) } } -} \ No newline at end of file +} diff --git a/intra/src/main/java/com/hoker/intra/data/NfcVControllerImpl.kt b/intra/src/main/java/com/vivokey/intra/data/NfcVControllerImpl.kt similarity index 89% rename from intra/src/main/java/com/hoker/intra/data/NfcVControllerImpl.kt rename to intra/src/main/java/com/vivokey/intra/data/NfcVControllerImpl.kt index 34f8e4a..43c6ccb 100644 --- a/intra/src/main/java/com/hoker/intra/data/NfcVControllerImpl.kt +++ b/intra/src/main/java/com/vivokey/intra/data/NfcVControllerImpl.kt @@ -1,19 +1,19 @@ -package com.hoker.intra.data +package com.vivokey.intra.data import android.nfc.NdefMessage import android.nfc.Tag import android.nfc.tech.Ndef import android.nfc.tech.NfcV import android.util.Log -import com.hoker.intra.di.IntraAuthApiService -import com.hoker.intra.domain.ApduUtils -import com.hoker.intra.domain.AuthApiService -import com.hoker.intra.domain.Consts.FUNCTION_NOT_SUPPORTED -import com.hoker.intra.domain.NfcController -import com.hoker.intra.domain.OperationResult -import com.hoker.intra.domain.Timer -import com.hoker.intra.domain.request.ChallengeRequest -import com.hoker.intra.domain.request.SessionRequest +import com.vivokey.intra.di.IntraAuthApiService +import com.vivokey.intra.domain.ApduUtils +import com.vivokey.intra.domain.AuthApiService +import com.vivokey.intra.domain.Consts.FUNCTION_NOT_SUPPORTED +import com.vivokey.intra.domain.NfcController +import com.vivokey.intra.domain.OperationResult +import com.vivokey.intra.domain.Timer +import com.vivokey.intra.domain.request.AuthenticateRequest +import com.vivokey.intra.domain.request.ChallengeRequest import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.async @@ -186,13 +186,14 @@ class NfcVControllerImpl @Inject constructor( override suspend fun getVivokeyJwt( tag: Tag, + devId: String, cld: String? ): OperationResult { return try { withContext(Dispatchers.IO) { - //get challenge from + // Get challenge from API (scheme 1 for NfcV/Spark 1) val challengeRequest = ChallengeRequest(1) val challengeResponse = async { authApiService.postChallenge(challengeRequest).body() @@ -226,25 +227,27 @@ class NfcVControllerImpl @Inject constructor( val response = nfcV?.transceive(command) Log.i("Response", Hex.encodeHexString(response)) - val sessionRequest = SessionRequest( + // Use /authenticate endpoint with developer ID + // Returns encrypted JWE that must be sent to server for decryption + val authenticateRequest = AuthenticateRequest( uid = Hex.encodeHexString(tag.id!!.reversedArray()), response = Hex.encodeHexString(response), token = challengeResponse.token, - cld = cld + dev_id = devId ) - val sessionResponse = async { - authApiService.postSession(sessionRequest) + val authenticateResponse = async { + authApiService.postAuthenticate(authenticateRequest) }.await() - if (!sessionResponse.isSuccessful) { + if (!authenticateResponse.isSuccessful) { OperationResult.Failure() } - val result = sessionResponse.body() - result?.token?.let { jwt -> - println(jwt) - return@withContext OperationResult.Success(jwt) + val result = authenticateResponse.body() + result?.token?.let { jwe -> + println(jwe) + return@withContext OperationResult.Success(jwe) } OperationResult.Failure() @@ -296,4 +299,4 @@ class NfcVControllerImpl @Inject constructor( OperationResult.Failure(e) } } -} \ No newline at end of file +} diff --git a/intra/src/main/java/com/hoker/intra/di/NetworkModule.kt b/intra/src/main/java/com/vivokey/intra/di/NetworkModule.kt similarity index 68% rename from intra/src/main/java/com/hoker/intra/di/NetworkModule.kt rename to intra/src/main/java/com/vivokey/intra/di/NetworkModule.kt index d8c1e5a..20c7180 100644 --- a/intra/src/main/java/com/hoker/intra/di/NetworkModule.kt +++ b/intra/src/main/java/com/vivokey/intra/di/NetworkModule.kt @@ -1,6 +1,6 @@ -package com.hoker.intra.di +package com.vivokey.intra.di -import com.hoker.intra.domain.AuthApiService +import com.vivokey.intra.domain.AuthApiService import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -27,22 +27,13 @@ annotation class IntraAuthApiService @InstallIn(SingletonComponent::class) object NetworkModule { - private const val AUTH_BASE_URL = "https://auth.vivokey.com/" - private const val API_HEADER = "X-API-VIVOKEY" - private const val API_KEY = "9e084e64-eb74-41b8-a87d-4c0bdcd1be64" + private const val API_BASE_URL = "https://api.vivokey.com/" @Provides @Singleton @IntraOkHttp fun provideOkHttpClient(): OkHttpClient { return OkHttpClient.Builder() - .addInterceptor { chain -> - val originalRequest = chain.request() - val newRequest = originalRequest.newBuilder() - .header(API_HEADER, API_KEY) - .build() - chain.proceed(newRequest) - } .build() } @@ -51,7 +42,7 @@ object NetworkModule { @IntraAuthRetrofit fun provideAuthRetrofit(@IntraOkHttp okHttpClient: OkHttpClient): Retrofit { return Retrofit.Builder() - .baseUrl(AUTH_BASE_URL) + .baseUrl(API_BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .client(okHttpClient) .build() @@ -63,4 +54,4 @@ object NetworkModule { fun provideAuthApiService(@IntraAuthRetrofit retrofit: Retrofit): AuthApiService { return retrofit.create(AuthApiService::class.java) } -} \ No newline at end of file +} diff --git a/intra/src/main/java/com/hoker/intra/di/NfcModule.kt b/intra/src/main/java/com/vivokey/intra/di/NfcModule.kt similarity index 90% rename from intra/src/main/java/com/hoker/intra/di/NfcModule.kt rename to intra/src/main/java/com/vivokey/intra/di/NfcModule.kt index 57eb95b..4b77b18 100644 --- a/intra/src/main/java/com/hoker/intra/di/NfcModule.kt +++ b/intra/src/main/java/com/vivokey/intra/di/NfcModule.kt @@ -1,4 +1,4 @@ -package com.hoker.intra.di +package com.vivokey.intra.di import android.content.Context import android.nfc.NfcAdapter @@ -6,12 +6,12 @@ import android.nfc.Tag import android.nfc.tech.IsoDep import android.nfc.tech.NfcA import android.nfc.tech.NfcV -import com.hoker.intra.data.IsodepControllerImpl -import com.hoker.intra.data.NfcAControllerImpl -import com.hoker.intra.data.NfcVControllerImpl -import com.hoker.intra.domain.NfcAdapterController -import com.hoker.intra.domain.NfcController -import com.hoker.intra.domain.OperationResult +import com.vivokey.intra.data.IsodepControllerImpl +import com.vivokey.intra.data.NfcAControllerImpl +import com.vivokey.intra.data.NfcVControllerImpl +import com.vivokey.intra.domain.NfcAdapterController +import com.vivokey.intra.domain.NfcController +import com.vivokey.intra.domain.OperationResult import dagger.Binds import dagger.Module import dagger.Provides @@ -102,4 +102,4 @@ abstract class NfcModule { } } } -} \ No newline at end of file +} diff --git a/intra/src/main/java/com/hoker/intra/domain/ApduUtils.kt b/intra/src/main/java/com/vivokey/intra/domain/ApduUtils.kt similarity index 97% rename from intra/src/main/java/com/hoker/intra/domain/ApduUtils.kt rename to intra/src/main/java/com/vivokey/intra/domain/ApduUtils.kt index 23f9767..76f30a0 100644 --- a/intra/src/main/java/com/hoker/intra/domain/ApduUtils.kt +++ b/intra/src/main/java/com/vivokey/intra/domain/ApduUtils.kt @@ -1,4 +1,4 @@ -package com.hoker.intra.domain +package com.vivokey.intra.domain import java.io.IOException import java.nio.ByteBuffer @@ -43,4 +43,4 @@ class ApduUtils { data class ApduResponse(val data: ByteArray, val statusCode: Int) } -} \ No newline at end of file +} diff --git a/intra/src/main/java/com/vivokey/intra/domain/AuthApiService.kt b/intra/src/main/java/com/vivokey/intra/domain/AuthApiService.kt new file mode 100644 index 0000000..e8a6947 --- /dev/null +++ b/intra/src/main/java/com/vivokey/intra/domain/AuthApiService.kt @@ -0,0 +1,23 @@ +package com.vivokey.intra.domain + +import com.vivokey.intra.domain.request.AuthenticateRequest +import com.vivokey.intra.domain.request.ChallengeRequest +import com.vivokey.intra.domain.response.AuthenticateResponse +import com.vivokey.intra.domain.response.ChallengeResponse +import retrofit2.Response +import retrofit2.http.Body +import retrofit2.http.POST + +interface AuthApiService { + + @POST("challenge") + suspend fun postChallenge(@Body challengeRequest: ChallengeRequest): Response + + /** + * Authenticate with the VivoKey verify API using the encrypted JWE flow. + * Returns an encrypted JWE token that must be decrypted by your server. + * The mobile app passes the developer ID (dev_id) but never has the API key. + */ + @POST("authenticate") + suspend fun postAuthenticate(@Body authenticateRequest: AuthenticateRequest): Response +} diff --git a/intra/src/main/java/com/hoker/intra/domain/Consts.kt b/intra/src/main/java/com/vivokey/intra/domain/Consts.kt similarity index 98% rename from intra/src/main/java/com/hoker/intra/domain/Consts.kt rename to intra/src/main/java/com/vivokey/intra/domain/Consts.kt index 1beb112..7e8332c 100644 --- a/intra/src/main/java/com/hoker/intra/domain/Consts.kt +++ b/intra/src/main/java/com/vivokey/intra/domain/Consts.kt @@ -1,4 +1,4 @@ -package com.hoker.intra.domain +package com.vivokey.intra.domain object Consts { const val SUCCESS_RESPONSE = "9000" @@ -45,4 +45,4 @@ object Consts { const val GET_UID_APDU = "FFCA000000" const val GET_ATS_APDU = "FFCA010000" const val TEST_NFC_A_ATR = "3B8F8001804F0CA0000003060300030000000068" -} \ No newline at end of file +} diff --git a/intra/src/main/java/com/hoker/intra/domain/NfcActivity.kt b/intra/src/main/java/com/vivokey/intra/domain/NfcActivity.kt similarity index 93% rename from intra/src/main/java/com/hoker/intra/domain/NfcActivity.kt rename to intra/src/main/java/com/vivokey/intra/domain/NfcActivity.kt index a8765d2..29993c7 100644 --- a/intra/src/main/java/com/hoker/intra/domain/NfcActivity.kt +++ b/intra/src/main/java/com/vivokey/intra/domain/NfcActivity.kt @@ -1,10 +1,10 @@ -package com.hoker.intra.domain +package com.vivokey.intra.domain import android.os.Bundle import android.os.PersistableBundle import android.widget.Toast import androidx.activity.ComponentActivity -import com.hoker.intra.di.NfcModule +import com.vivokey.intra.di.NfcModule import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.delay import javax.inject.Inject @@ -37,4 +37,4 @@ abstract class NfcActivity : ComponentActivity() { delay(500) nfcAdapterController.enableNfc(this) } -} \ No newline at end of file +} diff --git a/intra/src/main/java/com/hoker/intra/domain/NfcAdapterController.kt b/intra/src/main/java/com/vivokey/intra/domain/NfcAdapterController.kt similarity index 97% rename from intra/src/main/java/com/hoker/intra/domain/NfcAdapterController.kt rename to intra/src/main/java/com/vivokey/intra/domain/NfcAdapterController.kt index 4e75026..ed92159 100644 --- a/intra/src/main/java/com/hoker/intra/domain/NfcAdapterController.kt +++ b/intra/src/main/java/com/vivokey/intra/domain/NfcAdapterController.kt @@ -1,11 +1,11 @@ -package com.hoker.intra.domain +package com.vivokey.intra.domain import android.app.Activity import android.nfc.NfcAdapter import android.nfc.Tag import android.os.Bundle import android.util.Log -import com.hoker.intra.di.NfcModule +import com.vivokey.intra.di.NfcModule import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.receiveAsFlow import javax.inject.Inject @@ -90,4 +90,4 @@ open class NfcAdapterController @Inject constructor( } Log.i(this@NfcAdapterController::class.simpleName, log) } -} \ No newline at end of file +} diff --git a/intra/src/main/java/com/hoker/intra/domain/NfcController.kt b/intra/src/main/java/com/vivokey/intra/domain/NfcController.kt similarity index 86% rename from intra/src/main/java/com/hoker/intra/domain/NfcController.kt rename to intra/src/main/java/com/vivokey/intra/domain/NfcController.kt index b224b3e..63a7e33 100644 --- a/intra/src/main/java/com/hoker/intra/domain/NfcController.kt +++ b/intra/src/main/java/com/vivokey/intra/domain/NfcController.kt @@ -1,4 +1,4 @@ -package com.hoker.intra.domain +package com.vivokey.intra.domain import android.nfc.NdefMessage import android.nfc.Tag @@ -16,9 +16,19 @@ interface NfcController { suspend fun issueApdu(instruction: Byte, p1: Byte = 0, p2: Byte = 0, data: ByteBuffer.() -> Unit = {}): OperationResult suspend fun transceive(data: ByteArray): OperationResult suspend fun writeNdefMessage(tag: Tag, message: NdefMessage): OperationResult + /** + * Authenticates with VivoKey verify API and returns an encrypted JWE. + * The JWE must be sent to your server for decryption - mobile app cannot decrypt it. + * + * @param tag The NFC tag to authenticate + * @param devId The developer ID for the VivoKey verify API + * @param cld Optional custom client data to include in the JWT (max 2048 chars) + * @return Encrypted JWE token + */ suspend fun getVivokeyJwt( tag: Tag, - cid: String? = null + devId: String, + cld: String? = null ): OperationResult suspend fun getNdefCapacity(ndef: Ndef): OperationResult suspend fun getNdefMessage(ndef: Ndef): OperationResult @@ -82,4 +92,4 @@ interface NfcController { OperationResult.Failure(e) } } -} \ No newline at end of file +} diff --git a/intra/src/main/java/com/hoker/intra/domain/NfcViewModel.kt b/intra/src/main/java/com/vivokey/intra/domain/NfcViewModel.kt similarity index 96% rename from intra/src/main/java/com/hoker/intra/domain/NfcViewModel.kt rename to intra/src/main/java/com/vivokey/intra/domain/NfcViewModel.kt index 75fce08..65ca0e1 100644 --- a/intra/src/main/java/com/hoker/intra/domain/NfcViewModel.kt +++ b/intra/src/main/java/com/vivokey/intra/domain/NfcViewModel.kt @@ -1,4 +1,4 @@ -package com.hoker.intra.domain +package com.vivokey.intra.domain import android.nfc.Tag import androidx.lifecycle.ViewModel @@ -30,4 +30,4 @@ abstract class NfcViewModel( super.onCleared() nfcAdapterController.removeOnTagDiscoveredListener(uuid) } -} \ No newline at end of file +} diff --git a/intra/src/main/java/com/hoker/intra/domain/OperationResult.kt b/intra/src/main/java/com/vivokey/intra/domain/OperationResult.kt similarity index 84% rename from intra/src/main/java/com/hoker/intra/domain/OperationResult.kt rename to intra/src/main/java/com/vivokey/intra/domain/OperationResult.kt index f2aab70..568473c 100644 --- a/intra/src/main/java/com/hoker/intra/domain/OperationResult.kt +++ b/intra/src/main/java/com/vivokey/intra/domain/OperationResult.kt @@ -1,6 +1,6 @@ -package com.hoker.intra.domain +package com.vivokey.intra.domain sealed class OperationResult { data class Success(val data: T) : OperationResult() data class Failure(val exception: Throwable? = null) : OperationResult() -} \ No newline at end of file +} diff --git a/intra/src/main/java/com/hoker/intra/domain/Timer.kt b/intra/src/main/java/com/vivokey/intra/domain/Timer.kt similarity index 97% rename from intra/src/main/java/com/hoker/intra/domain/Timer.kt rename to intra/src/main/java/com/vivokey/intra/domain/Timer.kt index 60c3919..83ec608 100644 --- a/intra/src/main/java/com/hoker/intra/domain/Timer.kt +++ b/intra/src/main/java/com/vivokey/intra/domain/Timer.kt @@ -1,4 +1,4 @@ -package com.hoker.intra.domain +package com.vivokey.intra.domain import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -41,4 +41,4 @@ class Timer @Inject constructor() { } } } -} \ No newline at end of file +} diff --git a/intra/src/main/java/com/vivokey/intra/domain/request/AuthenticateRequest.kt b/intra/src/main/java/com/vivokey/intra/domain/request/AuthenticateRequest.kt new file mode 100644 index 0000000..33f4ad1 --- /dev/null +++ b/intra/src/main/java/com/vivokey/intra/domain/request/AuthenticateRequest.kt @@ -0,0 +1,8 @@ +package com.vivokey.intra.domain.request + +data class AuthenticateRequest( + val token: String, + val response: String, + val uid: String, + val dev_id: String +) diff --git a/intra/src/main/java/com/hoker/intra/domain/request/ChallengeRequest.kt b/intra/src/main/java/com/vivokey/intra/domain/request/ChallengeRequest.kt similarity index 73% rename from intra/src/main/java/com/hoker/intra/domain/request/ChallengeRequest.kt rename to intra/src/main/java/com/vivokey/intra/domain/request/ChallengeRequest.kt index 5f9ef5a..c13b3cc 100644 --- a/intra/src/main/java/com/hoker/intra/domain/request/ChallengeRequest.kt +++ b/intra/src/main/java/com/vivokey/intra/domain/request/ChallengeRequest.kt @@ -1,4 +1,4 @@ -package com.hoker.intra.domain.request +package com.vivokey.intra.domain.request data class ChallengeRequest( val scheme: Int, diff --git a/intra/src/main/java/com/vivokey/intra/domain/response/AuthenticateResponse.kt b/intra/src/main/java/com/vivokey/intra/domain/response/AuthenticateResponse.kt new file mode 100644 index 0000000..e07b22e --- /dev/null +++ b/intra/src/main/java/com/vivokey/intra/domain/response/AuthenticateResponse.kt @@ -0,0 +1,10 @@ +package com.vivokey.intra.domain.response + +/** + * Response from the /authenticate endpoint. + * Contains an encrypted JWE token that must be sent to your server for decryption. + * The mobile app cannot decrypt this token - it's opaque to the client. + */ +data class AuthenticateResponse( + val token: String // Encrypted JWE +) diff --git a/intra/src/main/java/com/hoker/intra/domain/response/ChallengeResponse.kt b/intra/src/main/java/com/vivokey/intra/domain/response/ChallengeResponse.kt similarity index 63% rename from intra/src/main/java/com/hoker/intra/domain/response/ChallengeResponse.kt rename to intra/src/main/java/com/vivokey/intra/domain/response/ChallengeResponse.kt index 2ad20f9..18e9678 100644 --- a/intra/src/main/java/com/hoker/intra/domain/response/ChallengeResponse.kt +++ b/intra/src/main/java/com/vivokey/intra/domain/response/ChallengeResponse.kt @@ -1,6 +1,6 @@ -package com.hoker.intra.domain.response +package com.vivokey.intra.domain.response data class ChallengeResponse( val payload: String, val token: String -) \ No newline at end of file +) From 1504024d6c53c65e80041fcb9bfb2e3484835e23 Mon Sep 17 00:00:00 2001 From: Amal Graafstra Date: Sat, 3 Jan 2026 15:28:33 -0800 Subject: [PATCH 2/3] Fix API base URL to auth.vivokey.com The api.vivokey.com endpoint is legacy/decommissioned. The correct base URL is https://auth.vivokey.com/ --- intra/src/main/java/com/vivokey/intra/di/NetworkModule.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intra/src/main/java/com/vivokey/intra/di/NetworkModule.kt b/intra/src/main/java/com/vivokey/intra/di/NetworkModule.kt index 20c7180..b40e9d3 100644 --- a/intra/src/main/java/com/vivokey/intra/di/NetworkModule.kt +++ b/intra/src/main/java/com/vivokey/intra/di/NetworkModule.kt @@ -27,7 +27,7 @@ annotation class IntraAuthApiService @InstallIn(SingletonComponent::class) object NetworkModule { - private const val API_BASE_URL = "https://api.vivokey.com/" + private const val API_BASE_URL = "https://auth.vivokey.com/" @Provides @Singleton From b3e65bae0c78fe805db87150ae982a0dba18d8ee Mon Sep 17 00:00:00 2001 From: Amal Graafstra Date: Thu, 8 Jan 2026 10:52:45 -0800 Subject: [PATCH 3/3] Fix connect() return statements and null safety in NFC controllers - Add missing return statement in connect() for IsodepControllerImpl, NfcAControllerImpl, and NfcVControllerImpl. Without the return, the let block result was discarded and Failure was always returned. - Add null safety checks for response and tag.id in NfcVControllerImpl getVivokeyJwt() to prevent null pointer exceptions during NFC scans. --- .../java/com/vivokey/intra/data/IsodepControllerImpl.kt | 2 +- .../java/com/vivokey/intra/data/NfcAControllerImpl.kt | 2 +- .../java/com/vivokey/intra/data/NfcVControllerImpl.kt | 8 ++++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/intra/src/main/java/com/vivokey/intra/data/IsodepControllerImpl.kt b/intra/src/main/java/com/vivokey/intra/data/IsodepControllerImpl.kt index 9563dbf..8aef4fa 100644 --- a/intra/src/main/java/com/vivokey/intra/data/IsodepControllerImpl.kt +++ b/intra/src/main/java/com/vivokey/intra/data/IsodepControllerImpl.kt @@ -66,7 +66,7 @@ class IsodepControllerImpl @Inject constructor( it.timeout = 20000 _connectionStatus.emit(true) startConnectionCheckJob() - OperationResult.Success(Unit) + return OperationResult.Success(Unit) } OperationResult.Failure(Exception("IsoDep.connect() came back as null")) } catch (e: Exception) { diff --git a/intra/src/main/java/com/vivokey/intra/data/NfcAControllerImpl.kt b/intra/src/main/java/com/vivokey/intra/data/NfcAControllerImpl.kt index 39d5a20..51f0350 100644 --- a/intra/src/main/java/com/vivokey/intra/data/NfcAControllerImpl.kt +++ b/intra/src/main/java/com/vivokey/intra/data/NfcAControllerImpl.kt @@ -38,7 +38,7 @@ class NfcAControllerImpl @Inject constructor( Log.i("ApexConnection", "----NFC_A CONNECTED") startConnectionCheckJob() _connectionStatus.emit(true) - OperationResult.Success(Unit) + return OperationResult.Success(Unit) } OperationResult.Failure(Exception("NfcA.connect() came back as null")) } catch (e: Exception) { diff --git a/intra/src/main/java/com/vivokey/intra/data/NfcVControllerImpl.kt b/intra/src/main/java/com/vivokey/intra/data/NfcVControllerImpl.kt index 43c6ccb..f453a27 100644 --- a/intra/src/main/java/com/vivokey/intra/data/NfcVControllerImpl.kt +++ b/intra/src/main/java/com/vivokey/intra/data/NfcVControllerImpl.kt @@ -71,7 +71,7 @@ class NfcVControllerImpl @Inject constructor( it.connect() startConnectionCheckJob() _connectionStatus.emit(true) - OperationResult.Success(Unit) + return OperationResult.Success(Unit) } OperationResult.Failure(Exception("NfcV.connect() came back as null")) } catch (e: Exception) { @@ -225,12 +225,16 @@ class NfcVControllerImpl @Inject constructor( // connect and send command Log.i("Command", Hex.encodeHexString(command)) val response = nfcV?.transceive(command) + ?: return@withContext OperationResult.Failure(Exception("NFC connection lost - please scan again")) Log.i("Response", Hex.encodeHexString(response)) + val tagId = tag.id + ?: return@withContext OperationResult.Failure(Exception("Unable to read tag ID")) + // Use /authenticate endpoint with developer ID // Returns encrypted JWE that must be sent to server for decryption val authenticateRequest = AuthenticateRequest( - uid = Hex.encodeHexString(tag.id!!.reversedArray()), + uid = Hex.encodeHexString(tagId.reversedArray()), response = Hex.encodeHexString(response), token = challengeResponse.token, dev_id = devId