diff --git a/lib/screens/common_widgets/auth/oauth2_field.dart b/lib/screens/common_widgets/auth/oauth2_field.dart index c291d717..d7fe4340 100644 --- a/lib/screens/common_widgets/auth/oauth2_field.dart +++ b/lib/screens/common_widgets/auth/oauth2_field.dart @@ -1,3 +1,6 @@ +import 'dart:convert'; + +import 'package:apidash/utils/file_utils.dart'; import 'package:apidash/widgets/field_auth.dart'; import 'package:apidash_core/apidash_core.dart'; import 'package:apidash_design_system/apidash_design_system.dart'; @@ -52,18 +55,50 @@ class _OAuth2FieldsState extends State { _usernameController = TextEditingController(text: oauth2?.username ?? ''); _passwordController = TextEditingController(text: oauth2?.password ?? ''); _refreshTokenController = - TextEditingController(text: oauth2?.refreshToken ?? ''); + TextEditingController(text: oauth2?.refreshToken ?? 'N/A'); _identityTokenController = - TextEditingController(text: oauth2?.identityToken ?? ''); + TextEditingController(text: oauth2?.identityToken ?? 'N/A'); _accessTokenController = - TextEditingController(text: oauth2?.accessToken ?? ''); + TextEditingController(text: oauth2?.accessToken ?? 'N/A'); _codeChallengeMethod = oauth2?.codeChallengeMethod ?? 'sha-256'; + + // Load credentials from file if available + _loadCredentialsFromFile(); + } + + Future _loadCredentialsFromFile() async { + final credentialsFilePath = widget.authData?.oauth2?.credentialsFilePath; + if (credentialsFilePath != null && credentialsFilePath.isNotEmpty) { + final credentialsFile = await loadFileFromPath(credentialsFilePath); + if (credentialsFile != null) { + final credentials = await credentialsFile.readAsString(); + final Map decoded = jsonDecode(credentials); + setState(() { + if (decoded['refreshToken'] != null) { + _refreshTokenController.text = decoded['refreshToken']!; + } else { + _refreshTokenController.text = "N/A"; + } + if (decoded['id_token'] != null) { + _identityTokenController.text = decoded['identityToken']!; + } else { + _identityTokenController.text = "N/A"; + } + if (decoded['accessToken'] != null) { + _accessTokenController.text = decoded['accessToken']!; + } else { + _accessTokenController.text = "N/A"; + } + }); + } + } } @override Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, + return ListView( + shrinkWrap: true, + physics: AlwaysScrollableScrollPhysics(), children: [ Text( "Grant Type", @@ -177,9 +212,7 @@ class _OAuth2FieldsState extends State { onChanged: (_) => _updateOAuth2(), ), ), - if (_shouldShowField( - OAuth2Field.scope)) // Based on refined list, Scope is always shown - + if (_shouldShowField(OAuth2Field.scope)) ..._buildFieldWithSpacing( AuthTextField( readOnly: widget.readOnly, @@ -197,6 +230,12 @@ class _OAuth2FieldsState extends State { onChanged: (_) => _updateOAuth2(), ), ), + ..._buildFieldWithSpacing( + ADTextButton( + label: "Clear OAuth2 Session", + onPressed: clearStoredCredentials, + ), + ), Divider(), kVSpacer16, ..._buildFieldWithSpacing( @@ -204,6 +243,7 @@ class _OAuth2FieldsState extends State { readOnly: widget.readOnly, controller: _refreshTokenController, hintText: "Refresh Token", + isObscureText: true, onChanged: (_) => _updateOAuth2(), ), ), @@ -212,6 +252,7 @@ class _OAuth2FieldsState extends State { readOnly: widget.readOnly, controller: _identityTokenController, hintText: "Identity Token", + isObscureText: true, onChanged: (_) => _updateOAuth2(), ), ), @@ -219,6 +260,7 @@ class _OAuth2FieldsState extends State { AuthTextField( readOnly: widget.readOnly, controller: _accessTokenController, + isObscureText: true, hintText: "Access Token", onChanged: (_) => _updateOAuth2(), ), @@ -244,6 +286,7 @@ class _OAuth2FieldsState extends State { OAuth2Field.refreshToken, OAuth2Field.identityToken, OAuth2Field.accessToken, + OAuth2Field.clearSession, }; if (alwaysShownFields.contains(field)) { @@ -270,13 +313,20 @@ class _OAuth2FieldsState extends State { } } - void _updateOAuth2() { + void _updateOAuth2() async { + final String? credentialsFilePath = + await getApplicationSupportDirectoryFilePath( + "oauth2_credentials", "json"); + if (credentialsFilePath == null) { + return; + } final updatedOAuth2 = AuthOAuth2Model( grantType: _grantType, authorizationUrl: _authorizationUrlController.text.trim(), clientId: _clientIdController.text.trim(), accessTokenUrl: _accessTokenUrlController.text.trim(), clientSecret: _clientSecretController.text.trim(), + credentialsFilePath: credentialsFilePath, codeChallengeMethod: _codeChallengeMethod, redirectUrl: _redirectUrlController.text.trim(), scope: _scopeController.text.trim(), @@ -300,6 +350,13 @@ class _OAuth2FieldsState extends State { ); } + Future clearStoredCredentials() async { + final credentialsFilePath = widget.authData?.oauth2?.credentialsFilePath; + if (credentialsFilePath != null && credentialsFilePath.isNotEmpty) { + await deleteFileFromPath(credentialsFilePath); + } + } + @override void dispose() { _authorizationUrlController.dispose(); @@ -332,4 +389,5 @@ enum OAuth2Field { refreshToken, identityToken, accessToken, + clearSession, } diff --git a/lib/utils/file_utils.dart b/lib/utils/file_utils.dart index 31d3bce6..07217844 100644 --- a/lib/utils/file_utils.dart +++ b/lib/utils/file_utils.dart @@ -34,8 +34,9 @@ Future getFileDownloadpath(String? name, String? ext) async { return null; } -Future getDocumentsDirectoryFilePath(String name, String? ext) async { - final Directory tempDir = await getApplicationDocumentsDirectory(); +Future getApplicationSupportDirectoryFilePath( + String name, String? ext) async { + final Directory tempDir = await getApplicationSupportDirectory(); name = name; ext = (ext != null) ? ".$ext" : ""; String path = '${tempDir.path}/$name$ext'; @@ -73,3 +74,28 @@ Future pickFile() async { XFile? pickedResult = await openFile(); return pickedResult; } + +Future loadFileFromPath(String filePath) async { + try { + final file = File(filePath); + if (!await file.exists()) { + return null; + } + return file; + } catch (e) { + return null; + } +} + +Future deleteFileFromPath(String filePath) async { + try { + final file = File(filePath); + if (await file.exists()) { + await file.delete(); + return true; + } + return false; + } catch (e) { + return false; + } +} diff --git a/packages/better_networking/lib/models/auth/auth_oauth2_model.dart b/packages/better_networking/lib/models/auth/auth_oauth2_model.dart index 17148fc1..94e92276 100644 --- a/packages/better_networking/lib/models/auth/auth_oauth2_model.dart +++ b/packages/better_networking/lib/models/auth/auth_oauth2_model.dart @@ -9,35 +9,21 @@ part 'auth_oauth2_model.freezed.dart'; class AuthOAuth2Model with _$AuthOAuth2Model { const factory AuthOAuth2Model({ @Default(OAuth2GrantType.authorizationCode) OAuth2GrantType grantType, - required String authorizationUrl, - required String accessTokenUrl, - required String clientId, - required String clientSecret, - + required String credentialsFilePath, String? redirectUrl, - String? scope, - String? state, - @Default("sha-256") String codeChallengeMethod, - String? codeVerifier, - String? codeChallenge, - String? username, - String? password, - String? refreshToken, - String? identityToken, - String? accessToken, }) = _AuthOAuth2Model; diff --git a/packages/better_networking/lib/models/auth/auth_oauth2_model.freezed.dart b/packages/better_networking/lib/models/auth/auth_oauth2_model.freezed.dart index 41f0697e..1264f8b0 100644 --- a/packages/better_networking/lib/models/auth/auth_oauth2_model.freezed.dart +++ b/packages/better_networking/lib/models/auth/auth_oauth2_model.freezed.dart @@ -26,6 +26,7 @@ mixin _$AuthOAuth2Model { String get accessTokenUrl => throw _privateConstructorUsedError; String get clientId => throw _privateConstructorUsedError; String get clientSecret => throw _privateConstructorUsedError; + String get credentialsFilePath => throw _privateConstructorUsedError; String? get redirectUrl => throw _privateConstructorUsedError; String? get scope => throw _privateConstructorUsedError; String? get state => throw _privateConstructorUsedError; @@ -61,6 +62,7 @@ abstract class $AuthOAuth2ModelCopyWith<$Res> { String accessTokenUrl, String clientId, String clientSecret, + String credentialsFilePath, String? redirectUrl, String? scope, String? state, @@ -95,6 +97,7 @@ class _$AuthOAuth2ModelCopyWithImpl<$Res, $Val extends AuthOAuth2Model> Object? accessTokenUrl = null, Object? clientId = null, Object? clientSecret = null, + Object? credentialsFilePath = null, Object? redirectUrl = freezed, Object? scope = freezed, Object? state = freezed, @@ -129,6 +132,10 @@ class _$AuthOAuth2ModelCopyWithImpl<$Res, $Val extends AuthOAuth2Model> ? _value.clientSecret : clientSecret // ignore: cast_nullable_to_non_nullable as String, + credentialsFilePath: null == credentialsFilePath + ? _value.credentialsFilePath + : credentialsFilePath // ignore: cast_nullable_to_non_nullable + as String, redirectUrl: freezed == redirectUrl ? _value.redirectUrl : redirectUrl // ignore: cast_nullable_to_non_nullable @@ -194,6 +201,7 @@ abstract class _$$AuthOAuth2ModelImplCopyWith<$Res> String accessTokenUrl, String clientId, String clientSecret, + String credentialsFilePath, String? redirectUrl, String? scope, String? state, @@ -227,6 +235,7 @@ class __$$AuthOAuth2ModelImplCopyWithImpl<$Res> Object? accessTokenUrl = null, Object? clientId = null, Object? clientSecret = null, + Object? credentialsFilePath = null, Object? redirectUrl = freezed, Object? scope = freezed, Object? state = freezed, @@ -261,6 +270,10 @@ class __$$AuthOAuth2ModelImplCopyWithImpl<$Res> ? _value.clientSecret : clientSecret // ignore: cast_nullable_to_non_nullable as String, + credentialsFilePath: null == credentialsFilePath + ? _value.credentialsFilePath + : credentialsFilePath // ignore: cast_nullable_to_non_nullable + as String, redirectUrl: freezed == redirectUrl ? _value.redirectUrl : redirectUrl // ignore: cast_nullable_to_non_nullable @@ -319,6 +332,7 @@ class _$AuthOAuth2ModelImpl implements _AuthOAuth2Model { required this.accessTokenUrl, required this.clientId, required this.clientSecret, + required this.credentialsFilePath, this.redirectUrl, this.scope, this.state, @@ -347,6 +361,8 @@ class _$AuthOAuth2ModelImpl implements _AuthOAuth2Model { @override final String clientSecret; @override + final String credentialsFilePath; + @override final String? redirectUrl; @override final String? scope; @@ -372,7 +388,7 @@ class _$AuthOAuth2ModelImpl implements _AuthOAuth2Model { @override String toString() { - return 'AuthOAuth2Model(grantType: $grantType, authorizationUrl: $authorizationUrl, accessTokenUrl: $accessTokenUrl, clientId: $clientId, clientSecret: $clientSecret, redirectUrl: $redirectUrl, scope: $scope, state: $state, codeChallengeMethod: $codeChallengeMethod, codeVerifier: $codeVerifier, codeChallenge: $codeChallenge, username: $username, password: $password, refreshToken: $refreshToken, identityToken: $identityToken, accessToken: $accessToken)'; + return 'AuthOAuth2Model(grantType: $grantType, authorizationUrl: $authorizationUrl, accessTokenUrl: $accessTokenUrl, clientId: $clientId, clientSecret: $clientSecret, credentialsFilePath: $credentialsFilePath, redirectUrl: $redirectUrl, scope: $scope, state: $state, codeChallengeMethod: $codeChallengeMethod, codeVerifier: $codeVerifier, codeChallenge: $codeChallenge, username: $username, password: $password, refreshToken: $refreshToken, identityToken: $identityToken, accessToken: $accessToken)'; } @override @@ -390,6 +406,8 @@ class _$AuthOAuth2ModelImpl implements _AuthOAuth2Model { other.clientId == clientId) && (identical(other.clientSecret, clientSecret) || other.clientSecret == clientSecret) && + (identical(other.credentialsFilePath, credentialsFilePath) || + other.credentialsFilePath == credentialsFilePath) && (identical(other.redirectUrl, redirectUrl) || other.redirectUrl == redirectUrl) && (identical(other.scope, scope) || other.scope == scope) && @@ -421,6 +439,7 @@ class _$AuthOAuth2ModelImpl implements _AuthOAuth2Model { accessTokenUrl, clientId, clientSecret, + credentialsFilePath, redirectUrl, scope, state, @@ -458,6 +477,7 @@ abstract class _AuthOAuth2Model implements AuthOAuth2Model { required final String accessTokenUrl, required final String clientId, required final String clientSecret, + required final String credentialsFilePath, final String? redirectUrl, final String? scope, final String? state, @@ -485,6 +505,8 @@ abstract class _AuthOAuth2Model implements AuthOAuth2Model { @override String get clientSecret; @override + String get credentialsFilePath; + @override String? get redirectUrl; @override String? get scope; diff --git a/packages/better_networking/lib/models/auth/auth_oauth2_model.g.dart b/packages/better_networking/lib/models/auth/auth_oauth2_model.g.dart index 742832f2..7865fc2f 100644 --- a/packages/better_networking/lib/models/auth/auth_oauth2_model.g.dart +++ b/packages/better_networking/lib/models/auth/auth_oauth2_model.g.dart @@ -16,6 +16,7 @@ _$AuthOAuth2ModelImpl _$$AuthOAuth2ModelImplFromJson( accessTokenUrl: json['accessTokenUrl'] as String, clientId: json['clientId'] as String, clientSecret: json['clientSecret'] as String, + credentialsFilePath: json['credentialsFilePath'] as String, redirectUrl: json['redirectUrl'] as String?, scope: json['scope'] as String?, state: json['state'] as String?, @@ -37,6 +38,7 @@ Map _$$AuthOAuth2ModelImplToJson( 'accessTokenUrl': instance.accessTokenUrl, 'clientId': instance.clientId, 'clientSecret': instance.clientSecret, + 'credentialsFilePath': instance.credentialsFilePath, 'redirectUrl': instance.redirectUrl, 'scope': instance.scope, 'state': instance.state, diff --git a/packages/better_networking/lib/utils/auth/handle_auth.dart b/packages/better_networking/lib/utils/auth/handle_auth.dart index c0ba15f2..4d8c8699 100644 --- a/packages/better_networking/lib/utils/auth/handle_auth.dart +++ b/packages/better_networking/lib/utils/auth/handle_auth.dart @@ -170,11 +170,7 @@ Future handleAuth( throw Exception("No Redirect URL found!"); } - //TODO: Create a proper credentials file path, use the existing file utils if needed. - final credentialsDir = Directory.systemTemp; - final credentialsFile = File( - '${credentialsDir.path}/oauth2_credentials.json', - ); + final credentialsFile = File(oauth2.credentialsFilePath); switch (oauth2.grantType) { case OAuth2GrantType.authorizationCode: