Skip to content

Commit

Permalink
Release 0.3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
SergioFierro authored Nov 6, 2024
2 parents a813766 + e349e12 commit 93a64f8
Show file tree
Hide file tree
Showing 15 changed files with 427 additions and 138 deletions.
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* [Building and Running Sample App](#building-and-running-sample-app)
* [Project Structure](#project-structure)
* [Code Structure](#code-structure)
* [Exception Types](#exception-types)
* [Useful Gradle Tasks](#useful-gradle-tasks)

## About <a name="about"></a>
Expand Down Expand Up @@ -166,7 +167,16 @@ The authenticate payload for authenticating a user is a JSON with the schema:

1. Clone this repository.
2. Open the project in IntelliJ IDEA or Android Studio or open `iosApp` module in Xcode.
3. Set your backend URL [BaseUrl](https://github.com/twilio/twilio-verify-passkeys/blob/main/iosApp/iosApp/Core/AuthenticationManager.swift#L22) and [Entitlements](https://github.com/twilio/twilio-verify-passkeys/blob/main/iosApp/iosApp/iosApp.entitlements#L7)
3. Configure your backend
- Set your backend domain and entitlements. Update these values in [Constants.swift](https://github.com/twilio/twilio-verify-passkeys/blob/main/iosApp/iosApp/Constants.swift#L12) and [iosApp.entitlements](https://github.com/twilio/twilio-verify-passkeys/blob/main/iosApp/iosApp/iosApp.entitlements#L7)
- [Constants.swift](https://github.com/twilio/twilio-verify-passkeys/blob/main/iosApp/iosApp/Constants.swift#L12) - Define your backend domain:
```swift
let domain: String = "passkeys-service.com" // Example domain; replace with your backend domain
```
- [iosApp.entitlements](https://github.com/twilio/twilio-verify-passkeys/blob/main/iosApp/iosApp/iosApp.entitlements#L7) - Specify entitlements:
```swift
<string>webcredentials:passkeys-service.com</string> <!-- Example entitlement; update with your domain -->
```
4. Build and run the iOS app from the `iosApp` module.

**Note**: To start sign up/in flows, the iPhone must have a valid iCloud account to store and fetch passkeys.
Expand Down Expand Up @@ -201,6 +211,23 @@ The `iosApp` module contains iOS-specific code, such as:
- Sample application that works as a code snippet for integrating with the Twilio Verify Passkeys SDK
- iOS-specific UI components

## Exception Types <a name="exception-types"></a>

The [TwilioException](https://github.com/twilio/twilio-verify-passkeys/blob/main/shared/src/commonMain/kotlin/com/twilio/passkeys/exception/TwilioException.kt) class offers structured error handling for various scenarios specific to Twilio's passkeys features. Each error type is represented by a subclass with a unique error code, enabling precise exception management. Below are the defined exception types:

### Exception Overview

| Exception | Code | Description | Platform-Specific Details |
|-----------------------------------|------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **`DomException`** | 1001 | Handles errors related to WebAuthn DOM configurations for passkeys. | **Android**: Ensure `/.well-known/assetlinks.json` is set up. See [Android Guide](https://developer.android.com/identity/sign-in/credential-manager#add-support-dal). <br> **iOS**: Ensure `/.well-known/apple-app-site-association` is set up. See [Apple Guide](https://developer.apple.com/documentation/xcode/supporting-associated-domains). |
| **`UserCanceledException`** | 1002 | Thrown when the user voluntarily cancels an operation, allowing graceful termination of user actions. | N/A |
| **`InterruptedException`** | 1003 | Thrown when an operation is interrupted, typically recoverable by retrying. | **Android Only** |
| **`UnsupportedException`** | 1004 | Indicates the device does not support or has disabled the passkeys feature, preventing passkey operations. | N/A |
| **`NoCredentialException`** | 1005 | Thrown when no passkey credentials are available, indicating no user credentials are set up for authentication. | **Android Only** |
| **`MissingAttestationObjectException`** | 1006 | Raised when the attestation object is null, which is essential for passkey operations. | **iOS Only** |
| **`InvalidPayloadException`** | 1007 | Thrown when the JSON payload is invalid, meaning the data does not meet the expected format or schema. | N/A |
| **`GeneralException`** | 1008 | Represents a general or unspecified error used as a fallback for errors not fitting other categories. | N/A |

## Useful Gradle Tasks <a name="useful-gradle-tasks"></a>

### Running Unit Tests
Expand Down
2 changes: 1 addition & 1 deletion scripts/versioning/create_github_release.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def create_github_release(repo_owner, repo_name, access_token, tag_name, release
response = Faraday.post(github_api_url, release_data.to_json, headers)

if response.status == 201
puts "Release URL: #{JSON.parse(response.body)
puts "Release URL: #{JSON.parse(response.body)}"
release_url = JSON.parse(response.body)['upload_url'].gsub("{?name,label}", "?name=#{File.basename(file_path)}")
if file_path
upload_asset(release_url, file_path, access_token)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import androidx.credentials.GetPublicKeyCredentialOption
import androidx.credentials.PublicKeyCredential
import androidx.credentials.exceptions.CreateCredentialException
import androidx.credentials.exceptions.GetCredentialException
import com.twilio.passkeys.exception.TwilioException
import com.twilio.passkeys.extensions.toTwilioException
import com.twilio.passkeys.models.AuthenticatePasskeyRequest
import com.twilio.passkeys.models.CreatePasskeyRequest
import kotlinx.serialization.encodeToString
Expand All @@ -40,7 +40,7 @@ import kotlinx.serialization.json.Json
* @property credentialManager The credential manager responsible for managing passkey credentials.
* @property passkeyPayloadMapper The passkey payload mapper used for mapping passkey payloads and responses.
*/
actual class TwilioPasskeys internal constructor(
actual open class TwilioPasskeys internal constructor(
private val credentialManager: CredentialManager,
private val passkeyPayloadMapper: PasskeyPayloadMapper,
) {
Expand Down Expand Up @@ -81,8 +81,7 @@ actual class TwilioPasskeys internal constructor(
),
)
} catch (e: CreateCredentialException) {
val error = TwilioException(e.type, e.errorMessage.toString())
return CreatePasskeyResult.Error(error)
return CreatePasskeyResult.Error(e.toTwilioException())
}
}

Expand Down Expand Up @@ -132,8 +131,7 @@ actual class TwilioPasskeys internal constructor(
),
)
} catch (e: GetCredentialException) {
val error = TwilioException(e.type, e.errorMessage.toString())
return AuthenticatePasskeyResult.Error(error)
return AuthenticatePasskeyResult.Error(e.toTwilioException())
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.twilio.passkeys.extensions

import androidx.credentials.exceptions.CreateCredentialCancellationException
import androidx.credentials.exceptions.CreateCredentialCustomException
import androidx.credentials.exceptions.CreateCredentialException
import androidx.credentials.exceptions.CreateCredentialInterruptedException
import androidx.credentials.exceptions.CreateCredentialNoCreateOptionException
import androidx.credentials.exceptions.CreateCredentialProviderConfigurationException
import androidx.credentials.exceptions.CreateCredentialUnknownException
import androidx.credentials.exceptions.CreateCredentialUnsupportedException
import androidx.credentials.exceptions.GetCredentialCancellationException
import androidx.credentials.exceptions.GetCredentialCustomException
import androidx.credentials.exceptions.GetCredentialException
import androidx.credentials.exceptions.GetCredentialInterruptedException
import androidx.credentials.exceptions.GetCredentialProviderConfigurationException
import androidx.credentials.exceptions.GetCredentialUnknownException
import androidx.credentials.exceptions.GetCredentialUnsupportedException
import androidx.credentials.exceptions.NoCredentialException
import androidx.credentials.exceptions.publickeycredential.CreatePublicKeyCredentialDomException
import androidx.credentials.exceptions.publickeycredential.CreatePublicKeyCredentialException
import androidx.credentials.exceptions.publickeycredential.GetPublicKeyCredentialDomException
import androidx.credentials.exceptions.publickeycredential.GetPublicKeyCredentialException
import com.twilio.passkeys.exception.TwilioException

internal const val DOM_MESSAGE =
"""
DOM errors thrown according to the WebAuthn spec.
Ensure `/.well-known/assetlinks.json` is properly configured
as outlined here: https://developer.android.com/identity/sign-in/credential-manager#add-support-dal
"""

internal fun GetCredentialException.toTwilioException(): TwilioException {
return when (this) {
is GetCredentialCancellationException -> TwilioException.UserCanceledException(this)
is GetCredentialInterruptedException -> TwilioException.InterruptedException(this)
is GetCredentialUnsupportedException -> TwilioException.UnsupportedException(this)
is GetPublicKeyCredentialDomException -> TwilioException.DomException(DOM_MESSAGE.trimIndent(), this)
is NoCredentialException -> TwilioException.NoCredentialException(this)
is GetPublicKeyCredentialException,
is GetCredentialUnknownException,
is GetCredentialCustomException,
is GetCredentialProviderConfigurationException,
-> TwilioException.GeneralException(this)

else -> TwilioException.GeneralException(this)
}
}

internal fun CreateCredentialException.toTwilioException(): TwilioException {
return when (this) {
is CreateCredentialCancellationException -> TwilioException.UserCanceledException(this)
is CreateCredentialInterruptedException -> TwilioException.InterruptedException(this)
is CreateCredentialUnsupportedException -> TwilioException.UnsupportedException(this)
is CreatePublicKeyCredentialDomException -> TwilioException.DomException(DOM_MESSAGE.trimIndent(), this)
is CreateCredentialNoCreateOptionException,
is CreatePublicKeyCredentialException,
is CreateCredentialUnknownException,
is CreateCredentialCustomException,
is CreateCredentialProviderConfigurationException,
-> TwilioException.GeneralException(this)

else -> TwilioException.GeneralException(this)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ class AndroidTwilioPasskeyTest {
@Test
fun `Create passkey with json payload fails`() {
val exception = SerializationException("error")
val expectedException = TwilioException("error")
val expectedException = TwilioException.InvalidPayloadException(Throwable("error"))
every { passkeyPayloadMapper.mapToCreatePasskeyRequest(createPayload) } throws exception
every { passkeyPayloadMapper.mapException(exception) } returns expectedException
runTest {
Expand Down Expand Up @@ -331,7 +331,7 @@ class AndroidTwilioPasskeyTest {
@Test
fun `Authenticate passkey with json payload fails`() {
val exception = SerializationException("error")
val expectedException = TwilioException("error")
val expectedException = TwilioException.InvalidPayloadException(Throwable("error"))
every { passkeyPayloadMapper.mapToAuthenticatePasskeyRequest(authenticatePayload) } throws exception
every { passkeyPayloadMapper.mapException(exception) } returns expectedException

Expand Down
Loading

0 comments on commit 93a64f8

Please sign in to comment.