diff --git a/android/build.gradle b/android/build.gradle index cf3b78a..46fbd39 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -77,5 +77,5 @@ dependencies { implementation "androidx.browser:browser:1.2.0" - implementation("com.authsignal:authsignal-android:2.6.0") + implementation("com.authsignal:authsignal-android:3.0.0-alpha") } diff --git a/android/src/main/java/com/authsignal/react/AuthsignalInAppModule.kt b/android/src/main/java/com/authsignal/react/AuthsignalInAppModule.kt new file mode 100644 index 0000000..030e05e --- /dev/null +++ b/android/src/main/java/com/authsignal/react/AuthsignalInAppModule.kt @@ -0,0 +1,135 @@ +package com.authsignal.react + +import android.util.Log +import com.authsignal.inapp.AuthsignalInApp +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch + +class AuthsignalInAppModule(private val reactContext: ReactApplicationContext) : + ReactContextBaseJavaModule( + reactContext + ) { + private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) + private var authsignal: AuthsignalInApp? = null + private var defaultError = "unexpected_error" + + override fun getConstants(): Map? { + val constants: MutableMap = HashMap() + constants["bundleIdentifier"] = reactContext.applicationInfo.packageName + return constants + } + + override fun getName(): String { + return "AuthsignalInAppModule" + } + + @ReactMethod + fun initialize(tenantID: String?, baseURL: String?, promise: Promise) { + Log.d("AuthsignalInAppModule", "initialize: $tenantID, $baseURL") + authsignal = AuthsignalInApp(tenantID!!, baseURL!!) + + promise.resolve(null) + } + + @ReactMethod + fun getCredential(promise: Promise) { + launch(promise) { + val response = it.getCredential() + + if (response.error != null) { + val errorCode = response.errorCode ?: defaultError + + promise.reject(errorCode, response.error) + } else if (response.data != null) { + val credential = response.data + val map = Arguments.createMap() + map.putString("credentialId", credential!!.credentialId) + map.putString("createdAt", credential.createdAt) + map.putString("userId", credential.userId) + map.putString("lastAuthenticatedAt", credential.lastAuthenticatedAt) + promise.resolve(map) + } else { + promise.resolve(null) + } + } + } + + @ReactMethod + fun addCredential( + token: String?, + promise: Promise + ) { + launch(promise) { + val response = it.addCredential(token, null) + + if (response.error != null) { + val errorCode = response.errorCode ?: defaultError + + promise.reject(errorCode, response.error) + } else { + val credential = response.data + val map = Arguments.createMap() + map.putString("credentialId", credential!!.credentialId) + map.putString("createdAt", credential.createdAt) + map.putString("userId", credential.userId) + map.putString("lastAuthenticatedAt", credential.lastAuthenticatedAt) + promise.resolve(map) + } + } + } + + @ReactMethod + fun removeCredential(promise: Promise) { + launch(promise) { + val response = it.removeCredential() + + if (response.error != null) { + val errorCode = response.errorCode ?: defaultError + + promise.reject(errorCode, response.error) + } else { + promise.resolve(response.data) + } + } + } + + @ReactMethod + fun verify(promise: Promise) { + launch(promise) { + val response = it.verify() + + if (response.error != null) { + val errorCode = response.errorCode ?: defaultError + + promise.reject(errorCode, response.error) + } else { + val data = response.data!! + val map = Arguments.createMap() + map.putString("token", data.token) + map.putString("userId", data.userId) + map.putString("userAuthenticatorId", data.userAuthenticatorId) + map.putString("username", data.username) + promise.resolve(map) + } + } + } + + private fun launch(promise: Promise, fn: suspend (client: AuthsignalDevice) -> Unit) { + coroutineScope.launch { + authsignal?.let { + fn(it) + } ?: run { + Log.w("init_error", "AuthsignalInAppModule is not initialized.") + + promise.resolve(null) + } + } + } +} diff --git a/android/src/main/java/com/authsignal/react/AuthsignalPackage.kt b/android/src/main/java/com/authsignal/react/AuthsignalPackage.kt index fb3e030..9451cd0 100644 --- a/android/src/main/java/com/authsignal/react/AuthsignalPackage.kt +++ b/android/src/main/java/com/authsignal/react/AuthsignalPackage.kt @@ -12,7 +12,8 @@ class AuthsignalPackage : ReactPackage { AuthsignalEmailModule(reactContext), AuthsignalPasskeyModule(reactContext), AuthsignalPushModule(reactContext), - AuthsignalDeviceModule(reactContext), + AuthsignalInAppModule(reactContext), + AuthsignalQRCodeModule(reactContext), AuthsignalSMSModule(reactContext), AuthsignalTOTPModule(reactContext), AuthsignalWhatsappModule(reactContext) diff --git a/android/src/main/java/com/authsignal/react/AuthsignalPushModule.kt b/android/src/main/java/com/authsignal/react/AuthsignalPushModule.kt index d020da0..ffd153f 100644 --- a/android/src/main/java/com/authsignal/react/AuthsignalPushModule.kt +++ b/android/src/main/java/com/authsignal/react/AuthsignalPushModule.kt @@ -51,6 +51,7 @@ class AuthsignalPushModule(private val reactContext: ReactApplicationContext) : val map = Arguments.createMap() map.putString("credentialId", credential!!.credentialId) map.putString("createdAt", credential.createdAt) + map.putString("userId", credential.userId) map.putString("lastAuthenticatedAt", credential.lastAuthenticatedAt) promise.resolve(map) } @@ -70,7 +71,13 @@ class AuthsignalPushModule(private val reactContext: ReactApplicationContext) : promise.reject(errorCode, response.error) } else { - promise.resolve(response.data) + val credential = response.data + val map = Arguments.createMap() + map.putString("credentialId", credential!!.credentialId) + map.putString("createdAt", credential.createdAt) + map.putString("userId", credential.userId) + map.putString("lastAuthenticatedAt", credential.lastAuthenticatedAt) + promise.resolve(map) } } } diff --git a/android/src/main/java/com/authsignal/react/AuthsignalDeviceModule.kt b/android/src/main/java/com/authsignal/react/AuthsignalQRCodeModule.kt similarity index 70% rename from android/src/main/java/com/authsignal/react/AuthsignalDeviceModule.kt rename to android/src/main/java/com/authsignal/react/AuthsignalQRCodeModule.kt index 1ebdd4e..5e993d3 100644 --- a/android/src/main/java/com/authsignal/react/AuthsignalDeviceModule.kt +++ b/android/src/main/java/com/authsignal/react/AuthsignalQRCodeModule.kt @@ -1,7 +1,7 @@ package com.authsignal.react import android.util.Log -import com.authsignal.device.AuthsignalDevice +import com.authsignal.qr.AuthsignalQRCode import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext @@ -12,12 +12,12 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch -class AuthsignalDeviceModule(private val reactContext: ReactApplicationContext) : +class AuthsignalQRCodeModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule( reactContext ) { private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) - private var authsignal: AuthsignalDevice? = null + private var authsignal: AuthsignalQRCode? = null private var defaultError = "unexpected_error" override fun getConstants(): Map? { @@ -27,12 +27,12 @@ class AuthsignalDeviceModule(private val reactContext: ReactApplicationContext) } override fun getName(): String { - return "AuthsignalDeviceModule" + return "AuthsignalQRCodeModule" } @ReactMethod fun initialize(tenantID: String?, baseURL: String?, promise: Promise) { - Log.d("AuthsignalDeviceModule", "initialize: $tenantID, $baseURL") + Log.d("AuthsignalQRCodeModule", "initialize: $tenantID, $baseURL") authsignal = AuthsignalDevice(tenantID!!, baseURL!!) promise.resolve(null) @@ -100,34 +100,6 @@ class AuthsignalDeviceModule(private val reactContext: ReactApplicationContext) } } - @ReactMethod - fun getChallenge(promise: Promise) { - launch(promise) { - val response = it.getChallenge() - - if (response.error != null) { - val errorCode = response.errorCode ?: defaultError - - promise.reject(errorCode, response.error) - } else { - val challenge = response.data - - if (challenge == null) { - promise.resolve(null) - } else { - val map = Arguments.createMap() - map.putString("challengeId", challenge.challengeId) - map.putString("actionCode", challenge.actionCode) - map.putString("idempotencyKey", challenge.idempotencyKey) - map.putString("ipAddress", challenge.ipAddress) - map.putString("deviceId", challenge.deviceId) - map.putString("userAgent", challenge.userAgent) - promise.resolve(map) - } - } - } - } - @ReactMethod fun claimChallenge( challengeId: String, @@ -175,33 +147,12 @@ class AuthsignalDeviceModule(private val reactContext: ReactApplicationContext) } } - @ReactMethod - fun verify(promise: Promise) { - launch(promise) { - val response = it.verify() - - if (response.error != null) { - val errorCode = response.errorCode ?: defaultError - - promise.reject(errorCode, response.error) - } else { - val data = response.data!! - val map = Arguments.createMap() - map.putString("token", data.token) - map.putString("userId", data.userId) - map.putString("userAuthenticatorId", data.userAuthenticatorId) - map.putString("username", data.username) - promise.resolve(map) - } - } - } - private fun launch(promise: Promise, fn: suspend (client: AuthsignalDevice) -> Unit) { coroutineScope.launch { authsignal?.let { fn(it) } ?: run { - Log.w("init_error", "AuthsignalDeviceModule is not initialized.") + Log.w("init_error", "AuthsignalQRCodeModule is not initialized.") promise.resolve(null) } diff --git a/ios/AuthsignalInAppModule.m b/ios/AuthsignalInAppModule.m new file mode 100644 index 0000000..1c5f694 --- /dev/null +++ b/ios/AuthsignalInAppModule.m @@ -0,0 +1,26 @@ +#import +#import + +@interface RCT_EXTERN_MODULE(AuthsignalInAppModule, NSObject) + +RCT_EXTERN_METHOD(initialize:(NSString)tenantID + withBaseURL:(NSString)baseURL + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(getCredential:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(addCredential:(NSString)token + withRequireUserAuthentication:(BOOL)requireUserAuthentication + withKeychainAccess:(NSString)keychainAccess + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(removeCredential:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(verify:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) + +@end diff --git a/ios/AuthsignalInAppModule.swift b/ios/AuthsignalInAppModule.swift new file mode 100644 index 0000000..388ab04 --- /dev/null +++ b/ios/AuthsignalInAppModule.swift @@ -0,0 +1,163 @@ +import Security +import Foundation +import Authsignal + +@objc(AuthsignalInAppModule) +class AuthsignalInAppModule: NSObject { + var authsignal: AuthsignalInApp? + + @objc static func requiresMainQueueSetup() -> Bool { + return true + } + + @objc func initialize( + _ tenantID: NSString, + withBaseURL baseURL: NSString, + resolver resolve: @escaping RCTPromiseResolveBlock, + rejecter reject: @escaping RCTPromiseRejectBlock + ) -> Void { + self.authsignal = AuthsignalInApp(tenantID: tenantID as String, baseURL: baseURL as String) + + resolve(nil) + } + + @objc func getCredential( + _ resolve: @escaping RCTPromiseResolveBlock, + rejecter reject: @escaping RCTPromiseRejectBlock + ) -> Void { + guard let authsignal = authsignal else { + resolve(nil) + return + } + + Task.init { + let response = await authsignal.getCredential() + + if let error = response.error { + reject(response.errorCode ?? "unexpected_error", error, nil) + } else if let data = response.data { + let credential: [String: String?] = [ + "credentialId": data.credentialId, + "createdAt": data.createdAt, + "userId": data.userId, + "lastAuthenticatedAt": data.lastAuthenticatedAt, + ] + + resolve(credential) + } else { + resolve(nil) + } + } + } + + @objc func addCredential( + _ token: NSString?, + withRequireUserAuthentication requireUserAuthentication: Bool, + withKeychainAccess keychainAccess: NSString, + resolver resolve: @escaping RCTPromiseResolveBlock, + rejecter reject: @escaping RCTPromiseRejectBlock + ) -> Void { + guard let authsignal = authsignal else { + resolve(nil) + return + } + + let tokenStr = token as String? + let userPresenceRequired = requireUserAuthentication as Bool + let keychainAccess = getKeychainAccess(value: keychainAccess as String?) + + Task.init { + let response = await authsignal.addCredential( + token: tokenStr, + keychainAccess: keychainAccess, + userPresenceRequired: userPresenceRequired + ) + + if let error = response.error { + reject(response.errorCode ?? "unexpected_error", error, nil) + } else if let data = response.data { + let credential: [String: String?] = [ + "credentialId": data.credentialId, + "createdAt": data.createdAt, + "userId": data.userId, + "lastAuthenticatedAt": data.lastAuthenticatedAt, + ] + + resolve(credential) + } else { + resolve(nil) + } + } + } + + @objc func removeCredential( + _ resolve: @escaping RCTPromiseResolveBlock, + rejecter reject: @escaping RCTPromiseRejectBlock + ) -> Void { + guard let authsignal = authsignal else { + resolve(nil) + return + } + + Task.init { + let response = await authsignal.removeCredential() + + if let error = response.error { + reject(response.errorCode ?? "unexpected_error", error, nil) + } else { + resolve(response.data) + } + } + } + + @objc func verify( + _ resolve: @escaping RCTPromiseResolveBlock, + rejecter reject: @escaping RCTPromiseRejectBlock + ) -> Void { + guard let authsignal = authsignal else { + resolve(nil) + return + } + + Task.init { + let response = await authsignal.verify() + + if let error = response.error { + reject(response.errorCode ?? "unexpected_error", error, nil) + } else if let data = response.data { + let result: [String: String?] = [ + "token": data.token, + "userId": data.userId, + "userAuthenticatorId": data.userAuthenticatorId, + "username": data.username, + ] + + resolve(result) + } else { + resolve(nil) + } + } + } + + func getKeychainAccess(value: String?) -> KeychainAccess { + switch value { + case "afterFirstUnlock": + return .afterFirstUnlock + + case "afterFirstUnlockThisDeviceOnly": + return .afterFirstUnlockThisDeviceOnly + + case "whenUnlocked": + return .whenUnlocked + + case "whenUnlockedThisDeviceOnly": + return .whenUnlockedThisDeviceOnly + + case "whenPasscodeSetThisDeviceOnly": + return .whenPasscodeSetThisDeviceOnly + + default: + return .whenUnlockedThisDeviceOnly + } + } +} diff --git a/ios/AuthsignalPushModule.swift b/ios/AuthsignalPushModule.swift index 55ce452..8c14d6a 100644 --- a/ios/AuthsignalPushModule.swift +++ b/ios/AuthsignalPushModule.swift @@ -39,6 +39,7 @@ class AuthsignalPushModule: NSObject { let credential: [String: String?] = [ "credentialId": data.credentialId, "createdAt": data.createdAt, + "userId": data.userId, "lastAuthenticatedAt": data.lastAuthenticatedAt, ] @@ -75,7 +76,14 @@ class AuthsignalPushModule: NSObject { if let error = response.error { reject(response.errorCode ?? "unexpected_error", error, nil) } else { - resolve(response.data) + let credential: [String: String?] = [ + "credentialId": data.credentialId, + "createdAt": data.createdAt, + "userId": data.userId, + "lastAuthenticatedAt": data.lastAuthenticatedAt, + ] + + resolve(credential) } } } diff --git a/ios/AuthsignalDeviceModule.m b/ios/AuthsignalQRModule.m similarity index 81% rename from ios/AuthsignalDeviceModule.m rename to ios/AuthsignalQRModule.m index edb9249..db1258a 100644 --- a/ios/AuthsignalDeviceModule.m +++ b/ios/AuthsignalQRModule.m @@ -1,7 +1,7 @@ #import #import -@interface RCT_EXTERN_MODULE(AuthsignalDeviceModule, NSObject) +@interface RCT_EXTERN_MODULE(AuthsignalQRCodeModule, NSObject) RCT_EXTERN_METHOD(initialize:(NSString)tenantID withBaseURL:(NSString)baseURL @@ -20,9 +20,6 @@ @interface RCT_EXTERN_MODULE(AuthsignalDeviceModule, NSObject) RCT_EXTERN_METHOD(removeCredential:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(getChallenge:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) - RCT_EXTERN_METHOD(claimChallenge:(NSString)challengeId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) @@ -33,7 +30,4 @@ @interface RCT_EXTERN_MODULE(AuthsignalDeviceModule, NSObject) resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(verify:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) - @end diff --git a/ios/AuthsignalDeviceModule.swift b/ios/AuthsignalQRModule.swift similarity index 74% rename from ios/AuthsignalDeviceModule.swift rename to ios/AuthsignalQRModule.swift index 8cf15d5..dbe7110 100644 --- a/ios/AuthsignalDeviceModule.swift +++ b/ios/AuthsignalQRModule.swift @@ -2,9 +2,9 @@ import Security import Foundation import Authsignal -@objc(AuthsignalDeviceModule) -class AuthsignalDeviceModule: NSObject { - var authsignal: AuthsignalDevice? +@objc(AuthsignalQRCodeModule) +class AuthsignalQRCodeModule: NSObject { + var authsignal: AuthsignalQRCode? @objc static func requiresMainQueueSetup() -> Bool { return true @@ -16,7 +16,7 @@ class AuthsignalDeviceModule: NSObject { resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock ) -> Void { - self.authsignal = AuthsignalDevice(tenantID: tenantID as String, baseURL: baseURL as String) + self.authsignal = AuthsignalQRCode(tenantID: tenantID as String, baseURL: baseURL as String) resolve(nil) } @@ -110,37 +110,6 @@ class AuthsignalDeviceModule: NSObject { } } - @objc func getChallenge( - _ resolve: @escaping RCTPromiseResolveBlock, - rejecter reject: @escaping RCTPromiseRejectBlock - ) -> Void { - guard let authsignal = authsignal else { - resolve(nil) - return - } - - Task.init { - let response = await authsignal.getChallenge() - - if let error = response.error { - reject(response.errorCode ?? "unexpected_error", error, nil) - } else if let data = response.data as? DeviceChallenge { - let challenge: [String: String?] = [ - "challengeId": data.challengeId, - "actionCode": data.actionCode, - "idempotencyKey": data.idempotencyKey, - "userAgent": data.userAgent, - "deviceId": data.deviceId, - "ipAddress": data.ipAddress, - ] - - resolve(challenge) - } else { - resolve(nil) - } - } - } - @objc func claimChallenge( _ challengeId: NSString, resolver resolve: @escaping RCTPromiseResolveBlock, @@ -203,35 +172,6 @@ class AuthsignalDeviceModule: NSObject { } } - @objc func verify( - _ resolve: @escaping RCTPromiseResolveBlock, - rejecter reject: @escaping RCTPromiseRejectBlock - ) -> Void { - guard let authsignal = authsignal else { - resolve(nil) - return - } - - Task.init { - let response = await authsignal.verify() - - if let error = response.error { - reject(response.errorCode ?? "unexpected_error", error, nil) - } else if let data = response.data { - let result: [String: String?] = [ - "token": data.token, - "userId": data.userId, - "userAuthenticatorId": data.userAuthenticatorId, - "username": data.username, - ] - - resolve(result) - } else { - resolve(nil) - } - } - } - func getKeychainAccess(value: String?) -> KeychainAccess { switch value { case "afterFirstUnlock": diff --git a/package.json b/package.json index 63a11c9..7bd3680 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-authsignal", - "version": "1.8.1", + "version": "2.0.0-alpha1", "description": "The official Authsignal React Native library.", "main": "lib/commonjs/index", "module": "lib/module/index", diff --git a/react-native-authsignal.podspec b/react-native-authsignal.podspec index e28076c..f65131e 100644 --- a/react-native-authsignal.podspec +++ b/react-native-authsignal.podspec @@ -17,7 +17,7 @@ Pod::Spec.new do |s| s.source_files = "ios/**/*.{h,m,mm,swift}" s.dependency "React-Core" - s.dependency 'Authsignal', '1.6.0' + s.dependency 'Authsignal', '2.0.0-alpha' # Don't install the dependencies when we run `pod install` in the old architecture. if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then diff --git a/src/inapp.ts b/src/inapp.ts new file mode 100644 index 0000000..b01d1aa --- /dev/null +++ b/src/inapp.ts @@ -0,0 +1,122 @@ +import { NativeModules, Platform } from 'react-native'; +import { handleErrorCodes, LINKING_ERROR } from './error'; +import type { + AuthsignalResponse, + AppCredential, + InAppVerifyResponse, + AddCredentialInput, +} from './types'; + +interface ConstructorArgs { + tenantID: string; + baseURL: string; + enableLogging: boolean; +} + +let initialized = false; + +const AuthsignalInAppModule = NativeModules.AuthsignalInAppModule + ? NativeModules.AuthsignalInAppModule + : new Proxy( + {}, + { + get() { + throw new Error(LINKING_ERROR); + }, + } + ); + +export class AuthsignalInApp { + tenantID: string; + baseURL: string; + enableLogging: boolean; + + constructor({ tenantID, baseURL, enableLogging }: ConstructorArgs) { + this.tenantID = tenantID; + this.baseURL = baseURL; + this.enableLogging = enableLogging; + } + + async getCredential(): Promise> { + await this.ensureModuleIsInitialized(); + + try { + const data = await AuthsignalInAppModule.getCredential(); + + return { data }; + } catch (ex) { + if (this.enableLogging) { + console.log(ex); + } + + return handleErrorCodes(ex); + } + } + + async addCredential({ + token, + requireUserAuthentication = false, + keychainAccess, + }: AddCredentialInput = {}): Promise> { + await this.ensureModuleIsInitialized(); + + try { + const data = + Platform.OS === 'ios' + ? await AuthsignalInAppModule.addCredential( + token, + requireUserAuthentication, + keychainAccess + ) + : await AuthsignalInAppModule.addCredential(token); + + return { data }; + } catch (ex) { + if (this.enableLogging) { + console.log(ex); + } + + return handleErrorCodes(ex); + } + } + + async removeCredential(): Promise> { + await this.ensureModuleIsInitialized(); + + try { + const data = await AuthsignalInAppModule.removeCredential(); + return { data }; + } catch (ex) { + if (this.enableLogging) { + console.log(ex); + } + + return handleErrorCodes(ex); + } + } + async verify(): Promise> { + await this.ensureModuleIsInitialized(); + + try { + const data = await AuthsignalInAppModule.verify(); + + return { data }; + } catch (ex) { + if (this.enableLogging) { + console.log(ex); + } + + return handleErrorCodes(ex); + } + } + + private async ensureModuleIsInitialized() { + if (initialized) { + return; + } + + await AuthsignalInAppModule.initialize(this.tenantID, this.baseURL); + + initialized = true; + } +} diff --git a/src/index.tsx b/src/index.tsx index b931612..b6cb68f 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -5,8 +5,9 @@ import { AuthsignalPasskey } from './passkey'; import { AuthsignalPush } from './push'; import { AuthsignalSms } from './sms'; import { AuthsignalTotp } from './totp'; -import { AuthsignalDevice } from './device'; +import { AuthsignalQrCode } from './qr'; import { AuthsignalWhatsapp } from './whatsapp'; +import { AuthsignalInApp } from './inapp'; export * from './types'; export { ErrorCode } from './error'; @@ -37,7 +38,8 @@ export class Authsignal { email: AuthsignalEmail; passkey: AuthsignalPasskey; push: AuthsignalPush; - device: AuthsignalDevice; + qr: AuthsignalQrCode; + inapp: AuthsignalInApp; sms: AuthsignalSms; totp: AuthsignalTotp; whatsapp: AuthsignalWhatsapp; @@ -52,23 +54,16 @@ export class Authsignal { this.baseURL = baseURL; this.enableLogging = enableLogging; - this.passkey = new AuthsignalPasskey({ - tenantID, - baseURL, - deviceID, - enableLogging, - }); + const input = { tenantID, baseURL, deviceID, enableLogging }; - this.email = new AuthsignalEmail({ tenantID, baseURL, enableLogging }); - this.push = new AuthsignalPush({ tenantID, baseURL, enableLogging }); - this.device = new AuthsignalDevice({ tenantID, baseURL, enableLogging }); - this.sms = new AuthsignalSms({ tenantID, baseURL, enableLogging }); - this.totp = new AuthsignalTotp({ tenantID, baseURL, enableLogging }); - this.whatsapp = new AuthsignalWhatsapp({ - tenantID, - baseURL, - enableLogging, - }); + this.passkey = new AuthsignalPasskey(input); + this.email = new AuthsignalEmail(input); + this.push = new AuthsignalPush(input); + this.qr = new AuthsignalQrCode(input); + this.inapp = new AuthsignalInApp(input); + this.sms = new AuthsignalSms(input); + this.totp = new AuthsignalTotp(input); + this.whatsapp = new AuthsignalWhatsapp(input); } async setToken(token: string): Promise { diff --git a/src/push.ts b/src/push.ts index 73e89d2..ae7106f 100644 --- a/src/push.ts +++ b/src/push.ts @@ -1,10 +1,11 @@ import { NativeModules, Platform } from 'react-native'; import { handleErrorCodes, LINKING_ERROR } from './error'; import type { + AddCredentialInput, + AppChallenge, + AppCredential, AuthsignalResponse, - KeychainAccess, - PushChallenge, - PushCredential, + UpdateChallengeInput, } from './types'; interface ConstructorArgs { @@ -26,18 +27,6 @@ const AuthsignalPushModule = NativeModules.AuthsignalPushModule } ); -interface AddCredentialInput { - token?: string; - requireUserAuthentication?: boolean; - keychainAccess?: KeychainAccess; -} - -interface UpdateChallengeInput { - challengeId: string; - approved: boolean; - verificationCode?: string | null; -} - export class AuthsignalPush { tenantID: string; baseURL: string; @@ -49,7 +38,7 @@ export class AuthsignalPush { this.enableLogging = enableLogging; } - async getCredential(): Promise> { + async getCredential(): Promise> { await this.ensureModuleIsInitialized(); try { @@ -69,7 +58,7 @@ export class AuthsignalPush { token, requireUserAuthentication = false, keychainAccess, - }: AddCredentialInput = {}): Promise> { + }: AddCredentialInput = {}): Promise> { await this.ensureModuleIsInitialized(); try { @@ -107,7 +96,7 @@ export class AuthsignalPush { } } - async getChallenge(): Promise> { + async getChallenge(): Promise> { await this.ensureModuleIsInitialized(); try { diff --git a/src/device.ts b/src/qr.ts similarity index 58% rename from src/device.ts rename to src/qr.ts index fce12c2..03d5b89 100644 --- a/src/device.ts +++ b/src/qr.ts @@ -1,12 +1,12 @@ import { NativeModules, Platform } from 'react-native'; import { handleErrorCodes, LINKING_ERROR } from './error'; import type { + AddCredentialInput, + AppCredential, AuthsignalResponse, + ClaimChallengeInput, ClaimChallengeResponse, - DeviceChallenge, - DeviceCredential, - KeychainAccess, - VerifyDeviceResponse, + UpdateChallengeInput, } from './types'; interface ConstructorArgs { @@ -17,8 +17,8 @@ interface ConstructorArgs { let initialized = false; -const AuthsignalDeviceModule = NativeModules.AuthsignalDeviceModule - ? NativeModules.AuthsignalDeviceModule +const AuthsignalQrCodeModule = NativeModules.AuthsignalQrCodeModule + ? NativeModules.AuthsignalQrCodeModule : new Proxy( {}, { @@ -28,23 +28,7 @@ const AuthsignalDeviceModule = NativeModules.AuthsignalDeviceModule } ); -interface AddCredentialInput { - token?: string; - requireUserAuthentication?: boolean; - keychainAccess?: KeychainAccess; -} - -interface ClaimChallengeInput { - challengeId: string; -} - -interface UpdateChallengeInput { - challengeId: string; - approved: boolean; - verificationCode?: string | null; -} - -export class AuthsignalDevice { +export class AuthsignalQrCode { tenantID: string; baseURL: string; enableLogging: boolean; @@ -55,11 +39,11 @@ export class AuthsignalDevice { this.enableLogging = enableLogging; } - async getCredential(): Promise> { + async getCredential(): Promise> { await this.ensureModuleIsInitialized(); try { - const data = await AuthsignalDeviceModule.getCredential(); + const data = await AuthsignalQrCodeModule.getCredential(); return { data }; } catch (ex) { @@ -75,18 +59,18 @@ export class AuthsignalDevice { token, requireUserAuthentication = false, keychainAccess, - }: AddCredentialInput = {}): Promise> { + }: AddCredentialInput = {}): Promise> { await this.ensureModuleIsInitialized(); try { const data = Platform.OS === 'ios' - ? await AuthsignalDeviceModule.addCredential( + ? await AuthsignalQrCodeModule.addCredential( token, requireUserAuthentication, keychainAccess ) - : await AuthsignalDeviceModule.addCredential(token); + : await AuthsignalQrCodeModule.addCredential(token); return { data }; } catch (ex) { @@ -102,25 +86,7 @@ export class AuthsignalDevice { await this.ensureModuleIsInitialized(); try { - const data = await AuthsignalDeviceModule.removeCredential(); - return { data }; - } catch (ex) { - if (this.enableLogging) { - console.log(ex); - } - - return handleErrorCodes(ex); - } - } - - async getChallenge(): Promise< - AuthsignalResponse - > { - await this.ensureModuleIsInitialized(); - - try { - const data = await AuthsignalDeviceModule.getChallenge(); - + const data = await AuthsignalQrCodeModule.removeCredential(); return { data }; } catch (ex) { if (this.enableLogging) { @@ -137,7 +103,7 @@ export class AuthsignalDevice { await this.ensureModuleIsInitialized(); try { - const data = await AuthsignalDeviceModule.claimChallenge(challengeId); + const data = await AuthsignalQrCodeModule.claimChallenge(challengeId); return { data }; } catch (ex) { @@ -157,7 +123,7 @@ export class AuthsignalDevice { await this.ensureModuleIsInitialized(); try { - const data = await AuthsignalDeviceModule.updateChallenge( + const data = await AuthsignalQrCodeModule.updateChallenge( challengeId, approved, verificationCode @@ -173,28 +139,12 @@ export class AuthsignalDevice { } } - async verify(): Promise> { - await this.ensureModuleIsInitialized(); - - try { - const data = await AuthsignalDeviceModule.verify(); - - return { data }; - } catch (ex) { - if (this.enableLogging) { - console.log(ex); - } - - return handleErrorCodes(ex); - } - } - private async ensureModuleIsInitialized() { if (initialized) { return; } - await AuthsignalDeviceModule.initialize(this.tenantID, this.baseURL); + await AuthsignalQrCodeModule.initialize(this.tenantID, this.baseURL); initialized = true; } diff --git a/src/types.ts b/src/types.ts index ef215af..0772da9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -45,13 +45,13 @@ export interface VerifyResponse { failureReason?: string; } -export interface PushCredential { - credentialId: string; - createdAt: string; - lastAuthenticatedAt?: string; +export interface AddCredentialInput { + token?: string; + requireUserAuthentication?: boolean; + keychainAccess?: KeychainAccess; } -export interface PushChallenge { +export interface AppChallenge { challengeId: string; actionCode?: string; idempotencyKey?: string; @@ -60,20 +60,15 @@ export interface PushChallenge { ipAddress?: string; } -export interface DeviceCredential { +export interface AppCredential { credentialId: string; createdAt: string; userId: string; lastAuthenticatedAt?: string; } -export interface DeviceChallenge { +export interface ClaimChallengeInput { challengeId: string; - actionCode?: string; - idempotencyKey?: string; - userAgent?: string; - deviceId?: string; - ipAddress?: string; } export interface ClaimChallengeResponse { @@ -82,7 +77,13 @@ export interface ClaimChallengeResponse { ipAddress?: string; } -export interface VerifyDeviceResponse { +export interface UpdateChallengeInput { + challengeId: string; + approved: boolean; + verificationCode?: string | null; +} + +export interface InAppVerifyResponse { token: string; userId: string; userAuthenticatorId: string;