Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New passkey auth methods #22

Merged
merged 2 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ dependencies {
implementation "com.facebook.react:react-native:+"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"

implementation "id.passage.android:passage:1.6.1"
implementation "id.passage.android:passage:1.7.0"
implementation "com.google.code.gson:gson:2.9.0"
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.os.Build
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.Promise
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand All @@ -12,11 +13,13 @@ import com.google.gson.Gson
import id.passage.android.Passage
import id.passage.android.PassageSocialConnection
import id.passage.android.PassageToken
import id.passage.android.PasskeyCreationOptions
import id.passage.android.exceptions.AddDevicePasskeyCancellationException
import id.passage.android.exceptions.LoginWithPasskeyCancellationException
import id.passage.android.exceptions.OneTimePasscodeActivateExceededAttemptsException
import id.passage.android.exceptions.PassageUserUnauthorizedException
import id.passage.android.exceptions.RegisterWithPasskeyCancellationException
import id.passage.android.model.AuthenticatorAttachment

@Suppress("unused")
class PassageReactNativeModule(reactContext: ReactApplicationContext) :
Expand Down Expand Up @@ -45,10 +48,16 @@ class PassageReactNativeModule(reactContext: ReactApplicationContext) :
// region PASSKEY METHODS

@ReactMethod
fun registerWithPasskey(identifier: String, promise: Promise) {
fun registerWithPasskey(identifier: String, optionsMap: ReadableMap?, promise: Promise) {
CoroutineScope(Dispatchers.IO).launch {
try {
val authResult = passage.registerWithPasskey(identifier)
var options: PasskeyCreationOptions? = null
optionsMap?.getString("authenticatorAttachment")?.let {
AuthenticatorAttachment.decode(it)?.let {
options = PasskeyCreationOptions(it)
SinaSeylani marked this conversation as resolved.
Show resolved Hide resolved
}
}
val authResult = passage.registerWithPasskey(identifier, options)
val jsonString = Gson().toJson(authResult)
promise.resolve(jsonString)
} catch (e: Exception) {
Expand All @@ -64,10 +73,10 @@ class PassageReactNativeModule(reactContext: ReactApplicationContext) :
}

@ReactMethod
fun loginWithPasskey(promise: Promise) {
fun loginWithPasskey(identifier: String?, promise: Promise) {
CoroutineScope(Dispatchers.IO).launch {
try {
val authResult = passage.loginWithPasskey()
val authResult = passage.loginWithPasskey(identifier)
val jsonString = Gson().toJson(authResult)
promise.resolve(jsonString)
} catch (e: Exception) {
Expand Down
4 changes: 3 additions & 1 deletion ios/PassageReactNative.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ @interface RCT_EXTERN_MODULE(PassageReactNative, NSObject)

// MARK: - Passkey Methods
RCT_EXTERN_METHOD(registerWithPasskey:(NSString *)identifier
withOptionsDictionary:( nullable NSDictionary *)optionsDictionary
withResolver:(RCTPromiseResolveBlock)resolve
withRejecter:(RCTPromiseRejectBlock)reject);

RCT_EXTERN_METHOD(loginWithPasskey:(RCTPromiseResolveBlock)resolve
RCT_EXTERN_METHOD(loginWithPasskey:(nullable NSString *)identifier
withResolver:(RCTPromiseResolveBlock)resolve
withRejecter:(RCTPromiseRejectBlock)reject);


Expand Down
16 changes: 12 additions & 4 deletions ios/PassageReactNative.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ class PassageReactNative: NSObject {

// MARK: - Passkey Methods

@objc(registerWithPasskey:withResolver:withRejecter:)
@objc(registerWithPasskey:withOptionsDictionary:withResolver:withRejecter:)
func registerWithPasskey(
identifier: String,
optionsDictionary: NSDictionary?,
resolve: @escaping RCTPromiseResolveBlock,
reject: @escaping RCTPromiseRejectBlock
) -> Void {
Expand All @@ -36,7 +37,13 @@ class PassageReactNative: NSObject {
}
Task {
do {
let authResult = try await passage.registerWithPasskey(identifier: identifier)
var passkeyCreationOptions: PasskeyCreationOptions?
if let authenticatorAttachmentString = optionsDictionary?["authenticatorAttachment"] as? String,
let authenticatorAttachment = AuthenticatorAttachment(rawValue: authenticatorAttachmentString)
{
passkeyCreationOptions = PasskeyCreationOptions(authenticatorAttachment: authenticatorAttachment)
}
let authResult = try await passage.registerWithPasskey(identifier: identifier, options: passkeyCreationOptions)
resolve(authResult.toJsonString())
} catch PassageASAuthorizationError.canceled {
reject("USER_CANCELLED", "User cancelled interaction", nil)
Expand All @@ -46,8 +53,9 @@ class PassageReactNative: NSObject {
}
}

@objc(loginWithPasskey:withRejecter:)
@objc(loginWithPasskey:withResolver:withRejecter:)
func loginWithPasskey(
identifier: String?,
resolve: @escaping RCTPromiseResolveBlock,
reject: @escaping RCTPromiseRejectBlock
) -> Void {
Expand All @@ -57,7 +65,7 @@ class PassageReactNative: NSObject {
}
Task {
do {
let authResult = try await passage.loginWithPasskey()
let authResult = try await passage.loginWithPasskey(identifier: identifier)
resolve(authResult.toJsonString())
} catch PassageASAuthorizationError.canceled {
reject("USER_CANCELLED", "User cancelled interaction", nil)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@passageidentity/passage-react-native",
"version": "0.5.2",
"version": "0.6.0",
"description": "Native passkey authentication for your React Native app",
"main": "lib/commonjs/index",
"module": "lib/module/index",
Expand Down
2 changes: 1 addition & 1 deletion passage-react-native.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Pod::Spec.new do |s|

s.dependency "React-Core"

s.dependency 'Passage', '1.5.1'
s.dependency 'Passage', '1.6.0'
s.platform = :ios, '16.0'

# Don't install the dependencies when we run `pod install` in the old architecture.
Expand Down
34 changes: 28 additions & 6 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,16 @@
ttl_display_unit: DisplayUnit;
};

export enum AuthenticatorAttachment {
Platform = 'platform',

Check warning on line 136 in src/index.tsx

View workflow job for this annotation

GitHub Actions / lint

'Platform' is already declared in the upper scope on line 1 column 25
CrossPlatform = 'cross-platform',
Any = 'any',
}

export interface PasskeyCreationOptions {
authenticatorAttachment?: AuthenticatorAttachment;
}

export enum DisplayUnit {
Seconds = 's',
Minutes = 'm',
Expand All @@ -145,8 +155,11 @@
Google = 'google',
}

type RegisterWithPasskey = (identifier: string) => Promise<AuthResult>;
type LoginWithPasskey = () => Promise<AuthResult>;
type RegisterWithPasskey = (
identifier: string,
options?: PasskeyCreationOptions
) => Promise<AuthResult>;
type LoginWithPasskey = (identifier?: string | null) => Promise<AuthResult>;
type DeviceSupportsPasskeys = () => Promise<boolean>;
type AuthWithoutPasskey = (identifier: string) => Promise<string>;
type OTPActivate = (otp: string, otpId: string) => Promise<AuthResult>;
Expand Down Expand Up @@ -195,10 +208,14 @@
* @throws {PassageError} When user cancels operation, user already exists, app configuration was not done properly, etc.
*/
registerWithPasskey: RegisterWithPasskey = async (
identifier: string
identifier: string,
options?: PasskeyCreationOptions
): Promise<AuthResult> => {
try {
const result = await PassageReactNative.registerWithPasskey(identifier);
const result = await PassageReactNative.registerWithPasskey(
identifier,
options || null
);
const parsedResult = JSON.parse(result);
return parsedResult;
} catch (error: any) {
Expand All @@ -212,12 +229,17 @@
* NOTE: Both Android and iOS do NOT take a user identifier paramter when logging in with a passkey.
* The operating systems both show all of the passkeys available for the user and your application.
*
* @param {string | null} identifier email address / phone for user (optional)
* @return {Promise<AuthResult>} a data object that includes a redirect URL and saves the authorization token and (optional) refresh token securely to device.
* @throws {PassageError} When user cancels operation, user does not exist, app configuration was not done properly, etc.
*/
loginWithPasskey: LoginWithPasskey = async (): Promise<AuthResult> => {
loginWithPasskey: LoginWithPasskey = async (
identifier?: string | null
): Promise<AuthResult> => {
try {
const result = await PassageReactNative.loginWithPasskey();
const result = await PassageReactNative.loginWithPasskey(
identifier || null
);
const parsedResult = JSON.parse(result);
return parsedResult;
} catch (error: any) {
Expand Down
Loading