diff --git a/packages/clerk_flutter/.gitignore b/packages/clerk_flutter/.gitignore index ace65b6f..4119594a 100644 --- a/packages/clerk_flutter/.gitignore +++ b/packages/clerk_flutter/.gitignore @@ -18,3 +18,5 @@ build/ # Flutter auto-generated files .flutter-plugins .flutter-plugins-dependencies + +.env.* \ No newline at end of file diff --git a/packages/clerk_flutter/example/ios/Podfile.lock b/packages/clerk_flutter/example/ios/Podfile.lock index 5884e82c..985a1ee8 100644 --- a/packages/clerk_flutter/example/ios/Podfile.lock +++ b/packages/clerk_flutter/example/ios/Podfile.lock @@ -41,6 +41,8 @@ PODS: - GTMSessionFetcher/Core - image_picker_ios (0.0.1): - Flutter + - integration_test (0.0.1): + - Flutter - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS @@ -55,6 +57,7 @@ DEPENDENCIES: - Flutter (from `Flutter`) - google_sign_in_ios (from `.symlinks/plugins/google_sign_in_ios/darwin`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) + - integration_test (from `.symlinks/plugins/integration_test/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`) @@ -78,6 +81,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/google_sign_in_ios/darwin" image_picker_ios: :path: ".symlinks/plugins/image_picker_ios/ios" + integration_test: + :path: ".symlinks/plugins/integration_test/ios" path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/darwin" url_launcher_ios: @@ -96,6 +101,7 @@ SPEC CHECKSUMS: GTMAppAuth: 217a876b249c3c585a54fd6f73e6b58c4f5c4238 GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6 image_picker_ios: 85f2b3c9fb98c09d63725c4d12ebd585b56ec35d + integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e path_provider_foundation: 608fcb11be570ce83519b076ab6a1fffe2474f05 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 url_launcher_ios: 9d5365b30ff416ba8e3d917bae5f7fb25f6a4a89 diff --git a/packages/clerk_flutter/example/lib/main.dart b/packages/clerk_flutter/example/lib/main.dart index b3f93e08..f7335e2b 100644 --- a/packages/clerk_flutter/example/lib/main.dart +++ b/packages/clerk_flutter/example/lib/main.dart @@ -27,55 +27,55 @@ Future main() async { } runApp( - const ExampleApp( - publishableKey: publishableKey, + ExampleApp( + config: ClerkAuthConfig( + publishableKey: publishableKey, + redirectionGenerator: generateDeepLink, + deepLinkStream: AppLinks().allUriLinkStream.map(createClerkLink), + ), ), ); } -/// Example App -class ExampleApp extends StatelessWidget { - /// Constructs an instance of Example App - const ExampleApp({super.key, required this.publishableKey}); +/// This function maps a [Uri] into a [ClerkDeepLink], which is essentially +/// just a container for the [Uri]. The [ClerkDeepLink] can also +/// contain a [clerk.Strategy], to use in preference to a strategy +/// inferred from the [Uri] +ClerkDeepLink? createClerkLink(Uri uri) { + if (uri.pathSegments.first == 'auth') { + return ClerkDeepLink(uri: uri); + } - /// Publishable Key - final String publishableKey; + // If the host app deems the deep link to be not relevant to the Clerk SDK, + // we return [null] instead of a [ClerkDeepLink] to inhibit its processing. + return null; +} - /// This function maps a [Uri] into a [ClerkDeepLink], which is essentially - /// just a container for the [Uri]. The [ClerkDeepLink] can also - /// contain a [clerk.Strategy], to use in preference to a strategy - /// inferred from the [Uri] - ClerkDeepLink? createClerkLink(Uri uri) { - if (uri.pathSegments.first == 'auth') { - return ClerkDeepLink(uri: uri); - } +/// A function that returns an appropriate deep link [Uri] for the oauth +/// redirect for a given [clerk.Strategy], or [null] if redirection should +/// be handled in-app +Uri? generateDeepLink(BuildContext context, clerk.Strategy strategy) { + return Uri.parse('clerk://example.com/auth/$strategy'); - // If the host app deems the deep link to be not relevant to the Clerk SDK, - // we return [null] instead of a [ClerkDeepLink] to inhibit its processing. - return null; - } + // if you want to use the default in-app SSO, just remove the + // [redirectionGenerator] parameter from the [ClerkAuthConfig] object + // below, or... - /// A function that returns an appropriate deep link [Uri] for the oauth - /// redirect for a given [clerk.Strategy], or [null] if redirection should - /// be handled in-app - Uri? generateDeepLink(BuildContext context, clerk.Strategy strategy) { - return Uri.parse('clerk://example.com/auth/$strategy'); + // return null; +} - // if you want to use the default in-app SSO, just remove the - // [redirectionGenerator] parameter from the [ClerkAuthConfig] object - // below, or... +/// Example App +class ExampleApp extends StatelessWidget { + /// Constructs an instance of Example App + const ExampleApp({super.key, required this.config}); - // return null; - } + /// Publishable Key + final ClerkAuthConfig config; @override Widget build(BuildContext context) { return ClerkAuth( - config: ClerkAuthConfig( - publishableKey: publishableKey, - redirectionGenerator: generateDeepLink, - deepLinkStream: AppLinks().allUriLinkStream.map(createClerkLink), - ), + config: config, child: MaterialApp( theme: ThemeData.light(), debugShowCheckedModeBanner: false, diff --git a/packages/clerk_flutter/example/pubspec.yaml b/packages/clerk_flutter/example/pubspec.yaml index 969fbd5a..34eebd10 100644 --- a/packages/clerk_flutter/example/pubspec.yaml +++ b/packages/clerk_flutter/example/pubspec.yaml @@ -22,6 +22,11 @@ dev_dependencies: flutter_lints: ^3.0.1 flutter_test: sdk: flutter + dart_dotenv: ^1.0.1 + test: ^1.25.5 + http: ^1.1.0 + integration_test: + sdk: flutter flutter: uses-material-design: true diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/001.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/001.json new file mode 100644 index 00000000..31f21971 --- /dev/null +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/001.json @@ -0,0 +1 @@ +{"key":"POST /v1/client","body":"{\"response\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[],\"sign_in\":null,\"sign_up\":null,\"last_active_session_id\":null,\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -2%%\",\"updated_at\":\"%%DATETIME -1%%\"},\"client\":null}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/002.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/002.json new file mode 100644 index 00000000..b910570a --- /dev/null +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/002.json @@ -0,0 +1 @@ +{"key":"GET /v1/environment","body":"{\"auth_config\":{\"object\":\"auth_config\",\"id\":\"AUTH_CONFIG_ID\",\"first_name\":\"%%FIRSTNAME%%\",\"last_name\":\"%%LASTNAME%%\",\"email_address\":\"%%EMAIL%%\",\"phone_number\":\"on\",\"username\":\"on\",\"password\":\"required\",\"identification_requirements\":[[\"email_address\",\"oauth_apple\",\"oauth_facebook\",\"oauth_github\",\"oauth_google\",\"oauth_linkedin_oidc\",\"oauth_token_apple\",\"phone_number\"],[\"username\"]],\"identification_strategies\":[\"email_address\",\"oauth_apple\",\"oauth_facebook\",\"oauth_github\",\"oauth_google\",\"oauth_linkedin_oidc\",\"passkey\",\"password\",\"phone_number\",\"username\"],\"first_factors\":[\"email_code\",\"email_link\",\"enterprise_sso\",\"google_one_tap\",\"oauth_apple\",\"oauth_facebook\",\"oauth_github\",\"oauth_google\",\"oauth_linkedin_oidc\",\"oauth_token_apple\",\"passkey\",\"password\",\"phone_code\",\"reset_password_email_code\",\"reset_password_phone_code\",\"saml\",\"ticket\"],\"second_factors\":[\"phone_code\",\"totp\"],\"email_address_verification_strategies\":[\"email_code\",\"email_link\"],\"single_session_mode\":false,\"enhanced_email_deliverability\":false,\"test_mode\":true,\"cookieless_dev\":true,\"url_based_session_syncing\":true,\"claimed_at\":null,\"reverification\":false},\"display_config\":{\"object\":\"display_config\",\"id\":\"DISPLAY_CONFIG_ID\",\"instance_environment_type\":\"development\",\"application_name\":\"APPLICATION_NAME\",\"theme\":{\"buttons\":{\"font_color\":\"#ffffff\",\"font_family\":\"\\\"Source Sans Pro\\\", sans-serif\",\"font_weight\":\"600\"},\"general\":{\"color\":\"#6c47ff\",\"padding\":\"1em\",\"box_shadow\":\"0 2px 8px rgba(0, 0, 0, 0.2)\",\"font_color\":\"#151515\",\"font_family\":\"\\\"Source Sans Pro\\\", sans-serif\",\"border_radius\":\"0.5em\",\"background_color\":\"#ffffff\",\"label_font_weight\":\"600\"},\"accounts\":{\"background_color\":\"#ffffff\"}},\"preferred_sign_in_strategy\":\"password\",\"logo_image_url\":\"URL\",\"favicon_image_url\":\"\",\"home_url\":\"URL\",\"sign_in_url\":\"URL\",\"sign_up_url\":\"URL\",\"user_profile_url\":\"URL\",\"waitlist_url\":\"URL\",\"after_sign_in_url\":\"URL\",\"after_sign_up_url\":\"URL\",\"after_sign_out_one_url\":\"URL\",\"after_sign_out_all_url\":\"URL\",\"after_switch_session_url\":\"URL\",\"after_join_waitlist_url\":\"URL\",\"organization_profile_url\":\"URL\",\"create_organization_url\":\"URL\",\"after_leave_organization_url\":\"URL\",\"after_create_organization_url\":\"URL\",\"logo_link_url\":\"URL\",\"support_email\":null,\"branded\":true,\"experimental_force_oauth_first\":false,\"clerk_js_version\":\"5\",\"show_devmode_warning\":true,\"google_one_tap_client_id\":\"GOOGLE_ONE_TAP_CLIENT_ID\",\"help_url\":null,\"privacy_policy_url\":\"URL\",\"terms_url\":\"URL\",\"logo_url\":\"URL\",\"favicon_url\":null,\"logo_image\":{\"object\":\"image\",\"id\":\"IMAGE_ID\",\"public_url\":\"URL\"},\"favicon_image\":null,\"captcha_public_key\":null,\"captcha_widget_type\":null,\"captcha_public_key_invisible\":null,\"captcha_provider\":null,\"captcha_oauth_bypass\":[]},\"user_settings\":{\"attributes\":{\"email_address\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":true,\"first_factors\":[\"email_code\",\"email_link\"],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[\"email_code\",\"email_link\"],\"verify_at_sign_up\":true},\"phone_number\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":true,\"first_factors\":[\"phone_code\"],\"used_for_second_factor\":true,\"second_factors\":[\"phone_code\"],\"verifications\":[\"phone_code\"],\"verify_at_sign_up\":true},\"username\":{\"enabled\":true,\"required\":false,\"used_for_first_factor\":true,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"web3_wallet\":{\"enabled\":false,\"required\":false,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"first_name\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"last_name\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"password\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":true,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"authenticator_app\":{\"enabled\":true,\"required\":false,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":true,\"second_factors\":[\"totp\"],\"verifications\":[\"totp\"],\"verify_at_sign_up\":false},\"ticket\":{\"enabled\":true,\"required\":false,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"backup_code\":{\"enabled\":false,\"required\":false,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"passkey\":{\"enabled\":true,\"required\":false,\"used_for_first_factor\":true,\"first_factors\":[\"passkey\"],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[\"passkey\"],\"verify_at_sign_up\":false}},\"sign_in\":{\"second_factor\":{\"required\":false}},\"sign_up\":{\"captcha_enabled\":false,\"captcha_widget_type\":\"smart\",\"custom_action_required\":false,\"progressive\":true,\"mode\":\"public\",\"legal_consent_enabled\":true},\"restrictions\":{\"allowlist\":{\"enabled\":false},\"blocklist\":{\"enabled\":false},\"allowlist_blocklist_disabled_on_sign_in\":{\"enabled\":false},\"block_email_subaddresses\":{\"enabled\":false},\"block_disposable_email_domains\":{\"enabled\":false}},\"username_settings\":{\"min_length\":4,\"max_length\":64,\"allow_extended_special_characters\":false},\"actions\":{\"delete_self\":true,\"create_organization\":true,\"create_organizations_limit\":null},\"attack_protection\":{\"user_lockout\":{\"enabled\":true,\"max_attempts\":100,\"duration_in_minutes\":60},\"pii\":{\"enabled\":true},\"email_link\":{\"require_same_client\":false},\"enumeration_protection\":{\"enabled\":false}},\"passkey_settings\":{\"allow_autofill\":true,\"show_sign_in_button\":true},\"social\":{\"oauth_apple\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_apple\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"Apple\",\"logo_url\":\"URL\"},\"oauth_custom_spung\":{\"enabled\":false,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_custom_spung\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"spung\"},\"oauth_facebook\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_facebook\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"Facebook\",\"logo_url\":\"URL\"},\"oauth_github\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_github\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"GitHub\",\"logo_url\":\"URL\"},\"oauth_google\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":true,\"strategy\":\"oauth_google\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"Google\",\"logo_url\":\"URL\"},\"oauth_linkedin_oidc\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_linkedin_oidc\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"LinkedIn\",\"logo_url\":\"URL\"}},\"password_settings\":{\"disable_hibp\":false,\"min_length\":8,\"max_length\":0,\"require_special_char\":true,\"require_numbers\":true,\"require_uppercase\":true,\"require_lowercase\":true,\"show_zxcvbn\":false,\"min_zxcvbn_strength\":0,\"enforce_hibp_on_sign_in\":false,\"allowed_special_characters\":\"+$-_\"}},\"commerce_settings\":{\"billing\":{\"enabled\":false,\"has_paid_user_plans\":false,\"has_paid_org_plans\":false,\"free_trial_requires_payment_method\":true,\"user\":{\"enabled\":false,\"has_paid_plans\":false},\"organization\":{\"enabled\":false,\"has_paid_plans\":false}}},\"api_keys_settings\":{\"enabled\":false},\"maintenance_mode\":false,\"client_debug_mode\":false}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/003.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/003.json new file mode 100644 index 00000000..d00b0874 --- /dev/null +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/003.json @@ -0,0 +1 @@ +{"key":"POST /v1/client/sign_ins identifier","body":"{\"response\":{\"object\":\"sign_in_attempt\",\"id\":\"SIGN_IN_ID\",\"status\":\"needs_first_factor\",\"supported_identifiers\":[\"email_address\",\"passkey\",\"password\",\"phone_number\",\"username\"],\"supported_first_factors\":[{\"strategy\":\"password\"},{\"strategy\":\"phone_code\",\"safe_identifier\":\"%%PHONE%%\",\"phone_number_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"email_code\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"email_link\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"reset_password_email_code\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true}],\"supported_second_factors\":null,\"first_factor_verification\":null,\"second_factor_verification\":null,\"identifier\":\"%%EMAIL%%\",\"user_data\":null,\"created_session_id\":null,\"abandon_at\":\"%%DATETIME 1%%\",\"locale\":null},\"client\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[],\"sign_in\":{\"object\":\"sign_in_attempt\",\"id\":\"SIGN_IN_ID\",\"status\":\"needs_first_factor\",\"supported_identifiers\":[\"email_address\",\"passkey\",\"password\",\"phone_number\",\"username\"],\"supported_first_factors\":[{\"strategy\":\"password\"},{\"strategy\":\"phone_code\",\"safe_identifier\":\"%%PHONE%%\",\"phone_number_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"email_code\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"email_link\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"reset_password_email_code\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true}],\"supported_second_factors\":null,\"first_factor_verification\":null,\"second_factor_verification\":null,\"identifier\":\"%%EMAIL%%\",\"user_data\":null,\"created_session_id\":null,\"abandon_at\":\"%%DATETIME 1%%\",\"locale\":null},\"sign_up\":null,\"last_active_session_id\":null,\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -2%%\",\"updated_at\":\"%%DATETIME -1%%\"}}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/004.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/004.json new file mode 100644 index 00000000..50a94a8e --- /dev/null +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/004.json @@ -0,0 +1 @@ +{"key":"POST /v1/client/sign_ins/SIGN_IN_ID/attempt_first_factor password&strategy=password","body":"{\"response\":{\"object\":\"sign_in_attempt\",\"id\":\"SIGN_IN_ID\",\"status\":\"complete\",\"supported_identifiers\":[\"email_address\",\"passkey\",\"password\",\"phone_number\",\"username\"],\"supported_first_factors\":null,\"supported_second_factors\":null,\"first_factor_verification\":{\"object\":\"verification_password\",\"status\":\"verified\",\"strategy\":\"password\",\"attempts\":1,\"expire_at\":null},\"second_factor_verification\":null,\"identifier\":\"%%EMAIL%%\",\"user_data\":null,\"created_session_id\":\"SESSION_ID\",\"abandon_at\":\"%%DATETIME 1%%\",\"locale\":null},\"client\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[{\"object\":\"session\",\"id\":\"SESSION_ID\",\"status\":\"active\",\"expire_at\":\"%%DATETIME 2%%\",\"abandon_at\":\"%%DATETIME 3%%\",\"last_active_at\":\"%%DATETIME -4%%\",\"last_active_organization_id\":null,\"actor\":null,\"user\":{\"id\":\"USER_ID\",\"object\":\"user\",\"username\":\"userfortests\",\"first_name\":\"%%FIRSTNAME%%\",\"last_name\":\"%%LASTNAME%%\",\"locale\":null,\"image_url\":\"URL\",\"has_image\":false,\"primary_email_address_id\":\"IDENTIFIER_ID\",\"primary_phone_number_id\":\"IDENTIFIER_ID\",\"primary_web3_wallet_id\":null,\"password_enabled\":true,\"two_factor_enabled\":false,\"totp_enabled\":false,\"backup_code_enabled\":false,\"email_addresses\":[{\"id\":\"IDENTIFIER_ID\",\"object\":\"email_address\",\"email_address\":\"%%EMAIL%%\",\"reserved\":false,\"verification\":{\"object\":\"verification_admin\",\"status\":\"verified\",\"strategy\":\"admin\",\"attempts\":null,\"expire_at\":null},\"linked_to\":[],\"matches_sso_connection\":false,\"created_at\":\"%%DATETIME -8%%\",\"updated_at\":\"%%DATETIME -8%%\"}],\"phone_numbers\":[{\"id\":\"IDENTIFIER_ID\",\"object\":\"phone_number\",\"phone_number\":\"+15555550105\",\"reserved_for_second_factor\":false,\"default_second_factor\":false,\"reserved\":false,\"verification\":{\"object\":\"verification_admin\",\"status\":\"verified\",\"strategy\":\"admin\",\"attempts\":null,\"expire_at\":null},\"linked_to\":[],\"backup_codes\":null,\"created_at\":\"%%DATETIME -7%%\",\"updated_at\":\"%%DATETIME -7%%\"}],\"web3_wallets\":[],\"passkeys\":[],\"external_accounts\":[],\"saml_accounts\":[],\"enterprise_accounts\":[],\"public_metadata\":{},\"unsafe_metadata\":{},\"external_id\":null,\"last_sign_in_at\":\"%%DATETIME -4%%\",\"banned\":false,\"locked\":false,\"lockout_expires_in_seconds\":null,\"verification_attempts_remaining\":100,\"created_at\":\"%%DATETIME -9%%\",\"updated_at\":\"%%DATETIME -3%%\",\"delete_self_enabled\":true,\"create_organization_enabled\":true,\"last_active_at\":\"%%DATETIME -6%%\",\"mfa_enabled_at\":null,\"mfa_disabled_at\":null,\"legal_accepted_at\":\"%%DATETIME -10%%\",\"profile_image_url\":\"URL\",\"organization_memberships\":[]},\"public_user_data\":{\"first_name\":\"%%FIRSTNAME%%\",\"last_name\":\"%%LASTNAME%%\",\"image_url\":\"URL\",\"has_image\":false,\"identifier\":\"%%EMAIL%%\",\"username\":\"userfortests\",\"profile_image_url\":\"URL\"},\"factor_verification_age\":[0,-1],\"created_at\":\"%%DATETIME -4%%\",\"updated_at\":\"%%DATETIME -1%%\",\"last_active_token\":{\"object\":\"token\",\"jwt\":\"e30=.e30=.e30=\"}}],\"sign_in\":null,\"sign_up\":null,\"last_active_session_id\":\"SESSION_ID\",\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -5%%\",\"updated_at\":\"%%DATETIME -2%%\"}}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/005.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/005.json new file mode 100644 index 00000000..6e1ebe1a --- /dev/null +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/005.json @@ -0,0 +1 @@ +{"key":"DELETE /v1/client","body":"{\"response\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[],\"sign_in\":null,\"sign_up\":null,\"last_active_session_id\":null,\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -2%%\",\"updated_at\":\"%%DATETIME -1%%\"},\"client\":null}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/001.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/001.json new file mode 100644 index 00000000..31f21971 --- /dev/null +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/001.json @@ -0,0 +1 @@ +{"key":"POST /v1/client","body":"{\"response\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[],\"sign_in\":null,\"sign_up\":null,\"last_active_session_id\":null,\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -2%%\",\"updated_at\":\"%%DATETIME -1%%\"},\"client\":null}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/002.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/002.json new file mode 100644 index 00000000..b910570a --- /dev/null +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/002.json @@ -0,0 +1 @@ +{"key":"GET /v1/environment","body":"{\"auth_config\":{\"object\":\"auth_config\",\"id\":\"AUTH_CONFIG_ID\",\"first_name\":\"%%FIRSTNAME%%\",\"last_name\":\"%%LASTNAME%%\",\"email_address\":\"%%EMAIL%%\",\"phone_number\":\"on\",\"username\":\"on\",\"password\":\"required\",\"identification_requirements\":[[\"email_address\",\"oauth_apple\",\"oauth_facebook\",\"oauth_github\",\"oauth_google\",\"oauth_linkedin_oidc\",\"oauth_token_apple\",\"phone_number\"],[\"username\"]],\"identification_strategies\":[\"email_address\",\"oauth_apple\",\"oauth_facebook\",\"oauth_github\",\"oauth_google\",\"oauth_linkedin_oidc\",\"passkey\",\"password\",\"phone_number\",\"username\"],\"first_factors\":[\"email_code\",\"email_link\",\"enterprise_sso\",\"google_one_tap\",\"oauth_apple\",\"oauth_facebook\",\"oauth_github\",\"oauth_google\",\"oauth_linkedin_oidc\",\"oauth_token_apple\",\"passkey\",\"password\",\"phone_code\",\"reset_password_email_code\",\"reset_password_phone_code\",\"saml\",\"ticket\"],\"second_factors\":[\"phone_code\",\"totp\"],\"email_address_verification_strategies\":[\"email_code\",\"email_link\"],\"single_session_mode\":false,\"enhanced_email_deliverability\":false,\"test_mode\":true,\"cookieless_dev\":true,\"url_based_session_syncing\":true,\"claimed_at\":null,\"reverification\":false},\"display_config\":{\"object\":\"display_config\",\"id\":\"DISPLAY_CONFIG_ID\",\"instance_environment_type\":\"development\",\"application_name\":\"APPLICATION_NAME\",\"theme\":{\"buttons\":{\"font_color\":\"#ffffff\",\"font_family\":\"\\\"Source Sans Pro\\\", sans-serif\",\"font_weight\":\"600\"},\"general\":{\"color\":\"#6c47ff\",\"padding\":\"1em\",\"box_shadow\":\"0 2px 8px rgba(0, 0, 0, 0.2)\",\"font_color\":\"#151515\",\"font_family\":\"\\\"Source Sans Pro\\\", sans-serif\",\"border_radius\":\"0.5em\",\"background_color\":\"#ffffff\",\"label_font_weight\":\"600\"},\"accounts\":{\"background_color\":\"#ffffff\"}},\"preferred_sign_in_strategy\":\"password\",\"logo_image_url\":\"URL\",\"favicon_image_url\":\"\",\"home_url\":\"URL\",\"sign_in_url\":\"URL\",\"sign_up_url\":\"URL\",\"user_profile_url\":\"URL\",\"waitlist_url\":\"URL\",\"after_sign_in_url\":\"URL\",\"after_sign_up_url\":\"URL\",\"after_sign_out_one_url\":\"URL\",\"after_sign_out_all_url\":\"URL\",\"after_switch_session_url\":\"URL\",\"after_join_waitlist_url\":\"URL\",\"organization_profile_url\":\"URL\",\"create_organization_url\":\"URL\",\"after_leave_organization_url\":\"URL\",\"after_create_organization_url\":\"URL\",\"logo_link_url\":\"URL\",\"support_email\":null,\"branded\":true,\"experimental_force_oauth_first\":false,\"clerk_js_version\":\"5\",\"show_devmode_warning\":true,\"google_one_tap_client_id\":\"GOOGLE_ONE_TAP_CLIENT_ID\",\"help_url\":null,\"privacy_policy_url\":\"URL\",\"terms_url\":\"URL\",\"logo_url\":\"URL\",\"favicon_url\":null,\"logo_image\":{\"object\":\"image\",\"id\":\"IMAGE_ID\",\"public_url\":\"URL\"},\"favicon_image\":null,\"captcha_public_key\":null,\"captcha_widget_type\":null,\"captcha_public_key_invisible\":null,\"captcha_provider\":null,\"captcha_oauth_bypass\":[]},\"user_settings\":{\"attributes\":{\"email_address\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":true,\"first_factors\":[\"email_code\",\"email_link\"],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[\"email_code\",\"email_link\"],\"verify_at_sign_up\":true},\"phone_number\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":true,\"first_factors\":[\"phone_code\"],\"used_for_second_factor\":true,\"second_factors\":[\"phone_code\"],\"verifications\":[\"phone_code\"],\"verify_at_sign_up\":true},\"username\":{\"enabled\":true,\"required\":false,\"used_for_first_factor\":true,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"web3_wallet\":{\"enabled\":false,\"required\":false,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"first_name\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"last_name\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"password\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":true,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"authenticator_app\":{\"enabled\":true,\"required\":false,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":true,\"second_factors\":[\"totp\"],\"verifications\":[\"totp\"],\"verify_at_sign_up\":false},\"ticket\":{\"enabled\":true,\"required\":false,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"backup_code\":{\"enabled\":false,\"required\":false,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"passkey\":{\"enabled\":true,\"required\":false,\"used_for_first_factor\":true,\"first_factors\":[\"passkey\"],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[\"passkey\"],\"verify_at_sign_up\":false}},\"sign_in\":{\"second_factor\":{\"required\":false}},\"sign_up\":{\"captcha_enabled\":false,\"captcha_widget_type\":\"smart\",\"custom_action_required\":false,\"progressive\":true,\"mode\":\"public\",\"legal_consent_enabled\":true},\"restrictions\":{\"allowlist\":{\"enabled\":false},\"blocklist\":{\"enabled\":false},\"allowlist_blocklist_disabled_on_sign_in\":{\"enabled\":false},\"block_email_subaddresses\":{\"enabled\":false},\"block_disposable_email_domains\":{\"enabled\":false}},\"username_settings\":{\"min_length\":4,\"max_length\":64,\"allow_extended_special_characters\":false},\"actions\":{\"delete_self\":true,\"create_organization\":true,\"create_organizations_limit\":null},\"attack_protection\":{\"user_lockout\":{\"enabled\":true,\"max_attempts\":100,\"duration_in_minutes\":60},\"pii\":{\"enabled\":true},\"email_link\":{\"require_same_client\":false},\"enumeration_protection\":{\"enabled\":false}},\"passkey_settings\":{\"allow_autofill\":true,\"show_sign_in_button\":true},\"social\":{\"oauth_apple\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_apple\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"Apple\",\"logo_url\":\"URL\"},\"oauth_custom_spung\":{\"enabled\":false,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_custom_spung\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"spung\"},\"oauth_facebook\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_facebook\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"Facebook\",\"logo_url\":\"URL\"},\"oauth_github\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_github\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"GitHub\",\"logo_url\":\"URL\"},\"oauth_google\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":true,\"strategy\":\"oauth_google\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"Google\",\"logo_url\":\"URL\"},\"oauth_linkedin_oidc\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_linkedin_oidc\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"LinkedIn\",\"logo_url\":\"URL\"}},\"password_settings\":{\"disable_hibp\":false,\"min_length\":8,\"max_length\":0,\"require_special_char\":true,\"require_numbers\":true,\"require_uppercase\":true,\"require_lowercase\":true,\"show_zxcvbn\":false,\"min_zxcvbn_strength\":0,\"enforce_hibp_on_sign_in\":false,\"allowed_special_characters\":\"+$-_\"}},\"commerce_settings\":{\"billing\":{\"enabled\":false,\"has_paid_user_plans\":false,\"has_paid_org_plans\":false,\"free_trial_requires_payment_method\":true,\"user\":{\"enabled\":false,\"has_paid_plans\":false},\"organization\":{\"enabled\":false,\"has_paid_plans\":false}}},\"api_keys_settings\":{\"enabled\":false},\"maintenance_mode\":false,\"client_debug_mode\":false}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/003.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/003.json new file mode 100644 index 00000000..ee371264 --- /dev/null +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/003.json @@ -0,0 +1 @@ +{"key":"POST /v1/client/sign_ins identifier","body":"{\"response\":{\"object\":\"sign_in_attempt\",\"id\":\"SIGN_IN_ID\",\"status\":\"needs_first_factor\",\"supported_identifiers\":[\"email_address\",\"passkey\",\"password\",\"phone_number\",\"username\"],\"supported_first_factors\":[{\"strategy\":\"password\"},{\"strategy\":\"phone_code\",\"safe_identifier\":\"%%PHONE%%\",\"phone_number_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"email_code\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"email_link\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"reset_password_phone_code\",\"safe_identifier\":\"%%PHONE%%\",\"phone_number_id\":\"IDENTIFIER_ID\",\"primary\":true}],\"supported_second_factors\":null,\"first_factor_verification\":null,\"second_factor_verification\":null,\"identifier\":\"%%PHONE%%\",\"user_data\":null,\"created_session_id\":null,\"abandon_at\":\"%%DATETIME 1%%\",\"locale\":null},\"client\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[],\"sign_in\":{\"object\":\"sign_in_attempt\",\"id\":\"SIGN_IN_ID\",\"status\":\"needs_first_factor\",\"supported_identifiers\":[\"email_address\",\"passkey\",\"password\",\"phone_number\",\"username\"],\"supported_first_factors\":[{\"strategy\":\"password\"},{\"strategy\":\"phone_code\",\"safe_identifier\":\"%%PHONE%%\",\"phone_number_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"email_code\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"email_link\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"reset_password_phone_code\",\"safe_identifier\":\"%%PHONE%%\",\"phone_number_id\":\"IDENTIFIER_ID\",\"primary\":true}],\"supported_second_factors\":null,\"first_factor_verification\":null,\"second_factor_verification\":null,\"identifier\":\"%%PHONE%%\",\"user_data\":null,\"created_session_id\":null,\"abandon_at\":\"%%DATETIME 1%%\",\"locale\":null},\"sign_up\":null,\"last_active_session_id\":null,\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -2%%\",\"updated_at\":\"%%DATETIME -1%%\"}}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/004.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/004.json new file mode 100644 index 00000000..edf043d4 --- /dev/null +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/004.json @@ -0,0 +1 @@ +{"key":"POST /v1/client/sign_ins/SIGN_IN_ID/attempt_first_factor password&strategy=password","body":"{\"response\":{\"object\":\"sign_in_attempt\",\"id\":\"SIGN_IN_ID\",\"status\":\"complete\",\"supported_identifiers\":[\"email_address\",\"passkey\",\"password\",\"phone_number\",\"username\"],\"supported_first_factors\":null,\"supported_second_factors\":null,\"first_factor_verification\":{\"object\":\"verification_password\",\"status\":\"verified\",\"strategy\":\"password\",\"attempts\":1,\"expire_at\":null},\"second_factor_verification\":null,\"identifier\":\"%%PHONE%%\",\"user_data\":null,\"created_session_id\":\"SESSION_ID\",\"abandon_at\":\"%%DATETIME 1%%\",\"locale\":null},\"client\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[{\"object\":\"session\",\"id\":\"SESSION_ID\",\"status\":\"active\",\"expire_at\":\"%%DATETIME 2%%\",\"abandon_at\":\"%%DATETIME 3%%\",\"last_active_at\":\"%%DATETIME -4%%\",\"last_active_organization_id\":null,\"actor\":null,\"user\":{\"id\":\"USER_ID\",\"object\":\"user\",\"username\":\"userfortests\",\"first_name\":\"%%FIRSTNAME%%\",\"last_name\":\"%%LASTNAME%%\",\"locale\":null,\"image_url\":\"URL\",\"has_image\":false,\"primary_email_address_id\":\"IDENTIFIER_ID\",\"primary_phone_number_id\":\"IDENTIFIER_ID\",\"primary_web3_wallet_id\":null,\"password_enabled\":true,\"two_factor_enabled\":false,\"totp_enabled\":false,\"backup_code_enabled\":false,\"email_addresses\":[{\"id\":\"IDENTIFIER_ID\",\"object\":\"email_address\",\"email_address\":\"%%EMAIL%%\",\"reserved\":false,\"verification\":{\"object\":\"verification_admin\",\"status\":\"verified\",\"strategy\":\"admin\",\"attempts\":null,\"expire_at\":null},\"linked_to\":[],\"matches_sso_connection\":false,\"created_at\":\"%%DATETIME -8%%\",\"updated_at\":\"%%DATETIME -8%%\"}],\"phone_numbers\":[{\"id\":\"IDENTIFIER_ID\",\"object\":\"phone_number\",\"phone_number\":\"+15555550105\",\"reserved_for_second_factor\":false,\"default_second_factor\":false,\"reserved\":false,\"verification\":{\"object\":\"verification_admin\",\"status\":\"verified\",\"strategy\":\"admin\",\"attempts\":null,\"expire_at\":null},\"linked_to\":[],\"backup_codes\":null,\"created_at\":\"%%DATETIME -7%%\",\"updated_at\":\"%%DATETIME -7%%\"}],\"web3_wallets\":[],\"passkeys\":[],\"external_accounts\":[],\"saml_accounts\":[],\"enterprise_accounts\":[],\"public_metadata\":{},\"unsafe_metadata\":{},\"external_id\":null,\"last_sign_in_at\":\"%%DATETIME -4%%\",\"banned\":false,\"locked\":false,\"lockout_expires_in_seconds\":null,\"verification_attempts_remaining\":100,\"created_at\":\"%%DATETIME -9%%\",\"updated_at\":\"%%DATETIME -3%%\",\"delete_self_enabled\":true,\"create_organization_enabled\":true,\"last_active_at\":\"%%DATETIME -6%%\",\"mfa_enabled_at\":null,\"mfa_disabled_at\":null,\"legal_accepted_at\":\"%%DATETIME -10%%\",\"profile_image_url\":\"URL\",\"organization_memberships\":[]},\"public_user_data\":{\"first_name\":\"%%FIRSTNAME%%\",\"last_name\":\"%%LASTNAME%%\",\"image_url\":\"URL\",\"has_image\":false,\"identifier\":\"%%PHONE%%\",\"username\":\"userfortests\",\"profile_image_url\":\"URL\"},\"factor_verification_age\":[0,-1],\"created_at\":\"%%DATETIME -4%%\",\"updated_at\":\"%%DATETIME -1%%\",\"last_active_token\":{\"object\":\"token\",\"jwt\":\"e30=.e30=.e30=\"}}],\"sign_in\":null,\"sign_up\":null,\"last_active_session_id\":\"SESSION_ID\",\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -5%%\",\"updated_at\":\"%%DATETIME -2%%\"}}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/005.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/005.json new file mode 100644 index 00000000..6e1ebe1a --- /dev/null +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/005.json @@ -0,0 +1 @@ +{"key":"DELETE /v1/client","body":"{\"response\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[],\"sign_in\":null,\"sign_up\":null,\"last_active_session_id\":null,\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -2%%\",\"updated_at\":\"%%DATETIME -1%%\"},\"client\":null}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/001.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/001.json new file mode 100644 index 00000000..31f21971 --- /dev/null +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/001.json @@ -0,0 +1 @@ +{"key":"POST /v1/client","body":"{\"response\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[],\"sign_in\":null,\"sign_up\":null,\"last_active_session_id\":null,\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -2%%\",\"updated_at\":\"%%DATETIME -1%%\"},\"client\":null}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/002.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/002.json new file mode 100644 index 00000000..b910570a --- /dev/null +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/002.json @@ -0,0 +1 @@ +{"key":"GET /v1/environment","body":"{\"auth_config\":{\"object\":\"auth_config\",\"id\":\"AUTH_CONFIG_ID\",\"first_name\":\"%%FIRSTNAME%%\",\"last_name\":\"%%LASTNAME%%\",\"email_address\":\"%%EMAIL%%\",\"phone_number\":\"on\",\"username\":\"on\",\"password\":\"required\",\"identification_requirements\":[[\"email_address\",\"oauth_apple\",\"oauth_facebook\",\"oauth_github\",\"oauth_google\",\"oauth_linkedin_oidc\",\"oauth_token_apple\",\"phone_number\"],[\"username\"]],\"identification_strategies\":[\"email_address\",\"oauth_apple\",\"oauth_facebook\",\"oauth_github\",\"oauth_google\",\"oauth_linkedin_oidc\",\"passkey\",\"password\",\"phone_number\",\"username\"],\"first_factors\":[\"email_code\",\"email_link\",\"enterprise_sso\",\"google_one_tap\",\"oauth_apple\",\"oauth_facebook\",\"oauth_github\",\"oauth_google\",\"oauth_linkedin_oidc\",\"oauth_token_apple\",\"passkey\",\"password\",\"phone_code\",\"reset_password_email_code\",\"reset_password_phone_code\",\"saml\",\"ticket\"],\"second_factors\":[\"phone_code\",\"totp\"],\"email_address_verification_strategies\":[\"email_code\",\"email_link\"],\"single_session_mode\":false,\"enhanced_email_deliverability\":false,\"test_mode\":true,\"cookieless_dev\":true,\"url_based_session_syncing\":true,\"claimed_at\":null,\"reverification\":false},\"display_config\":{\"object\":\"display_config\",\"id\":\"DISPLAY_CONFIG_ID\",\"instance_environment_type\":\"development\",\"application_name\":\"APPLICATION_NAME\",\"theme\":{\"buttons\":{\"font_color\":\"#ffffff\",\"font_family\":\"\\\"Source Sans Pro\\\", sans-serif\",\"font_weight\":\"600\"},\"general\":{\"color\":\"#6c47ff\",\"padding\":\"1em\",\"box_shadow\":\"0 2px 8px rgba(0, 0, 0, 0.2)\",\"font_color\":\"#151515\",\"font_family\":\"\\\"Source Sans Pro\\\", sans-serif\",\"border_radius\":\"0.5em\",\"background_color\":\"#ffffff\",\"label_font_weight\":\"600\"},\"accounts\":{\"background_color\":\"#ffffff\"}},\"preferred_sign_in_strategy\":\"password\",\"logo_image_url\":\"URL\",\"favicon_image_url\":\"\",\"home_url\":\"URL\",\"sign_in_url\":\"URL\",\"sign_up_url\":\"URL\",\"user_profile_url\":\"URL\",\"waitlist_url\":\"URL\",\"after_sign_in_url\":\"URL\",\"after_sign_up_url\":\"URL\",\"after_sign_out_one_url\":\"URL\",\"after_sign_out_all_url\":\"URL\",\"after_switch_session_url\":\"URL\",\"after_join_waitlist_url\":\"URL\",\"organization_profile_url\":\"URL\",\"create_organization_url\":\"URL\",\"after_leave_organization_url\":\"URL\",\"after_create_organization_url\":\"URL\",\"logo_link_url\":\"URL\",\"support_email\":null,\"branded\":true,\"experimental_force_oauth_first\":false,\"clerk_js_version\":\"5\",\"show_devmode_warning\":true,\"google_one_tap_client_id\":\"GOOGLE_ONE_TAP_CLIENT_ID\",\"help_url\":null,\"privacy_policy_url\":\"URL\",\"terms_url\":\"URL\",\"logo_url\":\"URL\",\"favicon_url\":null,\"logo_image\":{\"object\":\"image\",\"id\":\"IMAGE_ID\",\"public_url\":\"URL\"},\"favicon_image\":null,\"captcha_public_key\":null,\"captcha_widget_type\":null,\"captcha_public_key_invisible\":null,\"captcha_provider\":null,\"captcha_oauth_bypass\":[]},\"user_settings\":{\"attributes\":{\"email_address\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":true,\"first_factors\":[\"email_code\",\"email_link\"],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[\"email_code\",\"email_link\"],\"verify_at_sign_up\":true},\"phone_number\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":true,\"first_factors\":[\"phone_code\"],\"used_for_second_factor\":true,\"second_factors\":[\"phone_code\"],\"verifications\":[\"phone_code\"],\"verify_at_sign_up\":true},\"username\":{\"enabled\":true,\"required\":false,\"used_for_first_factor\":true,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"web3_wallet\":{\"enabled\":false,\"required\":false,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"first_name\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"last_name\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"password\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":true,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"authenticator_app\":{\"enabled\":true,\"required\":false,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":true,\"second_factors\":[\"totp\"],\"verifications\":[\"totp\"],\"verify_at_sign_up\":false},\"ticket\":{\"enabled\":true,\"required\":false,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"backup_code\":{\"enabled\":false,\"required\":false,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"passkey\":{\"enabled\":true,\"required\":false,\"used_for_first_factor\":true,\"first_factors\":[\"passkey\"],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[\"passkey\"],\"verify_at_sign_up\":false}},\"sign_in\":{\"second_factor\":{\"required\":false}},\"sign_up\":{\"captcha_enabled\":false,\"captcha_widget_type\":\"smart\",\"custom_action_required\":false,\"progressive\":true,\"mode\":\"public\",\"legal_consent_enabled\":true},\"restrictions\":{\"allowlist\":{\"enabled\":false},\"blocklist\":{\"enabled\":false},\"allowlist_blocklist_disabled_on_sign_in\":{\"enabled\":false},\"block_email_subaddresses\":{\"enabled\":false},\"block_disposable_email_domains\":{\"enabled\":false}},\"username_settings\":{\"min_length\":4,\"max_length\":64,\"allow_extended_special_characters\":false},\"actions\":{\"delete_self\":true,\"create_organization\":true,\"create_organizations_limit\":null},\"attack_protection\":{\"user_lockout\":{\"enabled\":true,\"max_attempts\":100,\"duration_in_minutes\":60},\"pii\":{\"enabled\":true},\"email_link\":{\"require_same_client\":false},\"enumeration_protection\":{\"enabled\":false}},\"passkey_settings\":{\"allow_autofill\":true,\"show_sign_in_button\":true},\"social\":{\"oauth_apple\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_apple\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"Apple\",\"logo_url\":\"URL\"},\"oauth_custom_spung\":{\"enabled\":false,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_custom_spung\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"spung\"},\"oauth_facebook\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_facebook\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"Facebook\",\"logo_url\":\"URL\"},\"oauth_github\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_github\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"GitHub\",\"logo_url\":\"URL\"},\"oauth_google\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":true,\"strategy\":\"oauth_google\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"Google\",\"logo_url\":\"URL\"},\"oauth_linkedin_oidc\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_linkedin_oidc\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"LinkedIn\",\"logo_url\":\"URL\"}},\"password_settings\":{\"disable_hibp\":false,\"min_length\":8,\"max_length\":0,\"require_special_char\":true,\"require_numbers\":true,\"require_uppercase\":true,\"require_lowercase\":true,\"show_zxcvbn\":false,\"min_zxcvbn_strength\":0,\"enforce_hibp_on_sign_in\":false,\"allowed_special_characters\":\"+$-_\"}},\"commerce_settings\":{\"billing\":{\"enabled\":false,\"has_paid_user_plans\":false,\"has_paid_org_plans\":false,\"free_trial_requires_payment_method\":true,\"user\":{\"enabled\":false,\"has_paid_plans\":false},\"organization\":{\"enabled\":false,\"has_paid_plans\":false}}},\"api_keys_settings\":{\"enabled\":false},\"maintenance_mode\":false,\"client_debug_mode\":false}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/003.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/003.json new file mode 100644 index 00000000..e265c271 --- /dev/null +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/003.json @@ -0,0 +1 @@ +{"key":"POST /v1/client/sign_ins identifier","body":"{\"response\":{\"object\":\"sign_in_attempt\",\"id\":\"SIGN_IN_ID\",\"status\":\"needs_first_factor\",\"supported_identifiers\":[\"email_address\",\"passkey\",\"password\",\"phone_number\",\"username\"],\"supported_first_factors\":[{\"strategy\":\"password\"},{\"strategy\":\"phone_code\",\"safe_identifier\":\"%%PHONE%%\",\"phone_number_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"email_code\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"email_link\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"reset_password_email_code\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true}],\"supported_second_factors\":null,\"first_factor_verification\":null,\"second_factor_verification\":null,\"identifier\":\"%%USERNAME%%\",\"user_data\":null,\"created_session_id\":null,\"abandon_at\":\"%%DATETIME 1%%\",\"locale\":null},\"client\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[],\"sign_in\":{\"object\":\"sign_in_attempt\",\"id\":\"SIGN_IN_ID\",\"status\":\"needs_first_factor\",\"supported_identifiers\":[\"email_address\",\"passkey\",\"password\",\"phone_number\",\"username\"],\"supported_first_factors\":[{\"strategy\":\"password\"},{\"strategy\":\"phone_code\",\"safe_identifier\":\"%%PHONE%%\",\"phone_number_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"email_code\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"email_link\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"reset_password_email_code\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true}],\"supported_second_factors\":null,\"first_factor_verification\":null,\"second_factor_verification\":null,\"identifier\":\"%%USERNAME%%\",\"user_data\":null,\"created_session_id\":null,\"abandon_at\":\"%%DATETIME 1%%\",\"locale\":null},\"sign_up\":null,\"last_active_session_id\":null,\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -2%%\",\"updated_at\":\"%%DATETIME -1%%\"}}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/004.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/004.json new file mode 100644 index 00000000..d7e83cbb --- /dev/null +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/004.json @@ -0,0 +1 @@ +{"key":"POST /v1/client/sign_ins/SIGN_IN_ID/attempt_first_factor password&strategy=password","body":"{\"response\":{\"object\":\"sign_in_attempt\",\"id\":\"SIGN_IN_ID\",\"status\":\"complete\",\"supported_identifiers\":[\"email_address\",\"passkey\",\"password\",\"phone_number\",\"username\"],\"supported_first_factors\":null,\"supported_second_factors\":null,\"first_factor_verification\":{\"object\":\"verification_password\",\"status\":\"verified\",\"strategy\":\"password\",\"attempts\":1,\"expire_at\":null},\"second_factor_verification\":null,\"identifier\":\"%%USERNAME%%\",\"user_data\":null,\"created_session_id\":\"SESSION_ID\",\"abandon_at\":\"%%DATETIME 1%%\",\"locale\":null},\"client\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[{\"object\":\"session\",\"id\":\"SESSION_ID\",\"status\":\"active\",\"expire_at\":\"%%DATETIME 2%%\",\"abandon_at\":\"%%DATETIME 3%%\",\"last_active_at\":\"%%DATETIME -4%%\",\"last_active_organization_id\":null,\"actor\":null,\"user\":{\"id\":\"USER_ID\",\"object\":\"user\",\"username\":\"userfortests\",\"first_name\":\"%%FIRSTNAME%%\",\"last_name\":\"%%LASTNAME%%\",\"locale\":null,\"image_url\":\"URL\",\"has_image\":false,\"primary_email_address_id\":\"IDENTIFIER_ID\",\"primary_phone_number_id\":\"IDENTIFIER_ID\",\"primary_web3_wallet_id\":null,\"password_enabled\":true,\"two_factor_enabled\":false,\"totp_enabled\":false,\"backup_code_enabled\":false,\"email_addresses\":[{\"id\":\"IDENTIFIER_ID\",\"object\":\"email_address\",\"email_address\":\"%%EMAIL%%\",\"reserved\":false,\"verification\":{\"object\":\"verification_admin\",\"status\":\"verified\",\"strategy\":\"admin\",\"attempts\":null,\"expire_at\":null},\"linked_to\":[],\"matches_sso_connection\":false,\"created_at\":\"%%DATETIME -8%%\",\"updated_at\":\"%%DATETIME -8%%\"}],\"phone_numbers\":[{\"id\":\"IDENTIFIER_ID\",\"object\":\"phone_number\",\"phone_number\":\"+15555550105\",\"reserved_for_second_factor\":false,\"default_second_factor\":false,\"reserved\":false,\"verification\":{\"object\":\"verification_admin\",\"status\":\"verified\",\"strategy\":\"admin\",\"attempts\":null,\"expire_at\":null},\"linked_to\":[],\"backup_codes\":null,\"created_at\":\"%%DATETIME -7%%\",\"updated_at\":\"%%DATETIME -7%%\"}],\"web3_wallets\":[],\"passkeys\":[],\"external_accounts\":[],\"saml_accounts\":[],\"enterprise_accounts\":[],\"public_metadata\":{},\"unsafe_metadata\":{},\"external_id\":null,\"last_sign_in_at\":\"%%DATETIME -4%%\",\"banned\":false,\"locked\":false,\"lockout_expires_in_seconds\":null,\"verification_attempts_remaining\":100,\"created_at\":\"%%DATETIME -9%%\",\"updated_at\":\"%%DATETIME -3%%\",\"delete_self_enabled\":true,\"create_organization_enabled\":true,\"last_active_at\":\"%%DATETIME -6%%\",\"mfa_enabled_at\":null,\"mfa_disabled_at\":null,\"legal_accepted_at\":\"%%DATETIME -10%%\",\"profile_image_url\":\"URL\",\"organization_memberships\":[]},\"public_user_data\":{\"first_name\":\"%%FIRSTNAME%%\",\"last_name\":\"%%LASTNAME%%\",\"image_url\":\"URL\",\"has_image\":false,\"identifier\":\"%%USERNAME%%\",\"username\":\"userfortests\",\"profile_image_url\":\"URL\"},\"factor_verification_age\":[0,-1],\"created_at\":\"%%DATETIME -4%%\",\"updated_at\":\"%%DATETIME -1%%\",\"last_active_token\":{\"object\":\"token\",\"jwt\":\"e30=.e30=.e30=\"}}],\"sign_in\":null,\"sign_up\":null,\"last_active_session_id\":\"SESSION_ID\",\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -5%%\",\"updated_at\":\"%%DATETIME -2%%\"}}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/005.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/005.json new file mode 100644 index 00000000..6e1ebe1a --- /dev/null +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/005.json @@ -0,0 +1 @@ +{"key":"DELETE /v1/client","body":"{\"response\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[],\"sign_in\":null,\"sign_up\":null,\"last_active_session_id\":null,\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -2%%\",\"updated_at\":\"%%DATETIME -1%%\"},\"client\":null}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/flows/sign_in_flow_test.dart b/packages/clerk_flutter/example/test/flows/sign_in_flow_test.dart new file mode 100644 index 00000000..9fcd13ce --- /dev/null +++ b/packages/clerk_flutter/example/test/flows/sign_in_flow_test.dart @@ -0,0 +1,170 @@ +import 'package:clerk_flutter/clerk_flutter.dart'; +import 'package:clerk_flutter/logging.dart'; +import 'package:clerk_flutter_example/main.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../test_helpers.dart'; + +Future _signOut(WidgetTester tester) async { + final signOutButton = find.text('Sign out'); + expect(signOutButton, findsOneWidget); + await tester.tap(signOutButton); + await tester.pumpAndSettle(); + + expect(find.text('Are you sure?'), findsOneWidget); + await tester.tap(find.text('OK')); + await tester.pumpAndSettle(); + + expect( + find.text('Welcome back! Please sign in to continue'), + findsOneWidget, + ); +} + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + late final TestEnv env; + + setUpAll(() async { + env = TestEnv('.env.test'); + await setUpLogging(printer: TestLogPrinter(), level: Level.INFO); + }); + + Future initialiseForTest(WidgetTester tester) async { + setIgnoreOverflowErrors(); + + final httpService = TestHttpService('flow/sign_in_flow_test', env) + ..recordPath = tester.testDescription.toLowerCase().replaceAll(' ', '_'); + + final config = TestAuthConfig( + publishableKey: env.publishableKey, + httpService: httpService, + ).toClerkAuthConfig(); + + await tester.pumpWidget(ExampleApp(config: config)); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Clerk UI Sign In')); + await tester.pumpAndSettle(); + } + + group('sign in with password: ', () { + testWidgets('with email and password', (tester) async { + // Load app widget. + await initialiseForTest(tester); + + final emailInput = find.byKey(kIdentifierInputField); + await tester.tap(emailInput); + tester.testTextInput.enterText(env.email); + + await tester.tap(find.text('Continue')); + await tester.pumpAndSettle(); + + expect(find.text(env.email), findsOneWidget); + + await tester.tap(find.byKey(kPasswordInputField)); + tester.testTextInput.enterText(env.password); + + await tester.tap(find.text('Continue')); + await tester.pumpAndSettle(); + + expect(find.text(env.email), findsOneWidget); + + await _signOut(tester); + }); + + testWidgets('with username and password', (tester) async { + // Load app widget. + await initialiseForTest(tester); + + final usernameInput = find.byKey(kIdentifierInputField); + await tester.tap(usernameInput); + tester.testTextInput.enterText(env.username); + + await tester.tap(find.text('Continue')); + await tester.pumpAndSettle(); + + expect(find.text(env.username), findsOneWidget); + + await tester.tap(find.byKey(kPasswordInputField)); + tester.testTextInput.enterText(env.password); + + await tester.tap(find.text('Continue')); + await tester.pumpAndSettle(); + + expect(find.text(env.email), findsOneWidget); + + await _signOut(tester); + }); + + testWidgets('with phone number and password', (tester) async { + // Load app widget. + await initialiseForTest(tester); + + await tester.tap(find.text('Switch to phone')); + await tester.pumpAndSettle(); + + final phoneInput = find.byKey(kPhoneInputField); + expect(phoneInput, findsOneWidget); + + // try changing zones away from the default +1/US to +44/UK... + await tester.tap(find.text('+ 1')); + await tester.pumpAndSettle(); + await tester.tap(find.text('United Kingdom')); + await tester.pumpAndSettle(); + + // ...and back + await tester.tap(find.text('+ 44')); + await tester.pumpAndSettle(); + await tester.tap(find.text('United States')); + await tester.pumpAndSettle(); + + await tester.tap(phoneInput); + final local = env.phoneNumber.replaceFirst('+1', ''); + tester.testTextInput.enterText(local); + + await tester.tap(find.text('Continue')); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(kPasswordInputField)); + tester.testTextInput.enterText(env.password); + + await tester.tap(find.text('Continue')); + await tester.pumpAndSettle(); + + expect(find.text(env.email), findsOneWidget); + + await _signOut(tester); + }); + }); + + // group('sign in with code: ', () { + // testWidgets('with email and code', (tester) async { + // // Load app widget. + // await initialiseForTest(tester); + // + // final emailInput = find.byKey(kIdentifierInputField); + // await tester.tap(emailInput); + // tester.testTextInput.enterText(env.email); + // + // await tester.tap(find.text('Continue')); + // await tester.pumpAndSettle(); + // + // expect(find.text(env.email), findsOneWidget); + // + // await tester.tap( + // find.text('Sign in by entering a code sent to you by email'), + // ); + // await tester.pumpAndSettle(); + // + // expect(find.text('Enter the code sent to ${env.email}'), findsOneWidget); + // tester.testTextInput.enterText(env.code); + // await tester.pumpAndSettle(); + // + // expect(find.text(env.email), findsOneWidget); + // + // await _signOut(tester); + // }); + // }); +} diff --git a/packages/clerk_flutter/example/test/test_helpers.dart b/packages/clerk_flutter/example/test/test_helpers.dart new file mode 100644 index 00000000..6ba8bc5a --- /dev/null +++ b/packages/clerk_flutter/example/test/test_helpers.dart @@ -0,0 +1,75 @@ +import 'dart:io'; + +import 'package:clerk_auth/clerk_auth.dart' as clerk show AuthConfig; +import 'package:clerk_flutter/clerk_flutter.dart'; +import 'package:clerk_flutter/src/utils/clerk_file_cache.dart'; +import 'package:flutter/foundation.dart'; +import 'package:test/test.dart' as test show expect; + +export 'package:flutter_test/flutter_test.dart'; + +export 'test_support/test_support.dart'; + +void expectThat(bool result, {String? reason}) => + test.expect(result, true, reason: reason); + +extension AuthConfigExt on clerk.AuthConfig { + ClerkAuthConfig toClerkAuthConfig() { + return ClerkAuthConfig( + publishableKey: publishableKey, + sessionTokenPolling: sessionTokenPolling, + isTestMode: isTestMode, + telemetryEndpoint: telemetryEndpoint, + telemetryPeriod: telemetryPeriod, + clientRefreshPeriod: clientRefreshPeriod, + httpService: httpService, + httpConnectionTimeout: httpConnectionTimeout, + persistor: persistor, + fileCache: const _NoneFileCache(), + ); + } +} + +void setIgnoreOverflowErrors() { + final originalOnError = FlutterError.onError; + if (originalOnError != setIgnoreOverflowErrors) { + FlutterError.onError = + (FlutterErrorDetails details, {bool forceReport = false}) { + final hasReportableError = switch (details.exception) { + FlutterError exception => exception.diagnostics.any( + (e) { + final value = e.toString(); + return value.startsWith('A RenderFlex overflowed by') == false && + value.startsWith('Unable to load asset') == false; + }, + ), + _ => true, + }; + + if (hasReportableError) { + if (originalOnError case FlutterExceptionHandler onError) { + onError(details); + } else { + FlutterError.dumpErrorToConsole(details, forceReport: forceReport); + } + } + }; + } +} + +class _NoneFileCache implements ClerkFileCache { + const _NoneFileCache(); + + @override + Future initialize() async {} + + @override + void terminate() {} + + @override + Stream stream( + Uri uri, { + Duration ttl = ClerkFileCache.defaultTTL, + Map? headers, + }) async* {} +} diff --git a/packages/clerk_flutter/example/test/test_support b/packages/clerk_flutter/example/test/test_support new file mode 120000 index 00000000..e03027e8 --- /dev/null +++ b/packages/clerk_flutter/example/test/test_support @@ -0,0 +1 @@ +../../../../test_support/test \ No newline at end of file diff --git a/packages/clerk_flutter/lib/clerk_flutter.dart b/packages/clerk_flutter/lib/clerk_flutter.dart index 8d0dd0a7..f8044706 100644 --- a/packages/clerk_flutter/lib/clerk_flutter.dart +++ b/packages/clerk_flutter/lib/clerk_flutter.dart @@ -7,6 +7,7 @@ export 'src/clerk_user_action.dart'; export 'src/utils/clerk_auth_config.dart'; export 'src/utils/clerk_sdk_flags.dart'; export 'src/widgets/authentication/clerk_authentication.dart'; +export 'src/widgets/clerk_keys.dart'; export 'src/widgets/control/clerk_auth.dart'; export 'src/widgets/control/clerk_auth_builder.dart'; export 'src/widgets/control/clerk_error_listener.dart'; diff --git a/packages/clerk_flutter/lib/generated/clerk_sdk_localizations.dart b/packages/clerk_flutter/lib/generated/clerk_sdk_localizations.dart index 98ac5a9c..9da6059c 100644 --- a/packages/clerk_flutter/lib/generated/clerk_sdk_localizations.dart +++ b/packages/clerk_flutter/lib/generated/clerk_sdk_localizations.dart @@ -62,18 +62,15 @@ import 'clerk_sdk_localizations_en.dart'; /// be consistent with the languages listed in the ClerkSdkLocalizations.supportedLocales /// property. abstract class ClerkSdkLocalizations { - ClerkSdkLocalizations(String locale) - : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + ClerkSdkLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString()); final String localeName; static ClerkSdkLocalizations? of(BuildContext context) { - return Localizations.of( - context, ClerkSdkLocalizations); + return Localizations.of(context, ClerkSdkLocalizations); } - static const LocalizationsDelegate delegate = - _ClerkSdkLocalizationsDelegate(); + static const LocalizationsDelegate delegate = _ClerkSdkLocalizationsDelegate(); /// A list of this localizations delegate along with the default localizations /// delegates. @@ -85,8 +82,7 @@ abstract class ClerkSdkLocalizations { /// Additional delegates can be added by appending to this list in /// MaterialApp. This list does not have to be used at all if a custom list /// of delegates is preferred or required. - static const List> localizationsDelegates = - >[ + static const List> localizationsDelegates = >[ delegate, GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, @@ -94,7 +90,9 @@ abstract class ClerkSdkLocalizations { ]; /// A list of this localizations delegate's supported locales. - static const List supportedLocales = [Locale('en')]; + static const List supportedLocales = [ + Locale('en') + ]; /// No description provided for @aLengthOfBetweenMINAndMAX. /// @@ -925,34 +923,33 @@ abstract class ClerkSdkLocalizations { String get welcomePleaseFillInTheDetailsToGetStarted; } -class _ClerkSdkLocalizationsDelegate - extends LocalizationsDelegate { +class _ClerkSdkLocalizationsDelegate extends LocalizationsDelegate { const _ClerkSdkLocalizationsDelegate(); @override Future load(Locale locale) { - return SynchronousFuture( - lookupClerkSdkLocalizations(locale)); + return SynchronousFuture(lookupClerkSdkLocalizations(locale)); } @override - bool isSupported(Locale locale) => - ['en'].contains(locale.languageCode); + bool isSupported(Locale locale) => ['en'].contains(locale.languageCode); @override bool shouldReload(_ClerkSdkLocalizationsDelegate old) => false; } ClerkSdkLocalizations lookupClerkSdkLocalizations(Locale locale) { + + // Lookup logic when only language code is specified. switch (locale.languageCode) { - case 'en': - return ClerkSdkLocalizationsEn(); + case 'en': return ClerkSdkLocalizationsEn(); } throw FlutterError( - 'ClerkSdkLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' - 'an issue with the localizations generation tool. Please file an issue ' - 'on GitHub with a reproducible sample app and the gen-l10n configuration ' - 'that was used.'); + 'ClerkSdkLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.' + ); } diff --git a/packages/clerk_flutter/lib/generated/clerk_sdk_localizations_en.dart b/packages/clerk_flutter/lib/generated/clerk_sdk_localizations_en.dart index bc1a1b35..2894b8c8 100644 --- a/packages/clerk_flutter/lib/generated/clerk_sdk_localizations_en.dart +++ b/packages/clerk_flutter/lib/generated/clerk_sdk_localizations_en.dart @@ -33,12 +33,10 @@ class ClerkSdkLocalizationsEn extends ClerkSdkLocalizations { String get abandoned => 'abandoned'; @override - String get acceptTerms => - 'I agree to the Terms of Service and Privacy Policy'; + String get acceptTerms => 'I agree to the Terms of Service and Privacy Policy'; @override - String get actionNotTimely => - 'Awaited user action not completed in required timeframe'; + String get actionNotTimely => 'Awaited user action not completed in required timeframe'; @override String get active => 'active'; @@ -144,8 +142,7 @@ class ClerkSdkLocalizationsEn extends ClerkSdkLocalizations { String get enterTheCodeSentToYou => 'Enter the code sent to you'; @override - String get enterYourOrganizationDetailsToContinue => - 'Enter your organization details to continue'; + String get enterYourOrganizationDetailsToContinue => 'Enter your organization details to continue'; @override String get expired => 'expired'; @@ -282,15 +279,13 @@ class ClerkSdkLocalizationsEn extends ClerkSdkLocalizations { String get password => 'Password'; @override - String get passwordAndPasswordConfirmationMustMatch => - 'Password and password confirmation must match'; + String get passwordAndPasswordConfirmationMustMatch => 'Password and password confirmation must match'; @override String get passwordConfirmation => 'confirm password'; @override - String get passwordMatchError => - 'Password and password confirmation must match'; + String get passwordMatchError => 'Password and password confirmation must match'; @override String get passwordMustBeSupplied => 'A password must be supplied'; @@ -311,12 +306,10 @@ class ClerkSdkLocalizationsEn extends ClerkSdkLocalizations { String get phoneNumberConcise => 'phone'; @override - String get pleaseAddRequiredInformation => - 'Something seems to be missing. Please add the required information'; + String get pleaseAddRequiredInformation => 'Something seems to be missing. Please add the required information'; @override - String get pleaseChooseAnAccountToConnect => - 'Please choose an account to connect'; + String get pleaseChooseAnAccountToConnect => 'Please choose an account to connect'; @override String get pleaseEnterYourIdentifier => 'Please enter your identifier'; @@ -346,15 +339,13 @@ class ClerkSdkLocalizationsEn extends ClerkSdkLocalizations { String get resend => 'Resend'; @override - String get resetFailed => - 'That password reset attempt failed. A new code has been sent.'; + String get resetFailed => 'That password reset attempt failed. A new code has been sent.'; @override String get resetPassword => 'Reset password and sign in'; @override - String get selectAccount => - 'Select the account with which you wish to continue'; + String get selectAccount => 'Select the account with which you wish to continue'; @override String get sendMeTheCode => 'Send me the reset code'; @@ -366,16 +357,13 @@ class ClerkSdkLocalizationsEn extends ClerkSdkLocalizations { String get signIn => 'Sign in'; @override - String get signInByClickingALinkSentToYouByEmail => - 'Sign in by clicking a link sent to you by email'; + String get signInByClickingALinkSentToYouByEmail => 'Sign in by clicking a link sent to you by email'; @override - String get signInByEnteringACodeSentToYouByEmail => - 'Sign in by entering a code sent to you by email'; + String get signInByEnteringACodeSentToYouByEmail => 'Sign in by entering a code sent to you by email'; @override - String get signInByEnteringACodeSentToYouByTextMessage => - 'Sign in by entering a code sent to you by text message'; + String get signInByEnteringACodeSentToYouByTextMessage => 'Sign in by entering a code sent to you by text message'; @override String signInError(String arg) { @@ -471,10 +459,8 @@ class ClerkSdkLocalizationsEn extends ClerkSdkLocalizations { String get web3Wallet => 'web3 wallet'; @override - String get welcomeBackPleaseSignInToContinue => - 'Welcome back! Please sign in to continue'; + String get welcomeBackPleaseSignInToContinue => 'Welcome back! Please sign in to continue'; @override - String get welcomePleaseFillInTheDetailsToGetStarted => - 'Welcome! Please fill in the details to get started'; + String get welcomePleaseFillInTheDetailsToGetStarted => 'Welcome! Please fill in the details to get started'; } diff --git a/packages/clerk_flutter/lib/logging.dart b/packages/clerk_flutter/lib/logging.dart index 1e6682d5..db0d6837 100644 --- a/packages/clerk_flutter/lib/logging.dart +++ b/packages/clerk_flutter/lib/logging.dart @@ -1,4 +1,5 @@ /// Enables the example code to use the clerk_auth logging facility library; -export 'package:clerk_auth/clerk_auth.dart' show setUpLogging, Logging, Printer; +export 'package:clerk_auth/clerk_auth.dart' + show setUpLogging, Level, Logging, Printer; diff --git a/packages/clerk_flutter/lib/src/widgets/authentication/clerk_sign_in_panel.dart b/packages/clerk_flutter/lib/src/widgets/authentication/clerk_sign_in_panel.dart index db471173..bf99f16c 100644 --- a/packages/clerk_flutter/lib/src/widgets/authentication/clerk_sign_in_panel.dart +++ b/packages/clerk_flutter/lib/src/widgets/authentication/clerk_sign_in_panel.dart @@ -275,6 +275,7 @@ class _FactorList extends StatelessWidget { Padding( padding: topPadding8 + bottomPadding2, child: ClerkTextFormField( + key: kPasswordInputField, label: l10ns.password, obscureText: true, onChanged: onPasswordChanged, @@ -290,7 +291,9 @@ class _FactorList extends StatelessWidget { child: StrategyButton( key: ValueKey(factor), strategy: factor.strategy, - onClick: () => onSubmit(factor.strategy), + onClick: () { + onSubmit(factor.strategy); + }, ), ), ], diff --git a/packages/clerk_flutter/lib/src/widgets/clerk_keys.dart b/packages/clerk_flutter/lib/src/widgets/clerk_keys.dart new file mode 100644 index 00000000..d287a01e --- /dev/null +++ b/packages/clerk_flutter/lib/src/widgets/clerk_keys.dart @@ -0,0 +1,10 @@ +import 'package:flutter/foundation.dart'; + +/// Email identifier +const kIdentifierInputField = Key('emailInputField'); + +/// Phone identifier +const kPhoneInputField = Key('phoneInputField'); + +/// Password +const kPasswordInputField = Key('passwordInputField'); diff --git a/packages/clerk_flutter/lib/src/widgets/ui/clerk_identifier_input.dart b/packages/clerk_flutter/lib/src/widgets/ui/clerk_identifier_input.dart index 448576e1..be301c71 100644 --- a/packages/clerk_flutter/lib/src/widgets/ui/clerk_identifier_input.dart +++ b/packages/clerk_flutter/lib/src/widgets/ui/clerk_identifier_input.dart @@ -108,7 +108,7 @@ class _ClerkIdentifierInputState extends State { Closeable( closed: widget.identifierType.value.isPhoneNumber, child: ClerkTextFormField( - key: const Key('identifier'), + key: kIdentifierInputField, initial: widget.initialValue, label: l10ns.grammar.toSentence( l10ns.grammar.toLitany( @@ -134,7 +134,7 @@ class _ClerkIdentifierInputState extends State { Closeable( closed: widget.identifierType.value.isEmailAddress, child: ClerkPhoneNumberFormField( - key: const Key('phoneIdentifier'), + key: kPhoneInputField, label: l10ns.grammar.toSentence( l10ns.grammar.toLitany( phoneStrategies diff --git a/packages/clerk_flutter/lib/src/widgets/ui/clerk_phone_number_form_field.dart b/packages/clerk_flutter/lib/src/widgets/ui/clerk_phone_number_form_field.dart index 020014da..34289239 100644 --- a/packages/clerk_flutter/lib/src/widgets/ui/clerk_phone_number_form_field.dart +++ b/packages/clerk_flutter/lib/src/widgets/ui/clerk_phone_number_form_field.dart @@ -92,10 +92,11 @@ class _PhoneInput extends StatefulWidget { class _PhoneInputState extends State<_PhoneInput> { static const _kIsoCode = 'phone_number.iso_code'; + static const _testNumberPrefix = '+155555501'; late PhoneNumber _phoneNumber; - // widget.initial is String ? PhoneNumber.parse(widget.initial!) : null; + late final bool _isTestMode; late bool _isValid = _phoneNumber.isValid() == true; late IsoCode _isoCode; @@ -108,7 +109,9 @@ class _PhoneInputState extends State<_PhoneInput> { } Future _getPhoneNumber() async { - final persistor = ClerkAuth.of(context, listen: false).config.persistor; + final config = ClerkAuth.of(context, listen: false).config; + _isTestMode = config.isTestMode; + final persistor = config.persistor; if (widget.initial case String initial) { _phoneNumber = PhoneNumber.parse(initial); _isoCode = _phoneNumber.isoCode; @@ -130,6 +133,17 @@ class _PhoneInputState extends State<_PhoneInput> { } } + bool _validate(PhoneNumber phoneNumber) { + if (_isTestMode) { + final number = phoneNumber.international; + if (number.startsWith(_testNumberPrefix) && + number.length == _testNumberPrefix.length + 2) { + return true; + } + } + return phoneNumber.isValid(); + } + @override Widget build(BuildContext context) { return FutureBuilder( @@ -148,7 +162,7 @@ class _PhoneInputState extends State<_PhoneInput> { focusNode: widget.focusNode, onChanged: (phoneNumber) { if (phoneNumber case PhoneNumber phoneNumber) { - final valid = phoneNumber.isValid(); + final valid = _validate(phoneNumber); if (valid != _isValid) setState(() => _isValid = valid); if (valid) { widget.onChanged(phoneNumber.international); diff --git a/packages/clerk_flutter/pubspec.yaml b/packages/clerk_flutter/pubspec.yaml index 76c1bfe8..8a0e8286 100644 --- a/packages/clerk_flutter/pubspec.yaml +++ b/packages/clerk_flutter/pubspec.yaml @@ -7,7 +7,6 @@ issue_tracker: https://github.com/clerk/clerk-sdk-flutter/labels/clerk_flutter topics: - authentication - environment: sdk: '>=3.6.2 <4.0.0' flutter: '>=3.27.4' @@ -33,6 +32,10 @@ dev_dependencies: flutter_lints: ^3.0.1 flutter_test: sdk: flutter + dart_dotenv: ^1.0.1 + test: ^1.25.5 + integration_test: + sdk: flutter flutter: assets: diff --git a/packages/clerk_flutter/test/widgets/clerk_loading_overlay_test.dart b/packages/clerk_flutter/test/widgets/clerk_loading_overlay_test.dart index e2d830c3..f441b4b4 100644 --- a/packages/clerk_flutter/test/widgets/clerk_loading_overlay_test.dart +++ b/packages/clerk_flutter/test/widgets/clerk_loading_overlay_test.dart @@ -5,7 +5,8 @@ import 'package:clerk_flutter/src/utils/clerk_auth_config.dart'; import 'package:clerk_flutter/src/widgets/ui/clerk_loading_overlay.dart'; import 'package:clerk_flutter/src/widgets/ui/clerk_overlay_host.dart'; import 'package:flutter/widgets.dart'; -import 'package:flutter_test/flutter_test.dart'; + +import '../../example/test/test_helpers.dart'; const startupDuration = ClerkLoadingOverlay.startupDuration; const minOnScreenTime = ClerkLoadingOverlay.minimumOnScreenDuration; @@ -19,9 +20,6 @@ extension on DateTime { int operator -(DateTime other) => difference(other).inMilliseconds; } -void expectThat(bool result, {String? reason}) => - expect(result, true, reason: reason); - void main() { group('ClerkLoadingOverlay:', skip: true, () { final config = ClerkAuthConfig( diff --git a/test_support/test/src/test_auth_config.dart b/test_support/test/src/test_auth_config.dart index e3918805..3a7d4a3b 100644 --- a/test_support/test/src/test_auth_config.dart +++ b/test_support/test/src/test_auth_config.dart @@ -11,6 +11,7 @@ class TestAuthConfig extends AuthConfig { persistor: Persistor.none, clientRefreshPeriod: Duration.zero, telemetryPeriod: Duration.zero, + isTestMode: true, ); static List _localesLookup() => const ['en'];