diff --git a/flutter-ory-network/.gitignore b/flutter-ory-network/.gitignore index 189b569..eb17452 100644 --- a/flutter-ory-network/.gitignore +++ b/flutter-ory-network/.gitignore @@ -32,6 +32,51 @@ migrate_working_dir/ .pub/ /build/ +# Android related +**/android/build/ +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages + # Symbolication related app.*.symbols @@ -43,4 +88,5 @@ app.*.map.json /android/app/profile /android/app/release +# Environmental variables .env diff --git a/flutter-ory-network/README.md b/flutter-ory-network/README.md index bfe6210..d3acb7d 100644 --- a/flutter-ory-network/README.md +++ b/flutter-ory-network/README.md @@ -20,6 +20,48 @@ Create .env file with your project url in the root folder of the Flutter app ORY_BASE_URL=https://{your-project-slug}.projects.oryapis.com ``` +### Google Sign In + +If you use Google Sign In on Android, add following variable to .env file + +```env +WEB_CLIENT_ID={web-client-id}.apps.googleusercontent.com +``` + +If you use Google Sign In on iOS, add following variable to .env file + +```env + +IOS_CLIENT_ID={ios-client-id}.apps.googleusercontent.com +``` + +Additionally, add iOS URL scheme to Info.plist. It can be found in additional +information of your iOS Client ID. + +```xml +CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + + IOS_URL_SCHEME + + + +``` + +For more information, see +[Google Integration Docs](https://www.ory.sh/docs/kratos/social-signin/google). + +### Apple Sign In + +To configure Apple Sign In on IOS, see +[Apple Integration Docs](https://www.ory.sh/docs/kratos/social-signin/apple).\ +To configure Apple Sign In on Android, see [Social sign-in for native and mobile apps ](https://www.ory.sh/docs/kratos/social-signin/native-apps). + ### Run locally 1. Install dependencies from `pubspec.yaml` diff --git a/flutter-ory-network/android/app/build.gradle b/flutter-ory-network/android/app/build.gradle index 9410838..cee1d87 100644 --- a/flutter-ory-network/android/app/build.gradle +++ b/flutter-ory-network/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - namespace "com.example.ory_network_flutter" + namespace "sh.ory.flutter_ory_network" compileSdkVersion flutter.compileSdkVersion ndkVersion flutter.ndkVersion @@ -45,10 +45,10 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.ory_network_flutter" + applicationId "sh.ory.flutter_ory_network" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. - minSdkVersion 18 + minSdkVersion 19 targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/flutter-ory-network/android/app/src/main/AndroidManifest.xml b/flutter-ory-network/android/app/src/main/AndroidManifest.xml index 097b25f..aff483c 100644 --- a/flutter-ory-network/android/app/src/main/AndroidManifest.xml +++ b/flutter-ory-network/android/app/src/main/AndroidManifest.xml @@ -1,8 +1,10 @@ + + android:icon="@mipmap/ic_launcher" + android:usesCleartextTraffic="true"> + android:name="io.flutter.embedding.android.NormalTheme" + android:resource="@style/NormalTheme" /> + - - + + + + + + + + + + + + + + + + + + + + + + + + + + IOS_URL_SCHEME + + + CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS diff --git a/flutter-ory-network/ios/Runner/Runner.entitlements b/flutter-ory-network/ios/Runner/Runner.entitlements new file mode 100644 index 0000000..a812db5 --- /dev/null +++ b/flutter-ory-network/ios/Runner/Runner.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.developer.applesignin + + Default + + + diff --git a/flutter-ory-network/lib/blocs/auth/auth_bloc.dart b/flutter-ory-network/lib/blocs/auth/auth_bloc.dart index 6d49f03..4ca09ad 100644 --- a/flutter-ory-network/lib/blocs/auth/auth_bloc.dart +++ b/flutter-ory-network/lib/blocs/auth/auth_bloc.dart @@ -16,13 +16,32 @@ part 'auth_bloc.freezed.dart'; class AuthBloc extends Bloc { final AuthRepository repository; AuthBloc({required this.repository}) - : super(const AuthState(status: AuthStatus.uninitialized)) { + : super(const AuthState.uninitialized()) { on(_onGetCurrentSessionInformation); + on(_onRequireLocationChange); + on(_onAddSession); on(_onChangeAuthStatus); on(_onLogOut); } + _onRequireLocationChange( + RequireLocationChange event, Emitter emit) { + emit(AuthState.locationChangeRequired(url: event.url)); + } + + _onAddSession(AddSession event, Emitter emit) { + emit(AuthState.authenticated(session: event.session)); + } + _onChangeAuthStatus(ChangeAuthStatus event, Emitter emit) { + switch (event.status) { + case AuthStatus.unauthenticated: + emit(const AuthState.unauthenticated()); + case AuthStatus.aal2Requested: + emit(const AuthState.aal2Requested()); + default: + emit(state); + } emit(state.copyWith(status: event.status, isLoading: false)); } @@ -33,16 +52,11 @@ class AuthBloc extends Bloc { final session = await repository.getCurrentSessionInformation(); - emit(state.copyWith( - isLoading: false, - status: AuthStatus.authenticated, - session: session)); + emit(AuthState.authenticated(session: session)); } on UnauthorizedException catch (_) { - emit(state.copyWith( - status: AuthStatus.unauthenticated, session: null, isLoading: false)); + emit(const AuthState.unauthenticated()); } on TwoFactorAuthRequiredException catch (_) { - emit(state.copyWith( - isLoading: false, session: null, status: AuthStatus.aal2Requested)); + emit(const AuthState.aal2Requested()); } on UnknownException catch (e) { emit(state.copyWith(isLoading: false, errorMessage: e.message)); } catch (_) { @@ -56,11 +70,9 @@ class AuthBloc extends Bloc { await repository.logout(); - emit(state.copyWith( - isLoading: false, status: AuthStatus.unauthenticated, session: null)); + emit(const AuthState.unauthenticated()); } on UnauthorizedException catch (_) { - emit(state.copyWith( - status: AuthStatus.unauthenticated, session: null, isLoading: false)); + emit(const AuthState.unauthenticated()); } on UnknownException catch (e) { emit(state.copyWith(isLoading: false, errorMessage: e.message)); } catch (_) { diff --git a/flutter-ory-network/lib/blocs/auth/auth_bloc.freezed.dart b/flutter-ory-network/lib/blocs/auth/auth_bloc.freezed.dart index c9fb075..d9c8dbd 100644 --- a/flutter-ory-network/lib/blocs/auth/auth_bloc.freezed.dart +++ b/flutter-ory-network/lib/blocs/auth/auth_bloc.freezed.dart @@ -20,9 +20,89 @@ final _privateConstructorUsedError = UnsupportedError( /// @nodoc mixin _$AuthState { AuthStatus get status => throw _privateConstructorUsedError; - Session? get session => throw _privateConstructorUsedError; bool get isLoading => throw _privateConstructorUsedError; String? get errorMessage => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult when({ + required TResult Function( + AuthStatus status, bool isLoading, String? errorMessage) + uninitialized, + required TResult Function( + AuthStatus status, bool isLoading, String? errorMessage) + unauthenticated, + required TResult Function(AuthStatus status, Session session, + bool isLoading, String? errorMessage) + authenticated, + required TResult Function( + AuthStatus status, bool isLoading, String? errorMessage) + aal2Requested, + required TResult Function( + AuthStatus status, String url, bool isLoading, String? errorMessage) + locationChangeRequired, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(AuthStatus status, bool isLoading, String? errorMessage)? + uninitialized, + TResult? Function(AuthStatus status, bool isLoading, String? errorMessage)? + unauthenticated, + TResult? Function(AuthStatus status, Session session, bool isLoading, + String? errorMessage)? + authenticated, + TResult? Function(AuthStatus status, bool isLoading, String? errorMessage)? + aal2Requested, + TResult? Function(AuthStatus status, String url, bool isLoading, + String? errorMessage)? + locationChangeRequired, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(AuthStatus status, bool isLoading, String? errorMessage)? + uninitialized, + TResult Function(AuthStatus status, bool isLoading, String? errorMessage)? + unauthenticated, + TResult Function(AuthStatus status, Session session, bool isLoading, + String? errorMessage)? + authenticated, + TResult Function(AuthStatus status, bool isLoading, String? errorMessage)? + aal2Requested, + TResult Function(AuthStatus status, String url, bool isLoading, + String? errorMessage)? + locationChangeRequired, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(AuthUninitialized value) uninitialized, + required TResult Function(AuthUnauthenticated value) unauthenticated, + required TResult Function(AuthAuthenticated value) authenticated, + required TResult Function(Aal2Requested value) aal2Requested, + required TResult Function(LocationChangeRequired value) + locationChangeRequired, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(AuthUninitialized value)? uninitialized, + TResult? Function(AuthUnauthenticated value)? unauthenticated, + TResult? Function(AuthAuthenticated value)? authenticated, + TResult? Function(Aal2Requested value)? aal2Requested, + TResult? Function(LocationChangeRequired value)? locationChangeRequired, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(AuthUninitialized value)? uninitialized, + TResult Function(AuthUnauthenticated value)? unauthenticated, + TResult Function(AuthAuthenticated value)? authenticated, + TResult Function(Aal2Requested value)? aal2Requested, + TResult Function(LocationChangeRequired value)? locationChangeRequired, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; @JsonKey(ignore: true) $AuthStateCopyWith get copyWith => @@ -34,11 +114,7 @@ abstract class $AuthStateCopyWith<$Res> { factory $AuthStateCopyWith(AuthState value, $Res Function(AuthState) then) = _$AuthStateCopyWithImpl<$Res, AuthState>; @useResult - $Res call( - {AuthStatus status, - Session? session, - bool isLoading, - String? errorMessage}); + $Res call({AuthStatus status, bool isLoading, String? errorMessage}); } /// @nodoc @@ -55,7 +131,6 @@ class _$AuthStateCopyWithImpl<$Res, $Val extends AuthState> @override $Res call({ Object? status = null, - Object? session = freezed, Object? isLoading = null, Object? errorMessage = freezed, }) { @@ -64,10 +139,6 @@ class _$AuthStateCopyWithImpl<$Res, $Val extends AuthState> ? _value.status : status // ignore: cast_nullable_to_non_nullable as AuthStatus, - session: freezed == session - ? _value.session - : session // ignore: cast_nullable_to_non_nullable - as Session?, isLoading: null == isLoading ? _value.isLoading : isLoading // ignore: cast_nullable_to_non_nullable @@ -81,44 +152,467 @@ class _$AuthStateCopyWithImpl<$Res, $Val extends AuthState> } /// @nodoc -abstract class _$$_AuthStateCopyWith<$Res> implements $AuthStateCopyWith<$Res> { - factory _$$_AuthStateCopyWith( - _$_AuthState value, $Res Function(_$_AuthState) then) = - __$$_AuthStateCopyWithImpl<$Res>; +abstract class _$$AuthUninitializedImplCopyWith<$Res> + implements $AuthStateCopyWith<$Res> { + factory _$$AuthUninitializedImplCopyWith(_$AuthUninitializedImpl value, + $Res Function(_$AuthUninitializedImpl) then) = + __$$AuthUninitializedImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({AuthStatus status, bool isLoading, String? errorMessage}); +} + +/// @nodoc +class __$$AuthUninitializedImplCopyWithImpl<$Res> + extends _$AuthStateCopyWithImpl<$Res, _$AuthUninitializedImpl> + implements _$$AuthUninitializedImplCopyWith<$Res> { + __$$AuthUninitializedImplCopyWithImpl(_$AuthUninitializedImpl _value, + $Res Function(_$AuthUninitializedImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = null, + Object? isLoading = null, + Object? errorMessage = freezed, + }) { + return _then(_$AuthUninitializedImpl( + status: null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as AuthStatus, + isLoading: null == isLoading + ? _value.isLoading + : isLoading // ignore: cast_nullable_to_non_nullable + as bool, + errorMessage: freezed == errorMessage + ? _value.errorMessage + : errorMessage // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +class _$AuthUninitializedImpl implements AuthUninitialized { + const _$AuthUninitializedImpl( + {this.status = AuthStatus.uninitialized, + this.isLoading = false, + this.errorMessage}); + + @override + @JsonKey() + final AuthStatus status; + @override + @JsonKey() + final bool isLoading; + @override + final String? errorMessage; + + @override + String toString() { + return 'AuthState.uninitialized(status: $status, isLoading: $isLoading, errorMessage: $errorMessage)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AuthUninitializedImpl && + (identical(other.status, status) || other.status == status) && + (identical(other.isLoading, isLoading) || + other.isLoading == isLoading) && + (identical(other.errorMessage, errorMessage) || + other.errorMessage == errorMessage)); + } + + @override + int get hashCode => Object.hash(runtimeType, status, isLoading, errorMessage); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$AuthUninitializedImplCopyWith<_$AuthUninitializedImpl> get copyWith => + __$$AuthUninitializedImplCopyWithImpl<_$AuthUninitializedImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + AuthStatus status, bool isLoading, String? errorMessage) + uninitialized, + required TResult Function( + AuthStatus status, bool isLoading, String? errorMessage) + unauthenticated, + required TResult Function(AuthStatus status, Session session, + bool isLoading, String? errorMessage) + authenticated, + required TResult Function( + AuthStatus status, bool isLoading, String? errorMessage) + aal2Requested, + required TResult Function( + AuthStatus status, String url, bool isLoading, String? errorMessage) + locationChangeRequired, + }) { + return uninitialized(status, isLoading, errorMessage); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(AuthStatus status, bool isLoading, String? errorMessage)? + uninitialized, + TResult? Function(AuthStatus status, bool isLoading, String? errorMessage)? + unauthenticated, + TResult? Function(AuthStatus status, Session session, bool isLoading, + String? errorMessage)? + authenticated, + TResult? Function(AuthStatus status, bool isLoading, String? errorMessage)? + aal2Requested, + TResult? Function(AuthStatus status, String url, bool isLoading, + String? errorMessage)? + locationChangeRequired, + }) { + return uninitialized?.call(status, isLoading, errorMessage); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(AuthStatus status, bool isLoading, String? errorMessage)? + uninitialized, + TResult Function(AuthStatus status, bool isLoading, String? errorMessage)? + unauthenticated, + TResult Function(AuthStatus status, Session session, bool isLoading, + String? errorMessage)? + authenticated, + TResult Function(AuthStatus status, bool isLoading, String? errorMessage)? + aal2Requested, + TResult Function(AuthStatus status, String url, bool isLoading, + String? errorMessage)? + locationChangeRequired, + required TResult orElse(), + }) { + if (uninitialized != null) { + return uninitialized(status, isLoading, errorMessage); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(AuthUninitialized value) uninitialized, + required TResult Function(AuthUnauthenticated value) unauthenticated, + required TResult Function(AuthAuthenticated value) authenticated, + required TResult Function(Aal2Requested value) aal2Requested, + required TResult Function(LocationChangeRequired value) + locationChangeRequired, + }) { + return uninitialized(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(AuthUninitialized value)? uninitialized, + TResult? Function(AuthUnauthenticated value)? unauthenticated, + TResult? Function(AuthAuthenticated value)? authenticated, + TResult? Function(Aal2Requested value)? aal2Requested, + TResult? Function(LocationChangeRequired value)? locationChangeRequired, + }) { + return uninitialized?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(AuthUninitialized value)? uninitialized, + TResult Function(AuthUnauthenticated value)? unauthenticated, + TResult Function(AuthAuthenticated value)? authenticated, + TResult Function(Aal2Requested value)? aal2Requested, + TResult Function(LocationChangeRequired value)? locationChangeRequired, + required TResult orElse(), + }) { + if (uninitialized != null) { + return uninitialized(this); + } + return orElse(); + } +} + +abstract class AuthUninitialized implements AuthState { + const factory AuthUninitialized( + {final AuthStatus status, + final bool isLoading, + final String? errorMessage}) = _$AuthUninitializedImpl; + + @override + AuthStatus get status; + @override + bool get isLoading; + @override + String? get errorMessage; + @override + @JsonKey(ignore: true) + _$$AuthUninitializedImplCopyWith<_$AuthUninitializedImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$AuthUnauthenticatedImplCopyWith<$Res> + implements $AuthStateCopyWith<$Res> { + factory _$$AuthUnauthenticatedImplCopyWith(_$AuthUnauthenticatedImpl value, + $Res Function(_$AuthUnauthenticatedImpl) then) = + __$$AuthUnauthenticatedImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({AuthStatus status, bool isLoading, String? errorMessage}); +} + +/// @nodoc +class __$$AuthUnauthenticatedImplCopyWithImpl<$Res> + extends _$AuthStateCopyWithImpl<$Res, _$AuthUnauthenticatedImpl> + implements _$$AuthUnauthenticatedImplCopyWith<$Res> { + __$$AuthUnauthenticatedImplCopyWithImpl(_$AuthUnauthenticatedImpl _value, + $Res Function(_$AuthUnauthenticatedImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = null, + Object? isLoading = null, + Object? errorMessage = freezed, + }) { + return _then(_$AuthUnauthenticatedImpl( + status: null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as AuthStatus, + isLoading: null == isLoading + ? _value.isLoading + : isLoading // ignore: cast_nullable_to_non_nullable + as bool, + errorMessage: freezed == errorMessage + ? _value.errorMessage + : errorMessage // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +class _$AuthUnauthenticatedImpl implements AuthUnauthenticated { + const _$AuthUnauthenticatedImpl( + {this.status = AuthStatus.unauthenticated, + this.isLoading = false, + this.errorMessage}); + + @override + @JsonKey() + final AuthStatus status; + @override + @JsonKey() + final bool isLoading; + @override + final String? errorMessage; + + @override + String toString() { + return 'AuthState.unauthenticated(status: $status, isLoading: $isLoading, errorMessage: $errorMessage)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AuthUnauthenticatedImpl && + (identical(other.status, status) || other.status == status) && + (identical(other.isLoading, isLoading) || + other.isLoading == isLoading) && + (identical(other.errorMessage, errorMessage) || + other.errorMessage == errorMessage)); + } + + @override + int get hashCode => Object.hash(runtimeType, status, isLoading, errorMessage); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$AuthUnauthenticatedImplCopyWith<_$AuthUnauthenticatedImpl> get copyWith => + __$$AuthUnauthenticatedImplCopyWithImpl<_$AuthUnauthenticatedImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + AuthStatus status, bool isLoading, String? errorMessage) + uninitialized, + required TResult Function( + AuthStatus status, bool isLoading, String? errorMessage) + unauthenticated, + required TResult Function(AuthStatus status, Session session, + bool isLoading, String? errorMessage) + authenticated, + required TResult Function( + AuthStatus status, bool isLoading, String? errorMessage) + aal2Requested, + required TResult Function( + AuthStatus status, String url, bool isLoading, String? errorMessage) + locationChangeRequired, + }) { + return unauthenticated(status, isLoading, errorMessage); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(AuthStatus status, bool isLoading, String? errorMessage)? + uninitialized, + TResult? Function(AuthStatus status, bool isLoading, String? errorMessage)? + unauthenticated, + TResult? Function(AuthStatus status, Session session, bool isLoading, + String? errorMessage)? + authenticated, + TResult? Function(AuthStatus status, bool isLoading, String? errorMessage)? + aal2Requested, + TResult? Function(AuthStatus status, String url, bool isLoading, + String? errorMessage)? + locationChangeRequired, + }) { + return unauthenticated?.call(status, isLoading, errorMessage); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(AuthStatus status, bool isLoading, String? errorMessage)? + uninitialized, + TResult Function(AuthStatus status, bool isLoading, String? errorMessage)? + unauthenticated, + TResult Function(AuthStatus status, Session session, bool isLoading, + String? errorMessage)? + authenticated, + TResult Function(AuthStatus status, bool isLoading, String? errorMessage)? + aal2Requested, + TResult Function(AuthStatus status, String url, bool isLoading, + String? errorMessage)? + locationChangeRequired, + required TResult orElse(), + }) { + if (unauthenticated != null) { + return unauthenticated(status, isLoading, errorMessage); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(AuthUninitialized value) uninitialized, + required TResult Function(AuthUnauthenticated value) unauthenticated, + required TResult Function(AuthAuthenticated value) authenticated, + required TResult Function(Aal2Requested value) aal2Requested, + required TResult Function(LocationChangeRequired value) + locationChangeRequired, + }) { + return unauthenticated(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(AuthUninitialized value)? uninitialized, + TResult? Function(AuthUnauthenticated value)? unauthenticated, + TResult? Function(AuthAuthenticated value)? authenticated, + TResult? Function(Aal2Requested value)? aal2Requested, + TResult? Function(LocationChangeRequired value)? locationChangeRequired, + }) { + return unauthenticated?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(AuthUninitialized value)? uninitialized, + TResult Function(AuthUnauthenticated value)? unauthenticated, + TResult Function(AuthAuthenticated value)? authenticated, + TResult Function(Aal2Requested value)? aal2Requested, + TResult Function(LocationChangeRequired value)? locationChangeRequired, + required TResult orElse(), + }) { + if (unauthenticated != null) { + return unauthenticated(this); + } + return orElse(); + } +} + +abstract class AuthUnauthenticated implements AuthState { + const factory AuthUnauthenticated( + {final AuthStatus status, + final bool isLoading, + final String? errorMessage}) = _$AuthUnauthenticatedImpl; + + @override + AuthStatus get status; + @override + bool get isLoading; + @override + String? get errorMessage; + @override + @JsonKey(ignore: true) + _$$AuthUnauthenticatedImplCopyWith<_$AuthUnauthenticatedImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$AuthAuthenticatedImplCopyWith<$Res> + implements $AuthStateCopyWith<$Res> { + factory _$$AuthAuthenticatedImplCopyWith(_$AuthAuthenticatedImpl value, + $Res Function(_$AuthAuthenticatedImpl) then) = + __$$AuthAuthenticatedImplCopyWithImpl<$Res>; @override @useResult $Res call( {AuthStatus status, - Session? session, + Session session, bool isLoading, String? errorMessage}); } /// @nodoc -class __$$_AuthStateCopyWithImpl<$Res> - extends _$AuthStateCopyWithImpl<$Res, _$_AuthState> - implements _$$_AuthStateCopyWith<$Res> { - __$$_AuthStateCopyWithImpl( - _$_AuthState _value, $Res Function(_$_AuthState) _then) +class __$$AuthAuthenticatedImplCopyWithImpl<$Res> + extends _$AuthStateCopyWithImpl<$Res, _$AuthAuthenticatedImpl> + implements _$$AuthAuthenticatedImplCopyWith<$Res> { + __$$AuthAuthenticatedImplCopyWithImpl(_$AuthAuthenticatedImpl _value, + $Res Function(_$AuthAuthenticatedImpl) _then) : super(_value, _then); @pragma('vm:prefer-inline') @override $Res call({ Object? status = null, - Object? session = freezed, + Object? session = null, Object? isLoading = null, Object? errorMessage = freezed, }) { - return _then(_$_AuthState( + return _then(_$AuthAuthenticatedImpl( status: null == status ? _value.status : status // ignore: cast_nullable_to_non_nullable as AuthStatus, - session: freezed == session + session: null == session ? _value.session : session // ignore: cast_nullable_to_non_nullable - as Session?, + as Session, isLoading: null == isLoading ? _value.isLoading : isLoading // ignore: cast_nullable_to_non_nullable @@ -133,17 +627,18 @@ class __$$_AuthStateCopyWithImpl<$Res> /// @nodoc -class _$_AuthState implements _AuthState { - const _$_AuthState( - {required this.status, - this.session, +class _$AuthAuthenticatedImpl implements AuthAuthenticated { + const _$AuthAuthenticatedImpl( + {this.status = AuthStatus.authenticated, + required this.session, this.isLoading = false, this.errorMessage}); @override + @JsonKey() final AuthStatus status; @override - final Session? session; + final Session session; @override @JsonKey() final bool isLoading; @@ -152,14 +647,14 @@ class _$_AuthState implements _AuthState { @override String toString() { - return 'AuthState(status: $status, session: $session, isLoading: $isLoading, errorMessage: $errorMessage)'; + return 'AuthState.authenticated(status: $status, session: $session, isLoading: $isLoading, errorMessage: $errorMessage)'; } @override bool operator ==(dynamic other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$_AuthState && + other is _$AuthAuthenticatedImpl && (identical(other.status, status) || other.status == status) && (identical(other.session, session) || other.session == session) && (identical(other.isLoading, isLoading) || @@ -175,27 +670,568 @@ class _$_AuthState implements _AuthState { @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') - _$$_AuthStateCopyWith<_$_AuthState> get copyWith => - __$$_AuthStateCopyWithImpl<_$_AuthState>(this, _$identity); + _$$AuthAuthenticatedImplCopyWith<_$AuthAuthenticatedImpl> get copyWith => + __$$AuthAuthenticatedImplCopyWithImpl<_$AuthAuthenticatedImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + AuthStatus status, bool isLoading, String? errorMessage) + uninitialized, + required TResult Function( + AuthStatus status, bool isLoading, String? errorMessage) + unauthenticated, + required TResult Function(AuthStatus status, Session session, + bool isLoading, String? errorMessage) + authenticated, + required TResult Function( + AuthStatus status, bool isLoading, String? errorMessage) + aal2Requested, + required TResult Function( + AuthStatus status, String url, bool isLoading, String? errorMessage) + locationChangeRequired, + }) { + return authenticated(status, session, isLoading, errorMessage); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(AuthStatus status, bool isLoading, String? errorMessage)? + uninitialized, + TResult? Function(AuthStatus status, bool isLoading, String? errorMessage)? + unauthenticated, + TResult? Function(AuthStatus status, Session session, bool isLoading, + String? errorMessage)? + authenticated, + TResult? Function(AuthStatus status, bool isLoading, String? errorMessage)? + aal2Requested, + TResult? Function(AuthStatus status, String url, bool isLoading, + String? errorMessage)? + locationChangeRequired, + }) { + return authenticated?.call(status, session, isLoading, errorMessage); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(AuthStatus status, bool isLoading, String? errorMessage)? + uninitialized, + TResult Function(AuthStatus status, bool isLoading, String? errorMessage)? + unauthenticated, + TResult Function(AuthStatus status, Session session, bool isLoading, + String? errorMessage)? + authenticated, + TResult Function(AuthStatus status, bool isLoading, String? errorMessage)? + aal2Requested, + TResult Function(AuthStatus status, String url, bool isLoading, + String? errorMessage)? + locationChangeRequired, + required TResult orElse(), + }) { + if (authenticated != null) { + return authenticated(status, session, isLoading, errorMessage); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(AuthUninitialized value) uninitialized, + required TResult Function(AuthUnauthenticated value) unauthenticated, + required TResult Function(AuthAuthenticated value) authenticated, + required TResult Function(Aal2Requested value) aal2Requested, + required TResult Function(LocationChangeRequired value) + locationChangeRequired, + }) { + return authenticated(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(AuthUninitialized value)? uninitialized, + TResult? Function(AuthUnauthenticated value)? unauthenticated, + TResult? Function(AuthAuthenticated value)? authenticated, + TResult? Function(Aal2Requested value)? aal2Requested, + TResult? Function(LocationChangeRequired value)? locationChangeRequired, + }) { + return authenticated?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(AuthUninitialized value)? uninitialized, + TResult Function(AuthUnauthenticated value)? unauthenticated, + TResult Function(AuthAuthenticated value)? authenticated, + TResult Function(Aal2Requested value)? aal2Requested, + TResult Function(LocationChangeRequired value)? locationChangeRequired, + required TResult orElse(), + }) { + if (authenticated != null) { + return authenticated(this); + } + return orElse(); + } } -abstract class _AuthState implements AuthState { - const factory _AuthState( - {required final AuthStatus status, - final Session? session, +abstract class AuthAuthenticated implements AuthState { + const factory AuthAuthenticated( + {final AuthStatus status, + required final Session session, final bool isLoading, - final String? errorMessage}) = _$_AuthState; + final String? errorMessage}) = _$AuthAuthenticatedImpl; @override AuthStatus get status; + Session get session; + @override + bool get isLoading; + @override + String? get errorMessage; + @override + @JsonKey(ignore: true) + _$$AuthAuthenticatedImplCopyWith<_$AuthAuthenticatedImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$Aal2RequestedImplCopyWith<$Res> + implements $AuthStateCopyWith<$Res> { + factory _$$Aal2RequestedImplCopyWith( + _$Aal2RequestedImpl value, $Res Function(_$Aal2RequestedImpl) then) = + __$$Aal2RequestedImplCopyWithImpl<$Res>; @override - Session? get session; + @useResult + $Res call({AuthStatus status, bool isLoading, String? errorMessage}); +} + +/// @nodoc +class __$$Aal2RequestedImplCopyWithImpl<$Res> + extends _$AuthStateCopyWithImpl<$Res, _$Aal2RequestedImpl> + implements _$$Aal2RequestedImplCopyWith<$Res> { + __$$Aal2RequestedImplCopyWithImpl( + _$Aal2RequestedImpl _value, $Res Function(_$Aal2RequestedImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = null, + Object? isLoading = null, + Object? errorMessage = freezed, + }) { + return _then(_$Aal2RequestedImpl( + status: null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as AuthStatus, + isLoading: null == isLoading + ? _value.isLoading + : isLoading // ignore: cast_nullable_to_non_nullable + as bool, + errorMessage: freezed == errorMessage + ? _value.errorMessage + : errorMessage // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +class _$Aal2RequestedImpl implements Aal2Requested { + const _$Aal2RequestedImpl( + {this.status = AuthStatus.aal2Requested, + this.isLoading = false, + this.errorMessage}); + + @override + @JsonKey() + final AuthStatus status; + @override + @JsonKey() + final bool isLoading; + @override + final String? errorMessage; + + @override + String toString() { + return 'AuthState.aal2Requested(status: $status, isLoading: $isLoading, errorMessage: $errorMessage)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$Aal2RequestedImpl && + (identical(other.status, status) || other.status == status) && + (identical(other.isLoading, isLoading) || + other.isLoading == isLoading) && + (identical(other.errorMessage, errorMessage) || + other.errorMessage == errorMessage)); + } + + @override + int get hashCode => Object.hash(runtimeType, status, isLoading, errorMessage); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$Aal2RequestedImplCopyWith<_$Aal2RequestedImpl> get copyWith => + __$$Aal2RequestedImplCopyWithImpl<_$Aal2RequestedImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + AuthStatus status, bool isLoading, String? errorMessage) + uninitialized, + required TResult Function( + AuthStatus status, bool isLoading, String? errorMessage) + unauthenticated, + required TResult Function(AuthStatus status, Session session, + bool isLoading, String? errorMessage) + authenticated, + required TResult Function( + AuthStatus status, bool isLoading, String? errorMessage) + aal2Requested, + required TResult Function( + AuthStatus status, String url, bool isLoading, String? errorMessage) + locationChangeRequired, + }) { + return aal2Requested(status, isLoading, errorMessage); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(AuthStatus status, bool isLoading, String? errorMessage)? + uninitialized, + TResult? Function(AuthStatus status, bool isLoading, String? errorMessage)? + unauthenticated, + TResult? Function(AuthStatus status, Session session, bool isLoading, + String? errorMessage)? + authenticated, + TResult? Function(AuthStatus status, bool isLoading, String? errorMessage)? + aal2Requested, + TResult? Function(AuthStatus status, String url, bool isLoading, + String? errorMessage)? + locationChangeRequired, + }) { + return aal2Requested?.call(status, isLoading, errorMessage); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(AuthStatus status, bool isLoading, String? errorMessage)? + uninitialized, + TResult Function(AuthStatus status, bool isLoading, String? errorMessage)? + unauthenticated, + TResult Function(AuthStatus status, Session session, bool isLoading, + String? errorMessage)? + authenticated, + TResult Function(AuthStatus status, bool isLoading, String? errorMessage)? + aal2Requested, + TResult Function(AuthStatus status, String url, bool isLoading, + String? errorMessage)? + locationChangeRequired, + required TResult orElse(), + }) { + if (aal2Requested != null) { + return aal2Requested(status, isLoading, errorMessage); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(AuthUninitialized value) uninitialized, + required TResult Function(AuthUnauthenticated value) unauthenticated, + required TResult Function(AuthAuthenticated value) authenticated, + required TResult Function(Aal2Requested value) aal2Requested, + required TResult Function(LocationChangeRequired value) + locationChangeRequired, + }) { + return aal2Requested(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(AuthUninitialized value)? uninitialized, + TResult? Function(AuthUnauthenticated value)? unauthenticated, + TResult? Function(AuthAuthenticated value)? authenticated, + TResult? Function(Aal2Requested value)? aal2Requested, + TResult? Function(LocationChangeRequired value)? locationChangeRequired, + }) { + return aal2Requested?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(AuthUninitialized value)? uninitialized, + TResult Function(AuthUnauthenticated value)? unauthenticated, + TResult Function(AuthAuthenticated value)? authenticated, + TResult Function(Aal2Requested value)? aal2Requested, + TResult Function(LocationChangeRequired value)? locationChangeRequired, + required TResult orElse(), + }) { + if (aal2Requested != null) { + return aal2Requested(this); + } + return orElse(); + } +} + +abstract class Aal2Requested implements AuthState { + const factory Aal2Requested( + {final AuthStatus status, + final bool isLoading, + final String? errorMessage}) = _$Aal2RequestedImpl; + + @override + AuthStatus get status; @override bool get isLoading; @override String? get errorMessage; @override @JsonKey(ignore: true) - _$$_AuthStateCopyWith<_$_AuthState> get copyWith => + _$$Aal2RequestedImplCopyWith<_$Aal2RequestedImpl> get copyWith => throw _privateConstructorUsedError; } + +/// @nodoc +abstract class _$$LocationChangeRequiredImplCopyWith<$Res> + implements $AuthStateCopyWith<$Res> { + factory _$$LocationChangeRequiredImplCopyWith( + _$LocationChangeRequiredImpl value, + $Res Function(_$LocationChangeRequiredImpl) then) = + __$$LocationChangeRequiredImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {AuthStatus status, String url, bool isLoading, String? errorMessage}); +} + +/// @nodoc +class __$$LocationChangeRequiredImplCopyWithImpl<$Res> + extends _$AuthStateCopyWithImpl<$Res, _$LocationChangeRequiredImpl> + implements _$$LocationChangeRequiredImplCopyWith<$Res> { + __$$LocationChangeRequiredImplCopyWithImpl( + _$LocationChangeRequiredImpl _value, + $Res Function(_$LocationChangeRequiredImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = null, + Object? url = null, + Object? isLoading = null, + Object? errorMessage = freezed, + }) { + return _then(_$LocationChangeRequiredImpl( + status: null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as AuthStatus, + url: null == url + ? _value.url + : url // ignore: cast_nullable_to_non_nullable + as String, + isLoading: null == isLoading + ? _value.isLoading + : isLoading // ignore: cast_nullable_to_non_nullable + as bool, + errorMessage: freezed == errorMessage + ? _value.errorMessage + : errorMessage // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +class _$LocationChangeRequiredImpl implements LocationChangeRequired { + const _$LocationChangeRequiredImpl( + {this.status = AuthStatus.locationChangeRequired, + required this.url, + this.isLoading = false, + this.errorMessage}); + + @override + @JsonKey() + final AuthStatus status; + @override + final String url; + @override + @JsonKey() + final bool isLoading; + @override + final String? errorMessage; + + @override + String toString() { + return 'AuthState.locationChangeRequired(status: $status, url: $url, isLoading: $isLoading, errorMessage: $errorMessage)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LocationChangeRequiredImpl && + (identical(other.status, status) || other.status == status) && + (identical(other.url, url) || other.url == url) && + (identical(other.isLoading, isLoading) || + other.isLoading == isLoading) && + (identical(other.errorMessage, errorMessage) || + other.errorMessage == errorMessage)); + } + + @override + int get hashCode => + Object.hash(runtimeType, status, url, isLoading, errorMessage); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$LocationChangeRequiredImplCopyWith<_$LocationChangeRequiredImpl> + get copyWith => __$$LocationChangeRequiredImplCopyWithImpl< + _$LocationChangeRequiredImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + AuthStatus status, bool isLoading, String? errorMessage) + uninitialized, + required TResult Function( + AuthStatus status, bool isLoading, String? errorMessage) + unauthenticated, + required TResult Function(AuthStatus status, Session session, + bool isLoading, String? errorMessage) + authenticated, + required TResult Function( + AuthStatus status, bool isLoading, String? errorMessage) + aal2Requested, + required TResult Function( + AuthStatus status, String url, bool isLoading, String? errorMessage) + locationChangeRequired, + }) { + return locationChangeRequired(status, url, isLoading, errorMessage); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(AuthStatus status, bool isLoading, String? errorMessage)? + uninitialized, + TResult? Function(AuthStatus status, bool isLoading, String? errorMessage)? + unauthenticated, + TResult? Function(AuthStatus status, Session session, bool isLoading, + String? errorMessage)? + authenticated, + TResult? Function(AuthStatus status, bool isLoading, String? errorMessage)? + aal2Requested, + TResult? Function(AuthStatus status, String url, bool isLoading, + String? errorMessage)? + locationChangeRequired, + }) { + return locationChangeRequired?.call(status, url, isLoading, errorMessage); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(AuthStatus status, bool isLoading, String? errorMessage)? + uninitialized, + TResult Function(AuthStatus status, bool isLoading, String? errorMessage)? + unauthenticated, + TResult Function(AuthStatus status, Session session, bool isLoading, + String? errorMessage)? + authenticated, + TResult Function(AuthStatus status, bool isLoading, String? errorMessage)? + aal2Requested, + TResult Function(AuthStatus status, String url, bool isLoading, + String? errorMessage)? + locationChangeRequired, + required TResult orElse(), + }) { + if (locationChangeRequired != null) { + return locationChangeRequired(status, url, isLoading, errorMessage); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(AuthUninitialized value) uninitialized, + required TResult Function(AuthUnauthenticated value) unauthenticated, + required TResult Function(AuthAuthenticated value) authenticated, + required TResult Function(Aal2Requested value) aal2Requested, + required TResult Function(LocationChangeRequired value) + locationChangeRequired, + }) { + return locationChangeRequired(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(AuthUninitialized value)? uninitialized, + TResult? Function(AuthUnauthenticated value)? unauthenticated, + TResult? Function(AuthAuthenticated value)? authenticated, + TResult? Function(Aal2Requested value)? aal2Requested, + TResult? Function(LocationChangeRequired value)? locationChangeRequired, + }) { + return locationChangeRequired?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(AuthUninitialized value)? uninitialized, + TResult Function(AuthUnauthenticated value)? unauthenticated, + TResult Function(AuthAuthenticated value)? authenticated, + TResult Function(Aal2Requested value)? aal2Requested, + TResult Function(LocationChangeRequired value)? locationChangeRequired, + required TResult orElse(), + }) { + if (locationChangeRequired != null) { + return locationChangeRequired(this); + } + return orElse(); + } +} + +abstract class LocationChangeRequired implements AuthState { + const factory LocationChangeRequired( + {final AuthStatus status, + required final String url, + final bool isLoading, + final String? errorMessage}) = _$LocationChangeRequiredImpl; + + @override + AuthStatus get status; + String get url; + @override + bool get isLoading; + @override + String? get errorMessage; + @override + @JsonKey(ignore: true) + _$$LocationChangeRequiredImplCopyWith<_$LocationChangeRequiredImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/flutter-ory-network/lib/blocs/auth/auth_event.dart b/flutter-ory-network/lib/blocs/auth/auth_event.dart index 041a607..199c044 100644 --- a/flutter-ory-network/lib/blocs/auth/auth_event.dart +++ b/flutter-ory-network/lib/blocs/auth/auth_event.dart @@ -19,4 +19,20 @@ final class ChangeAuthStatus extends AuthEvent { final class GetCurrentSessionInformation extends AuthEvent {} +final class RequireLocationChange extends AuthEvent { + final String url; + + RequireLocationChange({required this.url}); + @override + List get props => [url]; +} + +final class AddSession extends AuthEvent { + final Session session; + + AddSession({required this.session}); + @override + List get props => [session]; +} + final class LogOut extends AuthEvent {} diff --git a/flutter-ory-network/lib/blocs/auth/auth_state.dart b/flutter-ory-network/lib/blocs/auth/auth_state.dart index eaa742e..2ec18d8 100644 --- a/flutter-ory-network/lib/blocs/auth/auth_state.dart +++ b/flutter-ory-network/lib/blocs/auth/auth_state.dart @@ -5,9 +5,28 @@ part of 'auth_bloc.dart'; @freezed sealed class AuthState with _$AuthState { - const factory AuthState( - {required AuthStatus status, - Session? session, + const factory AuthState.uninitialized( + {@Default(AuthStatus.uninitialized) AuthStatus status, @Default(false) bool isLoading, - String? errorMessage}) = _AuthState; + String? errorMessage}) = AuthUninitialized; + const factory AuthState.unauthenticated( + {@Default(AuthStatus.unauthenticated) AuthStatus status, + @Default(false) bool isLoading, + String? errorMessage}) = AuthUnauthenticated; + const factory AuthState.authenticated( + {@Default(AuthStatus.authenticated) AuthStatus status, + required Session session, + @Default(false) bool isLoading, + String? errorMessage}) = AuthAuthenticated; + + const factory AuthState.aal2Requested( + {@Default(AuthStatus.aal2Requested) AuthStatus status, + @Default(false) bool isLoading, + String? errorMessage}) = Aal2Requested; + + const factory AuthState.locationChangeRequired( + {@Default(AuthStatus.locationChangeRequired) AuthStatus status, + required String url, + @Default(false) bool isLoading, + String? errorMessage}) = LocationChangeRequired; } diff --git a/flutter-ory-network/lib/blocs/login/login_bloc.dart b/flutter-ory-network/lib/blocs/login/login_bloc.dart index 103b574..a3c7a68 100644 --- a/flutter-ory-network/lib/blocs/login/login_bloc.dart +++ b/flutter-ory-network/lib/blocs/login/login_bloc.dart @@ -21,6 +21,8 @@ class LoginBloc extends Bloc { : super(const LoginState()) { on(_onCreateLoginFlow); on(_onGetLoginFlow); + on(_onExchangesCodeForSessionToken); + on(_onLoginWithWebAuth); on(_onChangeNodeValue); on(_onUpdateLoginFlow); } @@ -53,6 +55,35 @@ class LoginBloc extends Bloc { } } + Future _onLoginWithWebAuth( + LoginWithWebAuth event, Emitter emit) async { + try { + final code = await repository.getWebAuthCode(url: event.url); + add(ExchangeCodesForSessionToken(returnToCode: code)); + } on UnknownException catch (e) { + emit(state.copyWith(isLoading: false, message: e.message)); + } catch (_) { + emit(state.copyWith(isLoading: false)); + } + } + + _onExchangesCodeForSessionToken( + ExchangeCodesForSessionToken event, Emitter emit) async { + try { + emit(state.copyWith(isLoading: true, message: null)); + + final session = await repository.exchangeCodesForSessionToken( + flowId: state.loginFlow?.id, + initCode: state.loginFlow?.sessionTokenExchangeCode!, + returnToCode: event.returnToCode); + authBloc.add(AddSession(session: session)); + } on UnknownException catch (e) { + emit(state.copyWith(isLoading: false, message: e.message)); + } catch (_) { + emit(state.copyWith(isLoading: false)); + } + } + _onChangeNodeValue(ChangeNodeValue event, Emitter emit) { if (state.loginFlow != null) { final newLoginState = repository.changeLoginNodeValue( @@ -64,15 +95,18 @@ class LoginBloc extends Bloc { _onUpdateLoginFlow(UpdateLoginFlow event, Emitter emit) async { try { emit(state.copyWith(isLoading: true, message: null)); - await repository.updateLoginFlow( + final session = await repository.updateLoginFlow( flowId: state.loginFlow!.id, group: event.group, name: event.name, value: event.value, nodes: state.loginFlow!.ui.nodes.toList()); - authBloc.add(ChangeAuthStatus(status: AuthStatus.authenticated)); + authBloc.add(AddSession(session: session)); } on BadRequestException catch (e) { emit(state.copyWith(loginFlow: e.flow, isLoading: false)); + } on LocationChangeRequiredException catch (e) { + emit(state.copyWith(isLoading: false)); + add(LoginWithWebAuth(url: e.url)); } on UnauthorizedException catch (_) { authBloc.add(ChangeAuthStatus(status: AuthStatus.unauthenticated)); } on FlowExpiredException catch (e) { diff --git a/flutter-ory-network/lib/blocs/login/login_bloc.freezed.dart b/flutter-ory-network/lib/blocs/login/login_bloc.freezed.dart index 805f093..52bd212 100644 --- a/flutter-ory-network/lib/blocs/login/login_bloc.freezed.dart +++ b/flutter-ory-network/lib/blocs/login/login_bloc.freezed.dart @@ -72,22 +72,22 @@ class _$LoginStateCopyWithImpl<$Res, $Val extends LoginState> } /// @nodoc -abstract class _$$_LoginStateCopyWith<$Res> +abstract class _$$LoginStateImplCopyWith<$Res> implements $LoginStateCopyWith<$Res> { - factory _$$_LoginStateCopyWith( - _$_LoginState value, $Res Function(_$_LoginState) then) = - __$$_LoginStateCopyWithImpl<$Res>; + factory _$$LoginStateImplCopyWith( + _$LoginStateImpl value, $Res Function(_$LoginStateImpl) then) = + __$$LoginStateImplCopyWithImpl<$Res>; @override @useResult $Res call({LoginFlow? loginFlow, bool isLoading, String? message}); } /// @nodoc -class __$$_LoginStateCopyWithImpl<$Res> - extends _$LoginStateCopyWithImpl<$Res, _$_LoginState> - implements _$$_LoginStateCopyWith<$Res> { - __$$_LoginStateCopyWithImpl( - _$_LoginState _value, $Res Function(_$_LoginState) _then) +class __$$LoginStateImplCopyWithImpl<$Res> + extends _$LoginStateCopyWithImpl<$Res, _$LoginStateImpl> + implements _$$LoginStateImplCopyWith<$Res> { + __$$LoginStateImplCopyWithImpl( + _$LoginStateImpl _value, $Res Function(_$LoginStateImpl) _then) : super(_value, _then); @pragma('vm:prefer-inline') @@ -97,7 +97,7 @@ class __$$_LoginStateCopyWithImpl<$Res> Object? isLoading = null, Object? message = freezed, }) { - return _then(_$_LoginState( + return _then(_$LoginStateImpl( loginFlow: freezed == loginFlow ? _value.loginFlow : loginFlow // ignore: cast_nullable_to_non_nullable @@ -116,8 +116,9 @@ class __$$_LoginStateCopyWithImpl<$Res> /// @nodoc -class _$_LoginState implements _LoginState { - const _$_LoginState({this.loginFlow, this.isLoading = false, this.message}); +class _$LoginStateImpl implements _LoginState { + const _$LoginStateImpl( + {this.loginFlow, this.isLoading = false, this.message}); @override final LoginFlow? loginFlow; @@ -136,7 +137,7 @@ class _$_LoginState implements _LoginState { bool operator ==(dynamic other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$_LoginState && + other is _$LoginStateImpl && (identical(other.loginFlow, loginFlow) || other.loginFlow == loginFlow) && (identical(other.isLoading, isLoading) || @@ -150,15 +151,15 @@ class _$_LoginState implements _LoginState { @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') - _$$_LoginStateCopyWith<_$_LoginState> get copyWith => - __$$_LoginStateCopyWithImpl<_$_LoginState>(this, _$identity); + _$$LoginStateImplCopyWith<_$LoginStateImpl> get copyWith => + __$$LoginStateImplCopyWithImpl<_$LoginStateImpl>(this, _$identity); } abstract class _LoginState implements LoginState { const factory _LoginState( {final LoginFlow? loginFlow, final bool isLoading, - final String? message}) = _$_LoginState; + final String? message}) = _$LoginStateImpl; @override LoginFlow? get loginFlow; @@ -168,6 +169,6 @@ abstract class _LoginState implements LoginState { String? get message; @override @JsonKey(ignore: true) - _$$_LoginStateCopyWith<_$_LoginState> get copyWith => + _$$LoginStateImplCopyWith<_$LoginStateImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/flutter-ory-network/lib/blocs/login/login_event.dart b/flutter-ory-network/lib/blocs/login/login_event.dart index b048895..73c0c4e 100644 --- a/flutter-ory-network/lib/blocs/login/login_event.dart +++ b/flutter-ory-network/lib/blocs/login/login_event.dart @@ -26,6 +26,22 @@ class ChangeNodeValue extends LoginEvent { List get props => [value, name]; } +class ExchangeCodesForSessionToken extends LoginEvent { + final String returnToCode; + + ExchangeCodesForSessionToken({required this.returnToCode}); + @override + List get props => [returnToCode]; +} + +class LoginWithWebAuth extends LoginEvent { + final String url; + + LoginWithWebAuth({required this.url}); + @override + List get props => [url]; +} + class GetLoginFlow extends LoginEvent { final String flowId; diff --git a/flutter-ory-network/lib/blocs/registration/registration_bloc.dart b/flutter-ory-network/lib/blocs/registration/registration_bloc.dart index cc861f0..97cc172 100644 --- a/flutter-ory-network/lib/blocs/registration/registration_bloc.dart +++ b/flutter-ory-network/lib/blocs/registration/registration_bloc.dart @@ -21,6 +21,8 @@ class RegistrationBloc extends Bloc { : super(const RegistrationState()) { on(_onCreateRegistrationFlow); on(_onGetRegistrationFlow); + on(_onRegisterWithWebAuth); + on(_onExchangeCodesForSessionToken); on(_onChangeNodeValue); on(_onUpdateRegistrationFlow); } @@ -62,6 +64,35 @@ class RegistrationBloc extends Bloc { } } + Future _onRegisterWithWebAuth( + RegisterWithWebAuth event, Emitter emit) async { + try { + final code = await repository.getWebAuthCode(url: event.url); + add(ExchangeCodesForSessionToken(returnToCode: code)); + } on UnknownException catch (e) { + emit(state.copyWith(isLoading: false, message: e.message)); + } catch (_) { + emit(state.copyWith(isLoading: false)); + } + } + + _onExchangeCodesForSessionToken(ExchangeCodesForSessionToken event, + Emitter emit) async { + try { + emit(state.copyWith(isLoading: true, message: null)); + + final session = await repository.exchangeCodesForSessionToken( + flowId: state.registrationFlow?.id, + initCode: state.registrationFlow?.sessionTokenExchangeCode, + returnToCode: event.returnToCode); + authBloc.add(AddSession(session: session)); + } on UnknownException catch (e) { + emit(state.copyWith(isLoading: false, message: e.message)); + } catch (_) { + emit(state.copyWith(isLoading: false)); + } + } + Future _onUpdateRegistrationFlow( UpdateRegistrationFlow event, Emitter emit) async { try { @@ -77,6 +108,9 @@ class RegistrationBloc extends Bloc { } } on BadRequestException catch (e) { emit(state.copyWith(registrationFlow: e.flow, isLoading: false)); + } on LocationChangeRequiredException catch (e) { + emit(state.copyWith(isLoading: false)); + add(RegisterWithWebAuth(url: e.url)); } on FlowExpiredException catch (e) { add(GetRegistrationFlow(flowId: e.flowId)); } on UnknownException catch (e) { diff --git a/flutter-ory-network/lib/blocs/registration/registration_bloc.freezed.dart b/flutter-ory-network/lib/blocs/registration/registration_bloc.freezed.dart index f31c6b2..ef634d0 100644 --- a/flutter-ory-network/lib/blocs/registration/registration_bloc.freezed.dart +++ b/flutter-ory-network/lib/blocs/registration/registration_bloc.freezed.dart @@ -73,11 +73,11 @@ class _$RegistrationStateCopyWithImpl<$Res, $Val extends RegistrationState> } /// @nodoc -abstract class _$$_RegistrationStateCopyWith<$Res> +abstract class _$$RegistrationStateImplCopyWith<$Res> implements $RegistrationStateCopyWith<$Res> { - factory _$$_RegistrationStateCopyWith(_$_RegistrationState value, - $Res Function(_$_RegistrationState) then) = - __$$_RegistrationStateCopyWithImpl<$Res>; + factory _$$RegistrationStateImplCopyWith(_$RegistrationStateImpl value, + $Res Function(_$RegistrationStateImpl) then) = + __$$RegistrationStateImplCopyWithImpl<$Res>; @override @useResult $Res call( @@ -85,11 +85,11 @@ abstract class _$$_RegistrationStateCopyWith<$Res> } /// @nodoc -class __$$_RegistrationStateCopyWithImpl<$Res> - extends _$RegistrationStateCopyWithImpl<$Res, _$_RegistrationState> - implements _$$_RegistrationStateCopyWith<$Res> { - __$$_RegistrationStateCopyWithImpl( - _$_RegistrationState _value, $Res Function(_$_RegistrationState) _then) +class __$$RegistrationStateImplCopyWithImpl<$Res> + extends _$RegistrationStateCopyWithImpl<$Res, _$RegistrationStateImpl> + implements _$$RegistrationStateImplCopyWith<$Res> { + __$$RegistrationStateImplCopyWithImpl(_$RegistrationStateImpl _value, + $Res Function(_$RegistrationStateImpl) _then) : super(_value, _then); @pragma('vm:prefer-inline') @@ -99,7 +99,7 @@ class __$$_RegistrationStateCopyWithImpl<$Res> Object? isLoading = null, Object? message = freezed, }) { - return _then(_$_RegistrationState( + return _then(_$RegistrationStateImpl( registrationFlow: freezed == registrationFlow ? _value.registrationFlow : registrationFlow // ignore: cast_nullable_to_non_nullable @@ -118,8 +118,8 @@ class __$$_RegistrationStateCopyWithImpl<$Res> /// @nodoc -class _$_RegistrationState implements _RegistrationState { - const _$_RegistrationState( +class _$RegistrationStateImpl implements _RegistrationState { + const _$RegistrationStateImpl( {this.registrationFlow, this.isLoading = false, this.message}); @override @@ -139,7 +139,7 @@ class _$_RegistrationState implements _RegistrationState { bool operator ==(dynamic other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$_RegistrationState && + other is _$RegistrationStateImpl && (identical(other.registrationFlow, registrationFlow) || other.registrationFlow == registrationFlow) && (identical(other.isLoading, isLoading) || @@ -154,8 +154,8 @@ class _$_RegistrationState implements _RegistrationState { @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') - _$$_RegistrationStateCopyWith<_$_RegistrationState> get copyWith => - __$$_RegistrationStateCopyWithImpl<_$_RegistrationState>( + _$$RegistrationStateImplCopyWith<_$RegistrationStateImpl> get copyWith => + __$$RegistrationStateImplCopyWithImpl<_$RegistrationStateImpl>( this, _$identity); } @@ -163,7 +163,7 @@ abstract class _RegistrationState implements RegistrationState { const factory _RegistrationState( {final RegistrationFlow? registrationFlow, final bool isLoading, - final String? message}) = _$_RegistrationState; + final String? message}) = _$RegistrationStateImpl; @override RegistrationFlow? get registrationFlow; @@ -173,6 +173,6 @@ abstract class _RegistrationState implements RegistrationState { String? get message; @override @JsonKey(ignore: true) - _$$_RegistrationStateCopyWith<_$_RegistrationState> get copyWith => + _$$RegistrationStateImplCopyWith<_$RegistrationStateImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/flutter-ory-network/lib/blocs/registration/registration_event.dart b/flutter-ory-network/lib/blocs/registration/registration_event.dart index 164cac0..3d2c44e 100644 --- a/flutter-ory-network/lib/blocs/registration/registration_event.dart +++ b/flutter-ory-network/lib/blocs/registration/registration_event.dart @@ -19,6 +19,22 @@ final class GetRegistrationFlow extends RegistrationEvent { List get props => [flowId]; } +class ExchangeCodesForSessionToken extends RegistrationEvent { + final String returnToCode; + + ExchangeCodesForSessionToken({required this.returnToCode}); + @override + List get props => [returnToCode]; +} + +class RegisterWithWebAuth extends RegistrationEvent { + final String url; + + RegisterWithWebAuth({required this.url}); + @override + List get props => [url]; +} + class ChangeNodeValue extends RegistrationEvent { final String value; final String name; diff --git a/flutter-ory-network/lib/main.dart b/flutter-ory-network/lib/main.dart index 2ece2df..0dec1c2 100644 --- a/flutter-ory-network/lib/main.dart +++ b/flutter-ory-network/lib/main.dart @@ -6,7 +6,9 @@ import 'package:dio/io.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:google_sign_in/google_sign_in.dart'; import 'package:ory_network_flutter/widgets/ory_theme.dart'; +import 'dart:io' show Platform; import 'blocs/auth/auth_bloc.dart'; import 'pages/home.dart'; @@ -36,7 +38,21 @@ Future main() async { final dio = DioForNative(options); final authService = AuthService(dio); - final authRepository = AuthRepository(service: authService); + +// We use Web Client ID for Android devices as omitting Client ID +// leads to id Token being null. For more information, +// see https://github.com/flutter/flutter/issues/33393#issuecomment-964728679 + final googleSignIn = GoogleSignIn( + clientId: Platform.isAndroid + ? dotenv.get('WEB_CLIENT_ID') + : dotenv.get('IOS_CLIENT_ID'), + scopes: [ + 'email', + 'profile', + 'openid', + ]); + final authRepository = + AuthRepository(googleSignIn: googleSignIn, service: authService); runApp(RepositoryProvider.value( value: authRepository, child: BlocProvider( @@ -81,27 +97,24 @@ class _MyAppViewState extends State { // navigate to pages only when auth status has changed listenWhen: (previous, current) => previous.status != current.status, listener: (context, state) { - switch (state.status) { - case AuthStatus.authenticated: - _navigator.pushAndRemoveUntil( - MaterialPageRoute( - builder: (BuildContext context) => const HomePage()), - (Route route) => false); - case AuthStatus.unauthenticated: - _navigator.pushAndRemoveUntil( - MaterialPageRoute( - builder: (BuildContext context) => - const LoginPage(aal: 'aal1')), - (Route route) => false); - case AuthStatus.aal2Requested: - _navigator.pushAndRemoveUntil( - MaterialPageRoute( - builder: (BuildContext context) => - const LoginPage(aal: 'aal2')), - (Route route) => false); - case AuthStatus.uninitialized: - break; - } + state.mapOrNull(unauthenticated: (_) { + _navigator.pushAndRemoveUntil( + MaterialPageRoute( + builder: (BuildContext context) => + const LoginPage(aal: 'aal1')), + (Route route) => false); + }, authenticated: (_) { + _navigator.pushAndRemoveUntil( + MaterialPageRoute( + builder: (BuildContext context) => const HomePage()), + (Route route) => false); + }, aal2Requested: (_) { + _navigator.pushAndRemoveUntil( + MaterialPageRoute( + builder: (BuildContext context) => + const LoginPage(aal: 'aal2')), + (Route route) => false); + }); }, child: child, ); diff --git a/flutter-ory-network/lib/pages/home.dart b/flutter-ory-network/lib/pages/home.dart index af459fe..102b3c9 100644 --- a/flutter-ory-network/lib/pages/home.dart +++ b/flutter-ory-network/lib/pages/home.dart @@ -36,7 +36,6 @@ class _HomePageState extends State { width: 40, ), elevation: 0, - //shape: Border(bottom: BorderSide(color: Colors.green, width: 1)), actions: [ // if auth is loading, show progress indicator, otherwise a logout icon isLoading @@ -61,10 +60,11 @@ class _HomePageState extends State { body: BlocBuilder( bloc: context.read(), builder: (BuildContext context, AuthState state) { - if (state.session != null) { - return _buildSessionInformation(context, state.session!); - } else { - return _buildSessionNotFetched(context, state); + switch (state) { + case AuthAuthenticated(session: var session): + return _buildSessionInformation(context, session); + default: + return _buildSessionNotFetched(context, state); } }, )); @@ -99,24 +99,6 @@ class _HomePageState extends State { fontWeight: FontWeight.w500, color: Colors.white)), ), ), - const Padding( - padding: EdgeInsets.only(top: 15.0), - child: - Text("You are signed in using an ORY Kratos Session Token:"), - ), - Padding( - padding: const EdgeInsets.only(top: 15.0), - child: Container( - padding: const EdgeInsets.all(35), - width: double.infinity, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - color: Colors.grey[600]), - child: Text(session.id, - style: const TextStyle( - fontWeight: FontWeight.w500, color: Colors.white)), - ), - ), const Padding( padding: EdgeInsets.only(top: 15.0), child: Text( diff --git a/flutter-ory-network/lib/pages/login.dart b/flutter-ory-network/lib/pages/login.dart index 6227a3a..e5790cd 100644 --- a/flutter-ory-network/lib/pages/login.dart +++ b/flutter-ory-network/lib/pages/login.dart @@ -1,10 +1,11 @@ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:ory_client/ory_client.dart'; -import 'package:ory_network_flutter/widgets/nodes/provider.dart'; import '../blocs/auth/auth_bloc.dart'; import '../blocs/login/login_bloc.dart'; @@ -51,8 +52,20 @@ class LoginForm extends StatelessWidget { final nodes = state.loginFlow!.ui.nodes; // get default nodes from all nodes - final defaultNodes = - nodes.where((node) => node.group == UiNodeGroupEnum.default_).toList(); + final defaultNodes = nodes.where((node) { + if (node.group == UiNodeGroupEnum.default_) { + if (node.attributes.oneOf.isType(UiNodeInputAttributes)) { + final attributes = + node.attributes.oneOf.value as UiNodeInputAttributes; + if (attributes.type == UiNodeInputAttributesTypeEnum.hidden) { + return false; + } else { + return true; + } + } + } + return false; + }).toList(); // get password nodes from all nodes final passwordNodes = @@ -68,8 +81,18 @@ class LoginForm extends StatelessWidget { nodes.where((node) => node.group == UiNodeGroupEnum.totp).toList(); // get oidc nodes from all nodes - final oidcNodes = - nodes.where((node) => node.group == UiNodeGroupEnum.oidc).toList(); + final oidcNodes = nodes.where((node) { + if (node.group == UiNodeGroupEnum.oidc) { + if (node.attributes.oneOf.isType(UiNodeInputAttributes)) { + final attributes = + node.attributes.oneOf.value as UiNodeInputAttributes; + return Platform.isAndroid + ? !attributes.value!.asString.contains('ios') + : attributes.value!.asString.contains('ios'); + } + } + return false; + }).toList(); return Stack(children: [ Padding( @@ -109,14 +132,8 @@ class LoginForm extends StatelessWidget { ), if (oidcNodes.isNotEmpty) - Padding( - padding: const EdgeInsets.only(top: 32.0), - child: Column( - mainAxisSize: MainAxisSize.max, - children: oidcNodes - .map((node) => SocialProviderInput(node: node)) - .toList()), - ), + buildGroup(context, UiNodeGroupEnum.oidc, oidcNodes, + _onInputChange, _onInputSubmit), if (defaultNodes.isNotEmpty) buildGroup(context, UiNodeGroupEnum.default_, defaultNodes, _onInputChange, _onInputSubmit), diff --git a/flutter-ory-network/lib/pages/registration.dart b/flutter-ory-network/lib/pages/registration.dart index adedae7..b0ad52c 100644 --- a/flutter-ory-network/lib/pages/registration.dart +++ b/flutter-ory-network/lib/pages/registration.dart @@ -1,6 +1,8 @@ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 +import 'dart:io' show Platform; + import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:ory_client/ory_client.dart'; @@ -9,7 +11,6 @@ import '../blocs/auth/auth_bloc.dart'; import '../blocs/registration/registration_bloc.dart'; import '../repositories/auth.dart'; import '../widgets/helpers.dart'; -import '../widgets/nodes/provider.dart'; import 'login.dart'; class RegistrationPage extends StatelessWidget { @@ -57,8 +58,20 @@ class RegistrationFormState extends State { final nodes = state.registrationFlow!.ui.nodes; // get default nodes from all nodes - final defaultNodes = - nodes.where((node) => node.group == UiNodeGroupEnum.default_).toList(); + final defaultNodes = nodes.where((node) { + if (node.group == UiNodeGroupEnum.default_) { + if (node.attributes.oneOf.isType(UiNodeInputAttributes)) { + final attributes = + node.attributes.oneOf.value as UiNodeInputAttributes; + if (attributes.type == UiNodeInputAttributesTypeEnum.hidden) { + return false; + } else { + return true; + } + } + } + return false; + }).toList(); // get password nodes from all nodes final passwordNodes = @@ -74,8 +87,18 @@ class RegistrationFormState extends State { nodes.where((node) => node.group == UiNodeGroupEnum.totp).toList(); // get oidc nodes from all nodes - final oidcNodes = - nodes.where((node) => node.group == UiNodeGroupEnum.oidc).toList(); + final oidcNodes = nodes.where((node) { + if (node.group == UiNodeGroupEnum.oidc) { + if (node.attributes.oneOf.isType(UiNodeInputAttributes)) { + final attributes = + node.attributes.oneOf.value as UiNodeInputAttributes; + return Platform.isAndroid + ? !attributes.value!.asString.contains('ios') + : attributes.value!.asString.contains('ios'); + } + } + return false; + }).toList(); return Stack(children: [ Padding( @@ -103,14 +126,8 @@ class RegistrationFormState extends State { style: TextStyle( fontWeight: FontWeight.w600, height: 1.5, fontSize: 18)), if (oidcNodes.isNotEmpty) - Padding( - padding: const EdgeInsets.only(top: 32.0), - child: Column( - mainAxisSize: MainAxisSize.max, - children: oidcNodes - .map((node) => SocialProviderInput(node: node)) - .toList()), - ), + buildGroup(context, UiNodeGroupEnum.oidc, + oidcNodes, _onInputChange, _onInputSubmit), if (defaultNodes.isNotEmpty) buildGroup(context, UiNodeGroupEnum.default_, defaultNodes, _onInputChange, _onInputSubmit), diff --git a/flutter-ory-network/lib/repositories/auth.dart b/flutter-ory-network/lib/repositories/auth.dart index a482b94..6923006 100644 --- a/flutter-ory-network/lib/repositories/auth.dart +++ b/flutter-ory-network/lib/repositories/auth.dart @@ -1,21 +1,37 @@ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 +import 'dart:convert'; +import 'dart:io' show Platform; + import 'package:built_collection/built_collection.dart'; import 'package:built_value/json_object.dart'; +import 'package:crypto/crypto.dart'; import 'package:deep_collection/deep_collection.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_web_auth/flutter_web_auth.dart'; +import 'package:google_sign_in/google_sign_in.dart'; import 'package:one_of/one_of.dart'; import 'package:ory_client/ory_client.dart'; import 'package:collection/collection.dart'; +import 'package:ory_network_flutter/services/exceptions.dart'; +import 'package:sign_in_with_apple/sign_in_with_apple.dart'; import '../services/auth.dart'; -enum AuthStatus { uninitialized, authenticated, unauthenticated, aal2Requested } +enum AuthStatus { + uninitialized, + authenticated, + unauthenticated, + aal2Requested, + locationChangeRequired +} class AuthRepository { final AuthService service; + final GoogleSignIn googleSignIn; - AuthRepository({required this.service}); + AuthRepository({required this.googleSignIn, required this.service}); Future getCurrentSessionInformation() async { final session = await service.getCurrentSessionInformation(); @@ -46,34 +62,151 @@ class AuthRepository { await service.logout(); } - Future updateRegistrationFlow( + Future getWebAuthCode({required String url}) async { + try { + final result = + await FlutterWebAuth.authenticate(url: url, callbackUrlScheme: 'ory'); + // get return to code + final code = Uri.parse(result).queryParameters['code']; + if (code != null) { + return code; + } else { + throw const CustomException.unknown(); + } + } on PlatformException catch (_) { + throw const CustomException.unknown(); + } + } + + Future exchangeCodesForSessionToken( + {String? flowId, String? initCode, String? returnToCode}) async { + if (flowId != null && initCode != null && returnToCode != null) { + final session = service.exchangeCodesForSessionToken( + flowId: flowId, initCode: initCode, returnToCode: returnToCode); + return session; + } else { + throw const CustomException.unknown(); + } + } + + Future updateRegistrationFlow( {required String flowId, required UiNodeGroupEnum group, required String name, required String value, required List nodes}) async { // create request body - final body = _createRequestBody( + var body = _createRequestBody( group: group, name: name, value: value, nodes: nodes); - // submit registration - final registration = await service.updateRegistrationFlow( + // user used social sign in + if (group == UiNodeGroupEnum.oidc) { + if (value.contains('google')) { + final idToken = await _loginWithGoogle(); + // update body with id token and nonce + _addIdTokenAndNonceToBody(idToken, body); + } + if (value.contains('apple')) { + final idToken = + Platform.isAndroid ? await null : await _logInWithAppleOnIOS(); + // update body with id token and nonce + if (idToken != null) { + _addIdTokenAndNonceToBody(idToken, body); + } + } + } + // submit registration and retrieve session + final session = await service.updateRegistrationFlow( flowId: flowId, group: group, value: body); - return registration; + return session; } - Future updateLoginFlow( + Future updateLoginFlow( {required String flowId, required UiNodeGroupEnum group, required String name, required String value, required List nodes}) async { // create request body - final body = _createRequestBody( + var body = _createRequestBody( group: group, name: name, value: value, nodes: nodes); + // user used social sign in + if (group == UiNodeGroupEnum.oidc) { + if (value.contains('google')) { + final idToken = await _loginWithGoogle(); + // update body with id token and nonce + _addIdTokenAndNonceToBody(idToken, body); + } + if (value.contains('apple')) { + final idToken = + Platform.isAndroid ? null : await _logInWithAppleOnIOS(); + // update body with id token and nonce + if (idToken != null) { + _addIdTokenAndNonceToBody(idToken, body); + } + } + } // submit login final login = await service.updateLoginFlow( flowId: flowId, group: group, value: body); - return login; + return login.session; + } + + Map _getMapFromJWT(String splittedToken) { + String normalizedSource = base64Url.normalize(splittedToken); + return jsonDecode(utf8.decode(base64Url.decode(normalizedSource))); + } + + _addIdTokenAndNonceToBody(String idToken, Map body) { + // get nonce value from id token + final jwtParts = idToken.split('.'); + final jwt = _getMapFromJWT(jwtParts[1]); + // add id token and nonce to body + body.addAll({'id_token': idToken, 'nonce': jwt['nonce']}); + } + + Future _loginWithGoogle() async { + try { + final GoogleSignInAccount? account = await googleSignIn.signIn(); + + final GoogleSignInAuthentication? googleAuth = + await account?.authentication; + if (googleAuth?.idToken != null) { + return googleAuth!.idToken!; + } else { + throw const CustomException.unknown(); + } + } catch (e) { + throw const CustomException.unknown(); + } + } + + Future _logInWithAppleOnIOS() async { + try { + //Check if Apple SignIn is available for the device or not + if (await SignInWithApple.isAvailable()) { + // create nonce + final rawNonce = generateNonce(); + final nonceInBytes = utf8.encode(rawNonce); + final nonce = sha256.convert(nonceInBytes); + // login + final AuthorizationCredentialAppleID credential = + await SignInWithApple.getAppleIDCredential( + scopes: [AppleIDAuthorizationScopes.email], + nonce: nonce.toString(), + ); + + if (credential.identityToken != null) { + return credential.identityToken!; + } else { + throw const CustomException.unknown(); + } + } else { + throw const CustomException.unknown( + message: 'Sign in with Apple is not available on your device'); + } + } catch (e) { + throw const CustomException.unknown(); + } } Map _createRequestBody( diff --git a/flutter-ory-network/lib/services/auth.dart b/flutter-ory-network/lib/services/auth.dart index 2917ed5..a5e0774 100644 --- a/flutter-ory-network/lib/services/auth.dart +++ b/flutter-ory-network/lib/services/auth.dart @@ -46,8 +46,11 @@ class AuthService { Future createLoginFlow({required String aal}) async { try { final token = await storage.getToken(); - final response = - await _ory.createNativeLoginFlow(aal: aal, xSessionToken: token); + final response = await _ory.createNativeLoginFlow( + aal: aal, + xSessionToken: token, + returnSessionTokenExchangeCode: true, + returnTo: 'ory://flutter-ory-network'); if (response.data != null) { // return flow id return response.data!; @@ -62,7 +65,9 @@ class AuthService { /// Create registration flow Future createRegistrationFlow() async { try { - final response = await _ory.createNativeRegistrationFlow(); + final response = await _ory.createNativeRegistrationFlow( + returnSessionTokenExchangeCode: true, + returnTo: 'ory://flutter-ory-network'); if (response.data != null) { // return flow id return response.data!; @@ -102,6 +107,13 @@ class AuthService { value: UpdateLoginFlowWithTotpMethod((b) => b ..method = group.name ..totpCode = value['totp_code'])); + case UiNodeGroupEnum.oidc: + oneOf = OneOf.fromValue1( + value: UpdateLoginFlowWithOidcMethod((b) => b + ..method = group.name + ..provider = value['provider'] + ..idToken = value['id_token'] + ..idTokenNonce = value['nonce'])); // if method is not implemented, throw exception default: @@ -141,6 +153,13 @@ class AuthService { // settings flow expired, use new flow id throw CustomException.flowExpired( flowId: e.response?.data['use_flow_id']); + } else if (e.response?.statusCode == 422) { + final error = e.response?.data['error']; + if (error['id'] == 'browser_location_change_required') { + throw CustomException.locationChangeRequired( + url: e.response?.data['redirect_browser_to']); + } + throw const CustomException.unknown(); } else { throw _handleUnknownException(e.response?.data); } @@ -179,8 +198,29 @@ class AuthService { } } + /// When using oidc flow with [flowId], excange [initCode] and [returnToCode] for a session token + Future exchangeCodesForSessionToken( + {required String flowId, + required String initCode, + required String returnToCode}) async { + try { + final response = await _ory.exchangeSessionToken( + initCode: initCode, returnToCode: returnToCode); + + if (response.data?.session != null) { + // save session token after successful login + await storage.persistToken(response.data!.sessionToken!); + return response.data!.session; + } else { + throw const CustomException.unknown(); + } + } on DioException catch (e) { + throw _handleUnknownException(e.response?.data); + } + } + /// Update registration flow with [flowId] for [group] with [value] - Future updateRegistrationFlow( + Future updateRegistrationFlow( {required String flowId, required UiNodeGroupEnum group, required Map value}) async { @@ -195,6 +235,13 @@ class AuthService { ..method = group.name ..traits = JsonObject(value['traits']) ..password = value['password'])); + case UiNodeGroupEnum.oidc: + oneOf = OneOf.fromValue1( + value: UpdateRegistrationFlowWithOidcMethod((b) => b + ..method = group.name + ..provider = value['provider'] + ..idToken = value['id_token'] + ..idTokenNonce = value['nonce'])); // if method is not implemented, throw exception default: @@ -209,12 +256,24 @@ class AuthService { if (response.data?.session != null) { // save session token after successful login await storage.persistToken(response.data!.sessionToken!); - return response.data!; + return response.data!.session!; } else { throw const CustomException.unknown(); } + // dio throws exception 200 when logging in with google } on DioException catch (e) { - if (e.response?.statusCode == 400) { + // user tried to register with social sign in using already existing account + if (e.response?.statusCode == 200) { + final successfulLogin = standardSerializers.deserializeWith( + SuccessfulNativeLogin.serializer, e.response?.data); + if (successfulLogin?.session != null) { + // save session token after successful login + await storage.persistToken(successfulLogin!.sessionToken!); + return successfulLogin.session; + } else { + throw const CustomException.unknown(); + } + } else if (e.response?.statusCode == 400) { final registrationFlow = standardSerializers.deserializeWith( RegistrationFlow.serializer, e.response?.data); if (registrationFlow != null) { @@ -227,9 +286,18 @@ class AuthService { // settings flow expired, use new flow id and add error message throw CustomException.flowExpired( flowId: e.response?.data['use_flow_id']); + } else if (e.response?.statusCode == 422) { + final error = e.response?.data['error']; + if (error['id'] == 'browser_location_change_required') { + throw CustomException.locationChangeRequired( + url: e.response?.data['redirect_browser_to']); + } + throw const CustomException.unknown(); } else { throw _handleUnknownException(e.response?.data); } + } catch (e) { + throw const CustomException.unknown(); } } diff --git a/flutter-ory-network/lib/services/exceptions.dart b/flutter-ory-network/lib/services/exceptions.dart index 677d5b9..13de1e1 100644 --- a/flutter-ory-network/lib/services/exceptions.dart +++ b/flutter-ory-network/lib/services/exceptions.dart @@ -17,6 +17,8 @@ sealed class CustomException with _$CustomException { {required String flowId, String? message}) = FlowExpiredException; const factory CustomException.twoFactorAuthRequired({Session? session}) = TwoFactorAuthRequiredException; + const factory CustomException.locationChangeRequired({required String url}) = + LocationChangeRequiredException; const factory CustomException.unknown( {@Default('An error occured. Please try again later.') String? message}) = UnknownException; diff --git a/flutter-ory-network/lib/services/exceptions.freezed.dart b/flutter-ory-network/lib/services/exceptions.freezed.dart index f7a7d4b..692afea 100644 --- a/flutter-ory-network/lib/services/exceptions.freezed.dart +++ b/flutter-ory-network/lib/services/exceptions.freezed.dart @@ -25,6 +25,7 @@ mixin _$CustomException { required TResult Function() unauthorized, required TResult Function(String flowId, String? message) flowExpired, required TResult Function(Session? session) twoFactorAuthRequired, + required TResult Function(String url) locationChangeRequired, required TResult Function(String? message) unknown, }) => throw _privateConstructorUsedError; @@ -34,6 +35,7 @@ mixin _$CustomException { TResult? Function()? unauthorized, TResult? Function(String flowId, String? message)? flowExpired, TResult? Function(Session? session)? twoFactorAuthRequired, + TResult? Function(String url)? locationChangeRequired, TResult? Function(String? message)? unknown, }) => throw _privateConstructorUsedError; @@ -43,6 +45,7 @@ mixin _$CustomException { TResult Function()? unauthorized, TResult Function(String flowId, String? message)? flowExpired, TResult Function(Session? session)? twoFactorAuthRequired, + TResult Function(String url)? locationChangeRequired, TResult Function(String? message)? unknown, required TResult orElse(), }) => @@ -54,6 +57,8 @@ mixin _$CustomException { required TResult Function(FlowExpiredException value) flowExpired, required TResult Function(TwoFactorAuthRequiredException value) twoFactorAuthRequired, + required TResult Function(LocationChangeRequiredException value) + locationChangeRequired, required TResult Function(UnknownException value) unknown, }) => throw _privateConstructorUsedError; @@ -64,6 +69,8 @@ mixin _$CustomException { TResult? Function(FlowExpiredException value)? flowExpired, TResult? Function(TwoFactorAuthRequiredException value)? twoFactorAuthRequired, + TResult? Function(LocationChangeRequiredException value)? + locationChangeRequired, TResult? Function(UnknownException value)? unknown, }) => throw _privateConstructorUsedError; @@ -74,6 +81,8 @@ mixin _$CustomException { TResult Function(FlowExpiredException value)? flowExpired, TResult Function(TwoFactorAuthRequiredException value)? twoFactorAuthRequired, + TResult Function(LocationChangeRequiredException value)? + locationChangeRequired, TResult Function(UnknownException value)? unknown, required TResult orElse(), }) => @@ -99,20 +108,20 @@ class _$CustomExceptionCopyWithImpl> } /// @nodoc -abstract class _$$BadRequestExceptionCopyWith { - factory _$$BadRequestExceptionCopyWith(_$BadRequestException value, - $Res Function(_$BadRequestException) then) = - __$$BadRequestExceptionCopyWithImpl; +abstract class _$$BadRequestExceptionImplCopyWith { + factory _$$BadRequestExceptionImplCopyWith(_$BadRequestExceptionImpl value, + $Res Function(_$BadRequestExceptionImpl) then) = + __$$BadRequestExceptionImplCopyWithImpl; @useResult $Res call({T flow}); } /// @nodoc -class __$$BadRequestExceptionCopyWithImpl - extends _$CustomExceptionCopyWithImpl> - implements _$$BadRequestExceptionCopyWith { - __$$BadRequestExceptionCopyWithImpl(_$BadRequestException _value, - $Res Function(_$BadRequestException) _then) +class __$$BadRequestExceptionImplCopyWithImpl + extends _$CustomExceptionCopyWithImpl> + implements _$$BadRequestExceptionImplCopyWith { + __$$BadRequestExceptionImplCopyWithImpl(_$BadRequestExceptionImpl _value, + $Res Function(_$BadRequestExceptionImpl) _then) : super(_value, _then); @pragma('vm:prefer-inline') @@ -120,7 +129,7 @@ class __$$BadRequestExceptionCopyWithImpl $Res call({ Object? flow = freezed, }) { - return _then(_$BadRequestException( + return _then(_$BadRequestExceptionImpl( flow: freezed == flow ? _value.flow : flow // ignore: cast_nullable_to_non_nullable @@ -131,9 +140,9 @@ class __$$BadRequestExceptionCopyWithImpl /// @nodoc -class _$BadRequestException extends BadRequestException +class _$BadRequestExceptionImpl extends BadRequestException with DiagnosticableTreeMixin { - const _$BadRequestException({required this.flow}) : super._(); + const _$BadRequestExceptionImpl({required this.flow}) : super._(); @override final T flow; @@ -155,7 +164,7 @@ class _$BadRequestException extends BadRequestException bool operator ==(dynamic other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$BadRequestException && + other is _$BadRequestExceptionImpl && const DeepCollectionEquality().equals(other.flow, flow)); } @@ -166,9 +175,9 @@ class _$BadRequestException extends BadRequestException @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') - _$$BadRequestExceptionCopyWith> get copyWith => - __$$BadRequestExceptionCopyWithImpl>( - this, _$identity); + _$$BadRequestExceptionImplCopyWith> + get copyWith => __$$BadRequestExceptionImplCopyWithImpl>(this, _$identity); @override @optionalTypeArgs @@ -177,6 +186,7 @@ class _$BadRequestException extends BadRequestException required TResult Function() unauthorized, required TResult Function(String flowId, String? message) flowExpired, required TResult Function(Session? session) twoFactorAuthRequired, + required TResult Function(String url) locationChangeRequired, required TResult Function(String? message) unknown, }) { return badRequest(flow); @@ -189,6 +199,7 @@ class _$BadRequestException extends BadRequestException TResult? Function()? unauthorized, TResult? Function(String flowId, String? message)? flowExpired, TResult? Function(Session? session)? twoFactorAuthRequired, + TResult? Function(String url)? locationChangeRequired, TResult? Function(String? message)? unknown, }) { return badRequest?.call(flow); @@ -201,6 +212,7 @@ class _$BadRequestException extends BadRequestException TResult Function()? unauthorized, TResult Function(String flowId, String? message)? flowExpired, TResult Function(Session? session)? twoFactorAuthRequired, + TResult Function(String url)? locationChangeRequired, TResult Function(String? message)? unknown, required TResult orElse(), }) { @@ -218,6 +230,8 @@ class _$BadRequestException extends BadRequestException required TResult Function(FlowExpiredException value) flowExpired, required TResult Function(TwoFactorAuthRequiredException value) twoFactorAuthRequired, + required TResult Function(LocationChangeRequiredException value) + locationChangeRequired, required TResult Function(UnknownException value) unknown, }) { return badRequest(this); @@ -231,6 +245,8 @@ class _$BadRequestException extends BadRequestException TResult? Function(FlowExpiredException value)? flowExpired, TResult? Function(TwoFactorAuthRequiredException value)? twoFactorAuthRequired, + TResult? Function(LocationChangeRequiredException value)? + locationChangeRequired, TResult? Function(UnknownException value)? unknown, }) { return badRequest?.call(this); @@ -244,6 +260,8 @@ class _$BadRequestException extends BadRequestException TResult Function(FlowExpiredException value)? flowExpired, TResult Function(TwoFactorAuthRequiredException value)? twoFactorAuthRequired, + TResult Function(LocationChangeRequiredException value)? + locationChangeRequired, TResult Function(UnknownException value)? unknown, required TResult orElse(), }) { @@ -256,36 +274,39 @@ class _$BadRequestException extends BadRequestException abstract class BadRequestException extends CustomException { const factory BadRequestException({required final T flow}) = - _$BadRequestException; + _$BadRequestExceptionImpl; const BadRequestException._() : super._(); T get flow; @JsonKey(ignore: true) - _$$BadRequestExceptionCopyWith> get copyWith => - throw _privateConstructorUsedError; + _$$BadRequestExceptionImplCopyWith> + get copyWith => throw _privateConstructorUsedError; } /// @nodoc -abstract class _$$UnauthorizedExceptionCopyWith { - factory _$$UnauthorizedExceptionCopyWith(_$UnauthorizedException value, - $Res Function(_$UnauthorizedException) then) = - __$$UnauthorizedExceptionCopyWithImpl; +abstract class _$$UnauthorizedExceptionImplCopyWith { + factory _$$UnauthorizedExceptionImplCopyWith( + _$UnauthorizedExceptionImpl value, + $Res Function(_$UnauthorizedExceptionImpl) then) = + __$$UnauthorizedExceptionImplCopyWithImpl; } /// @nodoc -class __$$UnauthorizedExceptionCopyWithImpl - extends _$CustomExceptionCopyWithImpl> - implements _$$UnauthorizedExceptionCopyWith { - __$$UnauthorizedExceptionCopyWithImpl(_$UnauthorizedException _value, - $Res Function(_$UnauthorizedException) _then) +class __$$UnauthorizedExceptionImplCopyWithImpl + extends _$CustomExceptionCopyWithImpl> + implements _$$UnauthorizedExceptionImplCopyWith { + __$$UnauthorizedExceptionImplCopyWithImpl( + _$UnauthorizedExceptionImpl _value, + $Res Function(_$UnauthorizedExceptionImpl) _then) : super(_value, _then); } /// @nodoc -class _$UnauthorizedException extends UnauthorizedException +class _$UnauthorizedExceptionImpl extends UnauthorizedException with DiagnosticableTreeMixin { - const _$UnauthorizedException() : super._(); + const _$UnauthorizedExceptionImpl() : super._(); @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { @@ -303,7 +324,7 @@ class _$UnauthorizedException extends UnauthorizedException bool operator ==(dynamic other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$UnauthorizedException); + other is _$UnauthorizedExceptionImpl); } @override @@ -316,6 +337,7 @@ class _$UnauthorizedException extends UnauthorizedException required TResult Function() unauthorized, required TResult Function(String flowId, String? message) flowExpired, required TResult Function(Session? session) twoFactorAuthRequired, + required TResult Function(String url) locationChangeRequired, required TResult Function(String? message) unknown, }) { return unauthorized(); @@ -328,6 +350,7 @@ class _$UnauthorizedException extends UnauthorizedException TResult? Function()? unauthorized, TResult? Function(String flowId, String? message)? flowExpired, TResult? Function(Session? session)? twoFactorAuthRequired, + TResult? Function(String url)? locationChangeRequired, TResult? Function(String? message)? unknown, }) { return unauthorized?.call(); @@ -340,6 +363,7 @@ class _$UnauthorizedException extends UnauthorizedException TResult Function()? unauthorized, TResult Function(String flowId, String? message)? flowExpired, TResult Function(Session? session)? twoFactorAuthRequired, + TResult Function(String url)? locationChangeRequired, TResult Function(String? message)? unknown, required TResult orElse(), }) { @@ -357,6 +381,8 @@ class _$UnauthorizedException extends UnauthorizedException required TResult Function(FlowExpiredException value) flowExpired, required TResult Function(TwoFactorAuthRequiredException value) twoFactorAuthRequired, + required TResult Function(LocationChangeRequiredException value) + locationChangeRequired, required TResult Function(UnknownException value) unknown, }) { return unauthorized(this); @@ -370,6 +396,8 @@ class _$UnauthorizedException extends UnauthorizedException TResult? Function(FlowExpiredException value)? flowExpired, TResult? Function(TwoFactorAuthRequiredException value)? twoFactorAuthRequired, + TResult? Function(LocationChangeRequiredException value)? + locationChangeRequired, TResult? Function(UnknownException value)? unknown, }) { return unauthorized?.call(this); @@ -383,6 +411,8 @@ class _$UnauthorizedException extends UnauthorizedException TResult Function(FlowExpiredException value)? flowExpired, TResult Function(TwoFactorAuthRequiredException value)? twoFactorAuthRequired, + TResult Function(LocationChangeRequiredException value)? + locationChangeRequired, TResult Function(UnknownException value)? unknown, required TResult orElse(), }) { @@ -394,25 +424,27 @@ class _$UnauthorizedException extends UnauthorizedException } abstract class UnauthorizedException extends CustomException { - const factory UnauthorizedException() = _$UnauthorizedException; + const factory UnauthorizedException() = _$UnauthorizedExceptionImpl; const UnauthorizedException._() : super._(); } /// @nodoc -abstract class _$$FlowExpiredExceptionCopyWith { - factory _$$FlowExpiredExceptionCopyWith(_$FlowExpiredException value, - $Res Function(_$FlowExpiredException) then) = - __$$FlowExpiredExceptionCopyWithImpl; +abstract class _$$FlowExpiredExceptionImplCopyWith { + factory _$$FlowExpiredExceptionImplCopyWith( + _$FlowExpiredExceptionImpl value, + $Res Function(_$FlowExpiredExceptionImpl) then) = + __$$FlowExpiredExceptionImplCopyWithImpl; @useResult $Res call({String flowId, String? message}); } /// @nodoc -class __$$FlowExpiredExceptionCopyWithImpl - extends _$CustomExceptionCopyWithImpl> - implements _$$FlowExpiredExceptionCopyWith { - __$$FlowExpiredExceptionCopyWithImpl(_$FlowExpiredException _value, - $Res Function(_$FlowExpiredException) _then) +class __$$FlowExpiredExceptionImplCopyWithImpl + extends _$CustomExceptionCopyWithImpl> + implements _$$FlowExpiredExceptionImplCopyWith { + __$$FlowExpiredExceptionImplCopyWithImpl(_$FlowExpiredExceptionImpl _value, + $Res Function(_$FlowExpiredExceptionImpl) _then) : super(_value, _then); @pragma('vm:prefer-inline') @@ -421,7 +453,7 @@ class __$$FlowExpiredExceptionCopyWithImpl Object? flowId = null, Object? message = freezed, }) { - return _then(_$FlowExpiredException( + return _then(_$FlowExpiredExceptionImpl( flowId: null == flowId ? _value.flowId : flowId // ignore: cast_nullable_to_non_nullable @@ -436,9 +468,9 @@ class __$$FlowExpiredExceptionCopyWithImpl /// @nodoc -class _$FlowExpiredException extends FlowExpiredException +class _$FlowExpiredExceptionImpl extends FlowExpiredException with DiagnosticableTreeMixin { - const _$FlowExpiredException({required this.flowId, this.message}) + const _$FlowExpiredExceptionImpl({required this.flowId, this.message}) : super._(); @override @@ -464,7 +496,7 @@ class _$FlowExpiredException extends FlowExpiredException bool operator ==(dynamic other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$FlowExpiredException && + other is _$FlowExpiredExceptionImpl && (identical(other.flowId, flowId) || other.flowId == flowId) && (identical(other.message, message) || other.message == message)); } @@ -475,9 +507,9 @@ class _$FlowExpiredException extends FlowExpiredException @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') - _$$FlowExpiredExceptionCopyWith> get copyWith => - __$$FlowExpiredExceptionCopyWithImpl>( - this, _$identity); + _$$FlowExpiredExceptionImplCopyWith> + get copyWith => __$$FlowExpiredExceptionImplCopyWithImpl>(this, _$identity); @override @optionalTypeArgs @@ -486,6 +518,7 @@ class _$FlowExpiredException extends FlowExpiredException required TResult Function() unauthorized, required TResult Function(String flowId, String? message) flowExpired, required TResult Function(Session? session) twoFactorAuthRequired, + required TResult Function(String url) locationChangeRequired, required TResult Function(String? message) unknown, }) { return flowExpired(flowId, message); @@ -498,6 +531,7 @@ class _$FlowExpiredException extends FlowExpiredException TResult? Function()? unauthorized, TResult? Function(String flowId, String? message)? flowExpired, TResult? Function(Session? session)? twoFactorAuthRequired, + TResult? Function(String url)? locationChangeRequired, TResult? Function(String? message)? unknown, }) { return flowExpired?.call(flowId, message); @@ -510,6 +544,7 @@ class _$FlowExpiredException extends FlowExpiredException TResult Function()? unauthorized, TResult Function(String flowId, String? message)? flowExpired, TResult Function(Session? session)? twoFactorAuthRequired, + TResult Function(String url)? locationChangeRequired, TResult Function(String? message)? unknown, required TResult orElse(), }) { @@ -527,6 +562,8 @@ class _$FlowExpiredException extends FlowExpiredException required TResult Function(FlowExpiredException value) flowExpired, required TResult Function(TwoFactorAuthRequiredException value) twoFactorAuthRequired, + required TResult Function(LocationChangeRequiredException value) + locationChangeRequired, required TResult Function(UnknownException value) unknown, }) { return flowExpired(this); @@ -540,6 +577,8 @@ class _$FlowExpiredException extends FlowExpiredException TResult? Function(FlowExpiredException value)? flowExpired, TResult? Function(TwoFactorAuthRequiredException value)? twoFactorAuthRequired, + TResult? Function(LocationChangeRequiredException value)? + locationChangeRequired, TResult? Function(UnknownException value)? unknown, }) { return flowExpired?.call(this); @@ -553,6 +592,8 @@ class _$FlowExpiredException extends FlowExpiredException TResult Function(FlowExpiredException value)? flowExpired, TResult Function(TwoFactorAuthRequiredException value)? twoFactorAuthRequired, + TResult Function(LocationChangeRequiredException value)? + locationChangeRequired, TResult Function(UnknownException value)? unknown, required TResult orElse(), }) { @@ -566,34 +607,34 @@ class _$FlowExpiredException extends FlowExpiredException abstract class FlowExpiredException extends CustomException { const factory FlowExpiredException( {required final String flowId, - final String? message}) = _$FlowExpiredException; + final String? message}) = _$FlowExpiredExceptionImpl; const FlowExpiredException._() : super._(); String get flowId; String? get message; @JsonKey(ignore: true) - _$$FlowExpiredExceptionCopyWith> get copyWith => - throw _privateConstructorUsedError; + _$$FlowExpiredExceptionImplCopyWith> + get copyWith => throw _privateConstructorUsedError; } /// @nodoc -abstract class _$$TwoFactorAuthRequiredExceptionCopyWith { - factory _$$TwoFactorAuthRequiredExceptionCopyWith( - _$TwoFactorAuthRequiredException value, - $Res Function(_$TwoFactorAuthRequiredException) then) = - __$$TwoFactorAuthRequiredExceptionCopyWithImpl; +abstract class _$$TwoFactorAuthRequiredExceptionImplCopyWith { + factory _$$TwoFactorAuthRequiredExceptionImplCopyWith( + _$TwoFactorAuthRequiredExceptionImpl value, + $Res Function(_$TwoFactorAuthRequiredExceptionImpl) then) = + __$$TwoFactorAuthRequiredExceptionImplCopyWithImpl; @useResult $Res call({Session? session}); } /// @nodoc -class __$$TwoFactorAuthRequiredExceptionCopyWithImpl +class __$$TwoFactorAuthRequiredExceptionImplCopyWithImpl extends _$CustomExceptionCopyWithImpl> - implements _$$TwoFactorAuthRequiredExceptionCopyWith { - __$$TwoFactorAuthRequiredExceptionCopyWithImpl( - _$TwoFactorAuthRequiredException _value, - $Res Function(_$TwoFactorAuthRequiredException) _then) + _$TwoFactorAuthRequiredExceptionImpl> + implements _$$TwoFactorAuthRequiredExceptionImplCopyWith { + __$$TwoFactorAuthRequiredExceptionImplCopyWithImpl( + _$TwoFactorAuthRequiredExceptionImpl _value, + $Res Function(_$TwoFactorAuthRequiredExceptionImpl) _then) : super(_value, _then); @pragma('vm:prefer-inline') @@ -601,7 +642,7 @@ class __$$TwoFactorAuthRequiredExceptionCopyWithImpl $Res call({ Object? session = freezed, }) { - return _then(_$TwoFactorAuthRequiredException( + return _then(_$TwoFactorAuthRequiredExceptionImpl( session: freezed == session ? _value.session : session // ignore: cast_nullable_to_non_nullable @@ -612,9 +653,9 @@ class __$$TwoFactorAuthRequiredExceptionCopyWithImpl /// @nodoc -class _$TwoFactorAuthRequiredException +class _$TwoFactorAuthRequiredExceptionImpl extends TwoFactorAuthRequiredException with DiagnosticableTreeMixin { - const _$TwoFactorAuthRequiredException({this.session}) : super._(); + const _$TwoFactorAuthRequiredExceptionImpl({this.session}) : super._(); @override final Session? session; @@ -637,7 +678,7 @@ class _$TwoFactorAuthRequiredException bool operator ==(dynamic other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$TwoFactorAuthRequiredException && + other is _$TwoFactorAuthRequiredExceptionImpl && (identical(other.session, session) || other.session == session)); } @@ -647,10 +688,10 @@ class _$TwoFactorAuthRequiredException @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') - _$$TwoFactorAuthRequiredExceptionCopyWith> - get copyWith => __$$TwoFactorAuthRequiredExceptionCopyWithImpl>(this, _$identity); + _$$TwoFactorAuthRequiredExceptionImplCopyWith> + get copyWith => __$$TwoFactorAuthRequiredExceptionImplCopyWithImpl>(this, _$identity); @override @optionalTypeArgs @@ -659,6 +700,7 @@ class _$TwoFactorAuthRequiredException required TResult Function() unauthorized, required TResult Function(String flowId, String? message) flowExpired, required TResult Function(Session? session) twoFactorAuthRequired, + required TResult Function(String url) locationChangeRequired, required TResult Function(String? message) unknown, }) { return twoFactorAuthRequired(session); @@ -671,6 +713,7 @@ class _$TwoFactorAuthRequiredException TResult? Function()? unauthorized, TResult? Function(String flowId, String? message)? flowExpired, TResult? Function(Session? session)? twoFactorAuthRequired, + TResult? Function(String url)? locationChangeRequired, TResult? Function(String? message)? unknown, }) { return twoFactorAuthRequired?.call(session); @@ -683,6 +726,7 @@ class _$TwoFactorAuthRequiredException TResult Function()? unauthorized, TResult Function(String flowId, String? message)? flowExpired, TResult Function(Session? session)? twoFactorAuthRequired, + TResult Function(String url)? locationChangeRequired, TResult Function(String? message)? unknown, required TResult orElse(), }) { @@ -700,6 +744,8 @@ class _$TwoFactorAuthRequiredException required TResult Function(FlowExpiredException value) flowExpired, required TResult Function(TwoFactorAuthRequiredException value) twoFactorAuthRequired, + required TResult Function(LocationChangeRequiredException value) + locationChangeRequired, required TResult Function(UnknownException value) unknown, }) { return twoFactorAuthRequired(this); @@ -713,6 +759,8 @@ class _$TwoFactorAuthRequiredException TResult? Function(FlowExpiredException value)? flowExpired, TResult? Function(TwoFactorAuthRequiredException value)? twoFactorAuthRequired, + TResult? Function(LocationChangeRequiredException value)? + locationChangeRequired, TResult? Function(UnknownException value)? unknown, }) { return twoFactorAuthRequired?.call(this); @@ -726,6 +774,8 @@ class _$TwoFactorAuthRequiredException TResult Function(FlowExpiredException value)? flowExpired, TResult Function(TwoFactorAuthRequiredException value)? twoFactorAuthRequired, + TResult Function(LocationChangeRequiredException value)? + locationChangeRequired, TResult Function(UnknownException value)? unknown, required TResult orElse(), }) { @@ -738,31 +788,212 @@ class _$TwoFactorAuthRequiredException abstract class TwoFactorAuthRequiredException extends CustomException { const factory TwoFactorAuthRequiredException({final Session? session}) = - _$TwoFactorAuthRequiredException; + _$TwoFactorAuthRequiredExceptionImpl; const TwoFactorAuthRequiredException._() : super._(); Session? get session; @JsonKey(ignore: true) - _$$TwoFactorAuthRequiredExceptionCopyWith> + _$$TwoFactorAuthRequiredExceptionImplCopyWith> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$LocationChangeRequiredExceptionImplCopyWith { + factory _$$LocationChangeRequiredExceptionImplCopyWith( + _$LocationChangeRequiredExceptionImpl value, + $Res Function(_$LocationChangeRequiredExceptionImpl) then) = + __$$LocationChangeRequiredExceptionImplCopyWithImpl; + @useResult + $Res call({String url}); +} + +/// @nodoc +class __$$LocationChangeRequiredExceptionImplCopyWithImpl + extends _$CustomExceptionCopyWithImpl> + implements _$$LocationChangeRequiredExceptionImplCopyWith { + __$$LocationChangeRequiredExceptionImplCopyWithImpl( + _$LocationChangeRequiredExceptionImpl _value, + $Res Function(_$LocationChangeRequiredExceptionImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? url = null, + }) { + return _then(_$LocationChangeRequiredExceptionImpl( + url: null == url + ? _value.url + : url // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$LocationChangeRequiredExceptionImpl + extends LocationChangeRequiredException with DiagnosticableTreeMixin { + const _$LocationChangeRequiredExceptionImpl({required this.url}) : super._(); + + @override + final String url; + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'CustomException<$T>.locationChangeRequired(url: $url)'; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty( + 'type', 'CustomException<$T>.locationChangeRequired')) + ..add(DiagnosticsProperty('url', url)); + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LocationChangeRequiredExceptionImpl && + (identical(other.url, url) || other.url == url)); + } + + @override + int get hashCode => Object.hash(runtimeType, url); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$LocationChangeRequiredExceptionImplCopyWith> + get copyWith => __$$LocationChangeRequiredExceptionImplCopyWithImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(T flow) badRequest, + required TResult Function() unauthorized, + required TResult Function(String flowId, String? message) flowExpired, + required TResult Function(Session? session) twoFactorAuthRequired, + required TResult Function(String url) locationChangeRequired, + required TResult Function(String? message) unknown, + }) { + return locationChangeRequired(url); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(T flow)? badRequest, + TResult? Function()? unauthorized, + TResult? Function(String flowId, String? message)? flowExpired, + TResult? Function(Session? session)? twoFactorAuthRequired, + TResult? Function(String url)? locationChangeRequired, + TResult? Function(String? message)? unknown, + }) { + return locationChangeRequired?.call(url); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(T flow)? badRequest, + TResult Function()? unauthorized, + TResult Function(String flowId, String? message)? flowExpired, + TResult Function(Session? session)? twoFactorAuthRequired, + TResult Function(String url)? locationChangeRequired, + TResult Function(String? message)? unknown, + required TResult orElse(), + }) { + if (locationChangeRequired != null) { + return locationChangeRequired(url); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(BadRequestException value) badRequest, + required TResult Function(UnauthorizedException value) unauthorized, + required TResult Function(FlowExpiredException value) flowExpired, + required TResult Function(TwoFactorAuthRequiredException value) + twoFactorAuthRequired, + required TResult Function(LocationChangeRequiredException value) + locationChangeRequired, + required TResult Function(UnknownException value) unknown, + }) { + return locationChangeRequired(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(BadRequestException value)? badRequest, + TResult? Function(UnauthorizedException value)? unauthorized, + TResult? Function(FlowExpiredException value)? flowExpired, + TResult? Function(TwoFactorAuthRequiredException value)? + twoFactorAuthRequired, + TResult? Function(LocationChangeRequiredException value)? + locationChangeRequired, + TResult? Function(UnknownException value)? unknown, + }) { + return locationChangeRequired?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(BadRequestException value)? badRequest, + TResult Function(UnauthorizedException value)? unauthorized, + TResult Function(FlowExpiredException value)? flowExpired, + TResult Function(TwoFactorAuthRequiredException value)? + twoFactorAuthRequired, + TResult Function(LocationChangeRequiredException value)? + locationChangeRequired, + TResult Function(UnknownException value)? unknown, + required TResult orElse(), + }) { + if (locationChangeRequired != null) { + return locationChangeRequired(this); + } + return orElse(); + } +} + +abstract class LocationChangeRequiredException extends CustomException { + const factory LocationChangeRequiredException({required final String url}) = + _$LocationChangeRequiredExceptionImpl; + const LocationChangeRequiredException._() : super._(); + + String get url; + @JsonKey(ignore: true) + _$$LocationChangeRequiredExceptionImplCopyWith> get copyWith => throw _privateConstructorUsedError; } /// @nodoc -abstract class _$$UnknownExceptionCopyWith { - factory _$$UnknownExceptionCopyWith(_$UnknownException value, - $Res Function(_$UnknownException) then) = - __$$UnknownExceptionCopyWithImpl; +abstract class _$$UnknownExceptionImplCopyWith { + factory _$$UnknownExceptionImplCopyWith(_$UnknownExceptionImpl value, + $Res Function(_$UnknownExceptionImpl) then) = + __$$UnknownExceptionImplCopyWithImpl; @useResult $Res call({String? message}); } /// @nodoc -class __$$UnknownExceptionCopyWithImpl - extends _$CustomExceptionCopyWithImpl> - implements _$$UnknownExceptionCopyWith { - __$$UnknownExceptionCopyWithImpl( - _$UnknownException _value, $Res Function(_$UnknownException) _then) +class __$$UnknownExceptionImplCopyWithImpl + extends _$CustomExceptionCopyWithImpl> + implements _$$UnknownExceptionImplCopyWith { + __$$UnknownExceptionImplCopyWithImpl(_$UnknownExceptionImpl _value, + $Res Function(_$UnknownExceptionImpl) _then) : super(_value, _then); @pragma('vm:prefer-inline') @@ -770,7 +1001,7 @@ class __$$UnknownExceptionCopyWithImpl $Res call({ Object? message = freezed, }) { - return _then(_$UnknownException( + return _then(_$UnknownExceptionImpl( message: freezed == message ? _value.message : message // ignore: cast_nullable_to_non_nullable @@ -781,9 +1012,9 @@ class __$$UnknownExceptionCopyWithImpl /// @nodoc -class _$UnknownException extends UnknownException +class _$UnknownExceptionImpl extends UnknownException with DiagnosticableTreeMixin { - const _$UnknownException( + const _$UnknownExceptionImpl( {this.message = 'An error occured. Please try again later.'}) : super._(); @@ -808,7 +1039,7 @@ class _$UnknownException extends UnknownException bool operator ==(dynamic other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$UnknownException && + other is _$UnknownExceptionImpl && (identical(other.message, message) || other.message == message)); } @@ -818,8 +1049,8 @@ class _$UnknownException extends UnknownException @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') - _$$UnknownExceptionCopyWith> get copyWith => - __$$UnknownExceptionCopyWithImpl>( + _$$UnknownExceptionImplCopyWith> get copyWith => + __$$UnknownExceptionImplCopyWithImpl>( this, _$identity); @override @@ -829,6 +1060,7 @@ class _$UnknownException extends UnknownException required TResult Function() unauthorized, required TResult Function(String flowId, String? message) flowExpired, required TResult Function(Session? session) twoFactorAuthRequired, + required TResult Function(String url) locationChangeRequired, required TResult Function(String? message) unknown, }) { return unknown(message); @@ -841,6 +1073,7 @@ class _$UnknownException extends UnknownException TResult? Function()? unauthorized, TResult? Function(String flowId, String? message)? flowExpired, TResult? Function(Session? session)? twoFactorAuthRequired, + TResult? Function(String url)? locationChangeRequired, TResult? Function(String? message)? unknown, }) { return unknown?.call(message); @@ -853,6 +1086,7 @@ class _$UnknownException extends UnknownException TResult Function()? unauthorized, TResult Function(String flowId, String? message)? flowExpired, TResult Function(Session? session)? twoFactorAuthRequired, + TResult Function(String url)? locationChangeRequired, TResult Function(String? message)? unknown, required TResult orElse(), }) { @@ -870,6 +1104,8 @@ class _$UnknownException extends UnknownException required TResult Function(FlowExpiredException value) flowExpired, required TResult Function(TwoFactorAuthRequiredException value) twoFactorAuthRequired, + required TResult Function(LocationChangeRequiredException value) + locationChangeRequired, required TResult Function(UnknownException value) unknown, }) { return unknown(this); @@ -883,6 +1119,8 @@ class _$UnknownException extends UnknownException TResult? Function(FlowExpiredException value)? flowExpired, TResult? Function(TwoFactorAuthRequiredException value)? twoFactorAuthRequired, + TResult? Function(LocationChangeRequiredException value)? + locationChangeRequired, TResult? Function(UnknownException value)? unknown, }) { return unknown?.call(this); @@ -896,6 +1134,8 @@ class _$UnknownException extends UnknownException TResult Function(FlowExpiredException value)? flowExpired, TResult Function(TwoFactorAuthRequiredException value)? twoFactorAuthRequired, + TResult Function(LocationChangeRequiredException value)? + locationChangeRequired, TResult Function(UnknownException value)? unknown, required TResult orElse(), }) { @@ -908,11 +1148,11 @@ class _$UnknownException extends UnknownException abstract class UnknownException extends CustomException { const factory UnknownException({final String? message}) = - _$UnknownException; + _$UnknownExceptionImpl; const UnknownException._() : super._(); String? get message; @JsonKey(ignore: true) - _$$UnknownExceptionCopyWith> get copyWith => + _$$UnknownExceptionImplCopyWith> get copyWith => throw _privateConstructorUsedError; } diff --git a/flutter-ory-network/lib/widgets/helpers.dart b/flutter-ory-network/lib/widgets/helpers.dart index 6cb8675..ad6f03c 100644 --- a/flutter-ory-network/lib/widgets/helpers.dart +++ b/flutter-ory-network/lib/widgets/helpers.dart @@ -40,30 +40,27 @@ buildGroup( void Function(BuildContext, UiNodeGroupEnum, String, String) onInputSubmit) { final formKey = GlobalKey(); - return Padding( - padding: const EdgeInsets.only(bottom: 0), - child: Form( - key: formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ListView.builder( - physics: const NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemBuilder: ((BuildContext context, index) { - final attributes = nodes[index].attributes.oneOf; - if (attributes.isType(UiNodeInputAttributes)) { - return buildInputNode(context, formKey, nodes[index], - onInputChange, onInputSubmit); - } else if (attributes.isType(UiNodeTextAttributes)) { - return TextNode(node: nodes[index]); - } else { - return Container(); - } - }), - itemCount: nodes.length), - ], - ), + return Form( + key: formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListView.builder( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemBuilder: ((BuildContext context, index) { + final attributes = nodes[index].attributes.oneOf; + if (attributes.isType(UiNodeInputAttributes)) { + return buildInputNode(context, formKey, nodes[index], + onInputChange, onInputSubmit); + } else if (attributes.isType(UiNodeTextAttributes)) { + return TextNode(node: nodes[index]); + } else { + return Container(); + } + }), + itemCount: nodes.length), + ], ), ); } @@ -89,9 +86,6 @@ buildInputNode( formKey: formKey, onChange: onInputChange, onSubmit: onInputSubmit); - case UiNodeInputAttributesTypeEnum.hidden: - return const SizedBox.shrink(); - default: return InputNode(node: node, onChange: onInputChange); } diff --git a/flutter-ory-network/lib/widgets/nodes/input_submit.dart b/flutter-ory-network/lib/widgets/nodes/input_submit.dart index c2fed9c..e1c4884 100644 --- a/flutter-ory-network/lib/widgets/nodes/input_submit.dart +++ b/flutter-ory-network/lib/widgets/nodes/input_submit.dart @@ -19,38 +19,53 @@ class InputSubmitNode extends StatelessWidget { @override Widget build(BuildContext context) { - return Column( - children: [ - SizedBox( - width: double.infinity, - child: FilledButton( - // validate input fields that belong to this buttons group - onPressed: () { - if (formKey.currentState!.validate()) { - final attributes = - node.attributes.oneOf.value as UiNodeInputAttributes; - final type = attributes.type; - // if attribute type is a button with value 'false', set its value to true on submit - if (type == UiNodeInputAttributesTypeEnum.button || - type == UiNodeInputAttributesTypeEnum.submit && - attributes.value?.value == 'false') { - final nodeName = attributes.name; - - onChange(context, 'true', nodeName); - } - onSubmit( - context, - node.group, - attributes.name, - attributes.value!.isString - ? attributes.value!.asString - : ''); - } - }, - child: Text(node.meta.label?.text ?? ''), - ), - ), - ], + final value = node.attributes.oneOf.isType(UiNodeInputAttributes) + ? (node.attributes.oneOf.value as UiNodeInputAttributes).value?.asString + : null; + final provider = _getProviderName(value); + return SizedBox( + width: double.infinity, + child: node.group == UiNodeGroupEnum.oidc + ? OutlinedButton.icon( + icon: Image.asset( + 'assets/images/flows-auth-buttons-social-$provider.png'), + label: Text(node.meta.label?.text ?? ''), + onPressed: () => onPressed(context), + ) + : FilledButton( + // validate input fields that belong to this buttons group + onPressed: () => onPressed(context), + child: Text(node.meta.label?.text ?? ''), + ), ); } + + _getProviderName(String? value) { + if (value == null) { + return ''; + } else if (value.contains('google')) { + return 'google'; + } else if (value.contains('apple')) { + return 'apple'; + } else { + return ''; + } + } + + onPressed(BuildContext context) { + if (formKey.currentState!.validate()) { + final attributes = node.attributes.oneOf.value as UiNodeInputAttributes; + final type = attributes.type; + // if attribute type is a button with value 'false', set its value to true on submit + if (type == UiNodeInputAttributesTypeEnum.button || + type == UiNodeInputAttributesTypeEnum.submit && + attributes.value?.value == 'false') { + final nodeName = attributes.name; + + onChange(context, 'true', nodeName); + } + onSubmit(context, node.group, attributes.name, + attributes.value!.isString ? attributes.value!.asString : ''); + } + } } diff --git a/flutter-ory-network/lib/widgets/nodes/provider.dart b/flutter-ory-network/lib/widgets/nodes/provider.dart deleted file mode 100644 index d389084..0000000 --- a/flutter-ory-network/lib/widgets/nodes/provider.dart +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright © 2023 Ory Corp -// SPDX-License-Identifier: Apache-2.0 - -import 'package:flutter/material.dart'; -import 'package:ory_client/ory_client.dart'; - -class SocialProviderInput extends StatelessWidget { - final UiNode node; - - const SocialProviderInput({super.key, required this.node}); - @override - Widget build(BuildContext context) { - final provider = node.attributes.oneOf.isType(UiNodeInputAttributes) - ? (node.attributes.oneOf.value as UiNodeInputAttributes).value?.asString - : null; - return SizedBox( - width: double.infinity, - child: OutlinedButton.icon( - icon: Image.asset( - 'assets/images/flows-auth-buttons-social-$provider.png'), - label: Text(node.meta.label?.text ?? ''), - onPressed: () { - //TODO - }, - ), - ); - } -} diff --git a/flutter-ory-network/lib/widgets/ory_theme.dart b/flutter-ory-network/lib/widgets/ory_theme.dart index e9bf977..aad872f 100644 --- a/flutter-ory-network/lib/widgets/ory_theme.dart +++ b/flutter-ory-network/lib/widgets/ory_theme.dart @@ -71,6 +71,16 @@ class OryTheme { RoundedRectangleBorder( borderRadius: BorderRadius.circular(4.0))))); + static final OutlinedButtonThemeData outlinedButtonThemeData = + OutlinedButtonThemeData( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(Colors.white), + padding: MaterialStateProperty.all( + const EdgeInsets.symmetric(vertical: 12, horizontal: 16)), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4.0))))); + static final ThemeData defaultTheme = ThemeData( primarySwatch: primaryColorSwatch, fontFamily: GoogleFonts.getFont('Inter').fontFamily, @@ -78,6 +88,7 @@ class OryTheme { appBarTheme: const AppBarTheme(iconTheme: IconThemeData(color: primaryColor)), filledButtonTheme: filledButtonThemeData, + outlinedButtonTheme: outlinedButtonThemeData, inputDecorationTheme: textFieldTheme, ); } diff --git a/flutter-ory-network/lib/widgets/social_provider_box.dart b/flutter-ory-network/lib/widgets/social_provider_box.dart deleted file mode 100644 index 5c1e788..0000000 --- a/flutter-ory-network/lib/widgets/social_provider_box.dart +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright © 2023 Ory Corp -// SPDX-License-Identifier: Apache-2.0 - -import 'package:flutter/material.dart'; - -class SocialProviderBox extends StatelessWidget { - final SocialProvider provider; - - const SocialProviderBox({super.key, required this.provider}); - @override - Widget build(BuildContext context) { - return Expanded( - child: AspectRatio( - aspectRatio: 1.6, - child: Container( - padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16), - decoration: BoxDecoration( - border: Border.all(width: 1, color: const Color(0xFFE2E8F0))), - child: // get icon from assets depending on provider - Image.asset( - 'assets/images/flows-auth-buttons-social-${provider.name}.png'), - ), - ), - ); - } -} - -enum SocialProvider { google, github, apple, linkedin } diff --git a/flutter-ory-network/pubspec.lock b/flutter-ory-network/pubspec.lock index 9ce2d1f..1535585 100644 --- a/flutter-ory-network/pubspec.lock +++ b/flutter-ory-network/pubspec.lock @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: build_resolvers - sha256: "6c4dd11d05d056e76320b828a1db0fc01ccd376922526f8e9d6c796a5adbac20" + sha256: "64e12b0521812d1684b1917bc80945625391cb9bdd4312536b1d69dcb6133ed8" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.4.1" build_runner: dependency: "direct dev" description: @@ -93,10 +93,10 @@ packages: dependency: transitive description: name: build_runner_core - sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41" + sha256: c9e32d21dd6626b5c163d48b037ce906bbe428bc23ab77bcd77bb21e593b6185 url: "https://pub.dev" source: hosted - version: "7.2.10" + version: "7.2.11" built_collection: dependency: "direct main" description: @@ -109,10 +109,10 @@ packages: dependency: "direct main" description: name: built_value - sha256: ff627b645b28fb8bdb69e645f910c2458fd6b65f6585c3a53e0626024897dedf + sha256: a8de5955205b4d1dbbbc267daddf2178bd737e4bab8987c04a500478c9651e74 url: "https://pub.dev" source: hosted - version: "8.6.2" + version: "8.6.3" characters: dependency: transitive description: @@ -141,10 +141,10 @@ packages: dependency: transitive description: name: code_builder - sha256: "4ad01d6e56db961d29661561effde45e519939fdaeb46c351275b182eac70189" + sha256: "1be9be30396d7e4c0db42c35ea6ccd7cc6a1e19916b5dc64d6ac216b5544d677" url: "https://pub.dev" source: hosted - version: "4.5.0" + version: "4.7.0" collection: dependency: "direct main" description: @@ -162,29 +162,21 @@ packages: source: hosted version: "3.1.1" crypto: - dependency: transitive + dependency: "direct main" description: name: crypto sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab url: "https://pub.dev" source: hosted version: "3.0.3" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be - url: "https://pub.dev" - source: hosted - version: "1.0.5" dart_style: dependency: transitive description: name: dart_style - sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" + sha256: abd7625e16f51f554ea244d090292945ec4d4be7bfbaf2ec8cccea568919d334 url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.3" deep_collection: dependency: "direct main" description: @@ -197,18 +189,18 @@ packages: dependency: "direct main" description: name: dio - sha256: ce75a1b40947fea0a0e16ce73337122a86762e38b982e1ccb909daa3b9bc4197 + sha256: "417e2a6f9d83ab396ec38ff4ea5da6c254da71e4db765ad737a42af6930140b7" url: "https://pub.dev" source: hosted - version: "5.3.2" + version: "5.3.3" dotenv: dependency: "direct main" description: name: dotenv - sha256: e169b516bc7b88801919e1c508772bcb8e3d0d1776a43f74ab692c57e741cd8a + sha256: "379e64b6fc82d3df29461d349a1796ecd2c436c480d4653f3af6872eccbc90e1" url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "4.2.0" effective_dart: dependency: transitive description: @@ -245,10 +237,10 @@ packages: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.0" fixnum: dependency: transitive description: @@ -282,63 +274,71 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.0.3" flutter_secure_storage: dependency: "direct main" description: name: flutter_secure_storage - sha256: "98352186ee7ad3639ccc77ad7924b773ff6883076ab952437d20f18a61f0a7c5" + sha256: "22dbf16f23a4bcf9d35e51be1c84ad5bb6f627750565edd70dab70f3ff5fff8f" url: "https://pub.dev" source: hosted - version: "8.0.0" + version: "8.1.0" flutter_secure_storage_linux: dependency: transitive description: name: flutter_secure_storage_linux - sha256: "0912ae29a572230ad52d8a4697e5518d7f0f429052fd51df7e5a7952c7efe2a3" + sha256: "3d5032e314774ee0e1a7d0a9f5e2793486f0dff2dd9ef5a23f4e3fb2a0ae6a9e" url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "1.2.0" flutter_secure_storage_macos: dependency: transitive description: name: flutter_secure_storage_macos - sha256: "083add01847fc1c80a07a08e1ed6927e9acd9618a35e330239d4422cd2a58c50" + sha256: bd33935b4b628abd0b86c8ca20655c5b36275c3a3f5194769a7b3f37c905369c url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.0.1" flutter_secure_storage_platform_interface: dependency: transitive description: name: flutter_secure_storage_platform_interface - sha256: b3773190e385a3c8a382007893d678ae95462b3c2279e987b55d140d3b0cb81b + sha256: "0d4d3a5dd4db28c96ae414d7ba3b8422fd735a8255642774803b2532c9a61d7e" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.2" flutter_secure_storage_web: dependency: transitive description: name: flutter_secure_storage_web - sha256: "42938e70d4b872e856e678c423cc0e9065d7d294f45bc41fc1981a4eb4beaffe" + sha256: "30f84f102df9dcdaa2241866a958c2ec976902ebdaa8883fbfe525f1f2f3cf20" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" flutter_secure_storage_windows: dependency: transitive description: name: flutter_secure_storage_windows - sha256: fc2910ec9b28d60598216c29ea763b3a96c401f0ce1d13cdf69ccb0e5c93c3ee + sha256: "38f9501c7cb6f38961ef0e1eacacee2b2d4715c63cc83fe56449c4d3d0b47255" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.1.1" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_auth: + dependency: "direct main" + description: + name: flutter_web_auth + sha256: a69fa8f43b9e4d86ac72176bf747b735e7b977dd7cf215076d95b87cb05affdd + url: "https://pub.dev" + source: hosted + version: "0.5.0" flutter_web_plugins: dependency: transitive description: flutter @@ -348,10 +348,10 @@ packages: dependency: "direct dev" description: name: freezed - sha256: "83462cfc33dc9680533a7f3a4a6ab60aa94f287db5f4ee6511248c22833c497f" + sha256: "21bf2825311de65501d22e563e3d7605dff57fb5e6da982db785ae5372ff018a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.5" freezed_annotation: dependency: "direct main" description: @@ -384,6 +384,54 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.0" + google_identity_services_web: + dependency: transitive + description: + name: google_identity_services_web + sha256: "000b7a31e1fa17ee04b6c0553a2b2ea18f9f9352e4dcc0c9fcc785cf10f2484e" + url: "https://pub.dev" + source: hosted + version: "0.2.2" + google_sign_in: + dependency: "direct main" + description: + name: google_sign_in + sha256: f45038d27bcad37498f282295ae97eece23c9349fc16649154067b87b9f1fd03 + url: "https://pub.dev" + source: hosted + version: "6.1.5" + google_sign_in_android: + dependency: transitive + description: + name: google_sign_in_android + sha256: "6031f59074a337fdd81be821aba84cee3a41338c6e958499a5cd34d3e1db80ef" + url: "https://pub.dev" + source: hosted + version: "6.1.20" + google_sign_in_ios: + dependency: transitive + description: + name: google_sign_in_ios + sha256: "8edfde9698b5951f3d02632eceb39cc283865c3cff0b03216bf951089f10345b" + url: "https://pub.dev" + source: hosted + version: "5.6.3" + google_sign_in_platform_interface: + dependency: transitive + description: + name: google_sign_in_platform_interface + sha256: "35ceee5f0eadc1c07b0b4af7553246e315c901facbb7d3dadf734ba2693ceec4" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + google_sign_in_web: + dependency: transitive + description: + name: google_sign_in_web + sha256: "939e9172a378ec4eaeb7f71eeddac9b55ebd0e8546d336daec476a68e5279766" + url: "https://pub.dev" + source: hosted + version: "0.12.0+5" graphs: dependency: transitive description: @@ -524,10 +572,10 @@ packages: dependency: "direct main" description: name: ory_client - sha256: "151372511353601d4afd81213523ea0662be0f754b44b4be17c40c91bf1967cc" + sha256: "2eb2fb2ad8abe8093c970420972e5cb4a932640d031c52cae962dc444e0a7a54" url: "https://pub.dev" source: hosted - version: "1.2.10" + version: "1.2.11" package_config: dependency: transitive description: @@ -548,66 +596,66 @@ packages: dependency: transitive description: name: path_provider - sha256: "909b84830485dbcd0308edf6f7368bc8fd76afa26a270420f34cabea2a6467a0" + sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "5d44fc3314d969b84816b569070d7ace0f1dea04bd94a83f74c4829615d22ad8" + sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.1" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "1b744d3d774e5a879bb76d6cd1ecee2ba2c6960c03b1020cd35212f6aa267ac5" + sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.3.1" path_provider_linux: dependency: transitive description: name: path_provider_linux - sha256: ba2b77f0c52a33db09fc8caf85b12df691bf28d983e84cf87ff6d693cfa007b3 + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.1" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - sha256: bced5679c7df11190e1ddc35f3222c858f328fff85c3942e46e7f5589bf9eb84 + sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" path_provider_windows: dependency: transitive description: name: path_provider_windows - sha256: ee0e0d164516b90ae1f970bdf29f726f1aa730d7cfc449ecc74c495378b705da + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.1" platform: dependency: transitive description: name: platform - sha256: "57c07bf82207aee366dfaa3867b3164e4f03a238a461a11b0e8a3a510d51203d" + sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.3" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd" + sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.1.6" pool: dependency: transitive description: @@ -664,6 +712,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + sign_in_with_apple: + dependency: "direct main" + description: + name: sign_in_with_apple + sha256: "0975c23b9f8b30a80e27d5659a75993a093d4cb5f4eb7d23a9ccc586fea634e0" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + sign_in_with_apple_platform_interface: + dependency: transitive + description: + name: sign_in_with_apple_platform_interface + sha256: a5883edee09ed6be19de19e7d9f618a617fe41a6fa03f76d082dfb787e9ea18d + url: "https://pub.dev" + source: hosted + version: "1.0.0" + sign_in_with_apple_web: + dependency: transitive + description: + name: sign_in_with_apple_web + sha256: "44b66528f576e77847c14999d5e881e17e7223b7b0625a185417829e5306f47a" + url: "https://pub.dev" + source: hosted + version: "1.0.1" sky_engine: dependency: transitive description: flutter @@ -793,18 +865,18 @@ packages: dependency: transitive description: name: win32 - sha256: f2add6fa510d3ae152903412227bda57d0d5a8da61d2c39c1fb022c9429a41c0 + sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" url: "https://pub.dev" source: hosted - version: "5.0.6" + version: "5.0.9" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: f0c26453a2d47aa4c2570c6a033246a3fc62da2fe23c7ffdd0a7495086dc0247 + sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.0.3" yaml: dependency: transitive description: @@ -814,5 +886,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.1.0-185.0.dev <4.0.0" - flutter: ">=3.3.0" + dart: ">=3.1.0 <4.0.0" + flutter: ">=3.13.0" diff --git a/flutter-ory-network/pubspec.yaml b/flutter-ory-network/pubspec.yaml index dcc0062..7568190 100644 --- a/flutter-ory-network/pubspec.yaml +++ b/flutter-ory-network/pubspec.yaml @@ -31,9 +31,6 @@ dependencies: flutter: sdk: flutter - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 bloc: ^8.1.2 flutter_bloc: ^8.1.3 dio: ^5.3.2 @@ -50,6 +47,10 @@ dependencies: deep_collection: ^1.0.2 collection: ^1.17.2 built_collection: ^5.1.1 + google_sign_in: ^6.1.5 + sign_in_with_apple: ^5.0.0 + crypto: ^3.0.3 + flutter_web_auth: ^0.5.0 dev_dependencies: flutter_test: