Skip to main content

Offline functions

The SDK offers the possibility to use a specific functionality without an Internet connection or an active connection to the SSMS. This functionality is: generation of a one-time-password (OTP).

In order to enable the usage of these offline functions, first you need to define ssms.allowOfflinePinVerification in the mc_config.json file and you need to have a user that is already activated and logged in at least once.

Note otp verification can only be successful if the app digest check is enabled on ssms

{
"useScp": false,
"useTokenBasedLogin": false,
"useSmartScreen": false,
"astServerBackend": "ssms",

"ssms": {
"allowOfflinePinVerification": true
}
}

allowOfflinePinVerification:

  • true: the user's pin will be cashed. Once the user's pin is provided, GenerateOtpEvent can be triggered without pin.
  • false: the pin will never be cached and each time a GenerateOtpEvent is triggered the pin needs to be provided.

GenrateOtpEvent has 3 different constructors:

  1. GenerateOtpEvent(byte[] optionalData, String pin, UserIdentifier userIdentifier): can be used always.
  2. GenerateOtpEvent(byte[] optionalData, String pin): can be used if the user is logged in online, or if a GenerateOtpEvent with a userIdentifier has been triggered previously.
  3. GenerateOtpEvent(byte[] optionalData): can be used if allowOfflinePinVerification is set to true in mc_config.json and a GenerateOtpEvent was previously triggered with a userIdentifier and a pin or the user is logged in online.

iOS/Swift

Swift Generate OTP
private var localizedTable = "GenerateOtpModel"

class GenerateOTPModel {
static let sharedInstance: GenerateOTPModel = {
var sharedInstance = GenerateOTPModel()
return sharedInstance
}()

func generateOTP(otpRequest: GenerateOtpRequest, completion: @escaping (ActionResult, GenerateOtpResult?) -> Void) {
performGenerateOtp(otpRequest: otpRequest) { actionResult, otpResult in
completion(actionResult, otpResult)
}
}

func performGenerateOtp(otpRequest: GenerateOtpRequest, completion: @escaping (ActionResult, GenerateOtpResult?) -> Void) {
let userIdentifier = KsUserIdentifier(tenantId: otpRequest.tenant, userId: otpRequest.user)
let event: KSMGenerateOtpEvent
event = KSMGenerateOtpEvent(optionalData: otpRequest.data, userIdentifier: userIdentifier, pin: otpRequest.pin)

MasterControllerAdapter.sharedInstance.sendEvent2MasterController(event: event) { resultEvent in
if let eventResult = resultEvent as? KSMGenerateOtpResultEvent {
let result = GenerateOTPModel.handleGenerateOtpResultEvent(status: eventResult.status)
let actionResult = ActionResult(title: "generateOtpModel.title".localized(table: localizedTable),
success: result.success,
error: result.errorText?.localized(table: localizedTable),
duration: 0,
event: eventResult)
if result.success {
let otpResult = GenerateOtpResult(otp: eventResult.otp)
completion(actionResult, otpResult)
} else {
completion(actionResult, nil)
}
}
}
}

static let resultDict: [KSMEventStatusType: EventResultState] =
[.KSMOK: EventResultState(success: true, errorText: nil),
.KSMFAILED: EventResultState(success: false, errorText: "failed"),
.KSMINTERNAL_ERROR: EventResultState(success: false, errorText: "internal error")]

static func handleGenerateOtpResultEvent(status: KSMEventStatusType) -> EventResultState {
guard let state = resultDict[status] else {
return EventResultState(success: false, errorText: "failed")
}
return state
}
}

struct GenerateOtpRequest {
var pin: String
var data: Data
var user: String
var tenant: String
}

struct GenerateOtpResult {
var otp: String?
}

Android/Kotlin

Kotlin Generate OTP
private fun performGenerateOtpEvent(request: GenerateOtpRequest, completion: (ActionResult) -> Unit) {
val generateOtpEvent: GenerateOtpEvent?
if (request.pin.isBlank() && request.userId.isBlank()) {
generateOtpEvent = GenerateOtpEvent(
request.data.toByteArray())
} else if (request.userId.isBlank()) {
generateOtpEvent = GenerateOtpEvent(
request.data.toByteArray(),
request.pin)
} else {
generateOtpEvent = GenerateOtpEvent(
request.data.toByteArray(),
request.pin,
UserIdentifier(request.tenantId, request.userId))
}

val timer = CallTimer()
masterControllerAdapter?.postEvent(generateOtpEvent)?.then { resultEvent ->
Log.e(tag, "Result ==> $resultEvent")
val neededTime = timer.stop()
val title = MasterControllerAdapter.ctx.resources.getString(R.string.generateOtpModel_title)
when (resultEvent) {
is GenerateOtpResultEvent -> {
val status = handleGenerateOtpStatus(resultEvent.status)

var errorText: String? = null
if (resultEvent.hasErrorOccurred()) {

errorText = String.format(MasterControllerAdapter.ctx.resources.getString(status.errorKey), resultEvent.reportId, resultEvent.errorCode)
}
else if (status.errorKey != -1){
val numRetryCounter = MasterControllerAdapter.ctx.resources.getQuantityString(R.plurals.num_retry, resultEvent.retryCounter, resultEvent.retryCounter)
val numRetryDelay = MasterControllerAdapter.ctx.resources.getQuantityString(R.plurals.num_delay, resultEvent.retryDelay, resultEvent.retryDelay)
errorText = String.format(MasterControllerAdapter.ctx.resources.getString(status.errorKey), numRetryCounter, numRetryDelay)
}

completion(
ActionResult(
title = title,
success = status.success,
error = errorText,
duration = neededTime,
event = resultEvent
)

)
}

else -> {
completion(
ActionResult(
title = title,
success = false,
error = MasterControllerAdapter.ctx.resources.getString(R.string.generateOtpModel_status_unexpectedEvent),
duration = neededTime,
event = resultEvent
)
)
}
}
}
}

data class GenerateOtpRequest(
val userId: String,
val tenantId: String,
val pin: String,
val data: String
)