diff --git a/lib/consts.dart b/lib/consts.dart index 595aa69e7..4ad19924f 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -435,10 +435,12 @@ const kLabelImport = "Import"; const kUntitled = "untitled"; // Request Pane const kLabelRequest = "Request"; +const kLabelAuthorizationType = "Authorization Type"; const kLabelHideCode = "Hide Code"; const kLabelViewCode = "View Code"; const kLabelURLParams = "URL Params"; const kLabelHeaders = "Headers"; +const kLabelAuthorization = "Authorization"; const kLabelBody = "Body"; const kLabelQuery = "Query"; const kNameCheckbox = "Checkbox"; diff --git a/lib/providers/auth_providers.dart b/lib/providers/auth_providers.dart new file mode 100644 index 000000000..c8e6da975 --- /dev/null +++ b/lib/providers/auth_providers.dart @@ -0,0 +1,82 @@ +import 'dart:convert'; +import 'package:apidash_core/apidash_core.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +enum AuthType { + none, + basicAuth, + apiKey, + bearerToken, + jwtBearer, + digestAuth, + oauth1, + oauth2, +} + +String authTypeToString(AuthType authType) { + switch (authType) { + case AuthType.basicAuth: + return "Basic Auth"; + case AuthType.apiKey: + return "API Key"; + case AuthType.bearerToken: + return "Bearer Token"; + case AuthType.jwtBearer: + return "JWT Bearer"; + case AuthType.digestAuth: + return "Digest Auth"; + case AuthType.oauth1: + return "OAuth 1.0"; + case AuthType.oauth2: + return "OAuth 2.0"; + case AuthType.none: + default: + return "None"; + } +} + +final authTypeProvider = StateProvider((ref) => AuthType.none); +final authCredentialsProvider = StateProvider>((ref) => { + 'username': '', + 'password': '', + 'apiKey': '', + 'token': '', + }); + +void updateAuthType(WidgetRef ref, AuthType newAuthType) { + ref.read(authTypeProvider.notifier).state = newAuthType; +} + +void updateCredentials(WidgetRef ref, Map newCredentials) { + ref.read(authCredentialsProvider.notifier).update((state) => { + ...state, + ...newCredentials, + }); +} + +List generateAuthHeaders(WidgetRef ref) { + final authType = ref.read(authTypeProvider); + final credentials = ref.read(authCredentialsProvider); + + switch (authType) { + case AuthType.basicAuth: + if (credentials['username']?.isNotEmpty == true && + credentials['password']?.isNotEmpty == true) { + String basicAuth = + 'Basic ${base64Encode(utf8.encode('${credentials['username']}:${credentials['password']}'))}'; + return [NameValueModel(name: "Authorization", value: basicAuth)]; + } + break; + case AuthType.bearerToken: + case AuthType.jwtBearer: + break; + case AuthType.apiKey: + break; + case AuthType.oauth1: + case AuthType.oauth2: + case AuthType.digestAuth: + case AuthType.none: + break; + } + return []; +} diff --git a/lib/providers/providers.dart b/lib/providers/providers.dart index 29fc6e595..d279f001d 100644 --- a/lib/providers/providers.dart +++ b/lib/providers/providers.dart @@ -3,3 +3,4 @@ export 'environment_providers.dart'; export 'history_providers.dart'; export 'settings_providers.dart'; export 'ui_providers.dart'; +export 'auth_providers.dart'; \ No newline at end of file diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_authorization.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_authorization.dart new file mode 100644 index 000000000..e741380c4 --- /dev/null +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_authorization.dart @@ -0,0 +1,148 @@ +import 'dart:convert'; + +import 'package:apidash/consts.dart'; +import 'package:apidash/providers/collection_providers.dart'; +import 'package:apidash_core/apidash_core.dart'; +import 'package:apidash_design_system/tokens/tokens.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../../../providers/auth_providers.dart'; + +class EditRequestAuthorization extends ConsumerStatefulWidget { + const EditRequestAuthorization({super.key}); + + @override + ConsumerState createState() => + _EditRequestAuthorizationState(); +} + +class _EditRequestAuthorizationState + extends ConsumerState { + late TextEditingController _usernameController; + late TextEditingController _passwordController; + + @override + void initState() { + super.initState(); + _usernameController = TextEditingController(); + _passwordController = TextEditingController(); + + _usernameController.addListener(() { + ref.read(authCredentialsProvider.notifier).update((state) => { + ...state, + 'username': _usernameController.text, + }); + }); + + _passwordController.addListener(() { + ref.read(authCredentialsProvider.notifier).update((state) => { + ...state, + 'password': _passwordController.text, + }); + }); + } + + @override + void dispose() { + _usernameController.dispose(); + _passwordController.dispose(); + super.dispose(); + } + + void _updateHeaders(AuthType? authType, String? username, String? password) { + final headers = _generateAuthHeaders(authType, username, password); + ref.read(collectionStateNotifierProvider.notifier).update(headers: headers); + } + + List _generateAuthHeaders( + AuthType? authType, String? username, String? password) { + if (authType == AuthType.basicAuth && + username != null && + password != null && + username.isNotEmpty && + password.isNotEmpty) { + String basicAuth = + 'Basic ${base64Encode(utf8.encode('$username:$password'))}'; + return [NameValueModel(name: "Authorization", value: basicAuth)]; + } + return []; + } + + @override + Widget build(BuildContext context) { + final authType = ref.watch(authTypeProvider); + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + kVSpacer16, + DropdownButtonFormField( + value: authType, + decoration: InputDecoration( + labelText: kLabelAuthorizationType, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + contentPadding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + ), + items: AuthType.values.map((AuthType type) { + return DropdownMenuItem( + value: type, child: Text(type.toString().split('.').last)); + }).toList(), + onChanged: (value) { + if (value != null) { + ref.read(authTypeProvider.notifier).update((state) => value); + _updateHeaders( + value, _usernameController.text, _passwordController.text); + } + }, + ), + const SizedBox(height: 16), + if (authType == AuthType.basicAuth) ...[ + TextFormField( + controller: _usernameController, + decoration: InputDecoration( + labelText: "Username", + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + contentPadding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + ), + onChanged: (value) { + ref.read(authCredentialsProvider.notifier).update((state) => { + ...state, + 'username': value, + }); + _updateHeaders(authType, value, _passwordController.text); + }, + ), + const SizedBox(height: 12), + TextFormField( + controller: _passwordController, + decoration: InputDecoration( + labelText: "Password", + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + contentPadding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + ), + obscureText: true, + onChanged: (value) { + ref.read(authCredentialsProvider.notifier).update((state) => { + ...state, + 'password': value, + }); + _updateHeaders(authType, _usernameController.text, value); + }, + ), + ], + ], + ), + ); + } +} diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane_graphql.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane_graphql.dart index beae2b6c5..094ce74f7 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane_graphql.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane_graphql.dart @@ -1,4 +1,5 @@ import 'package:apidash/consts.dart'; +import 'package:apidash/screens/home_page/editor_pane/details_card/request_pane/request_authorization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:apidash/providers/providers.dart'; @@ -21,6 +22,9 @@ class EditGraphQLRequestPane extends ConsumerWidget { final hasQuery = ref.watch(selectedRequestModelProvider .select((value) => value?.httpRequestModel?.hasQuery)) ?? false; + + final authEnabled = ref.watch(authTypeProvider) != AuthType.none; + if (tabIndex >= 2) { tabIndex = 0; } @@ -38,14 +42,17 @@ class EditGraphQLRequestPane extends ConsumerWidget { .update(requestTabIndex: index); }, showIndicators: [ - headerLength > 0, + authEnabled, + headerLength > 0 && !authEnabled, hasQuery, ], tabLabels: const [ + kLabelAuthorization, kLabelHeaders, kLabelQuery, ], children: const [ + EditRequestAuthorization(), EditRequestHeaders(), EditRequestBody(), ], diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane_rest.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane_rest.dart index e323f83b8..c34cf4cca 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane_rest.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane_rest.dart @@ -1,9 +1,10 @@ import 'package:apidash/consts.dart'; +import 'package:apidash/screens/home_page/editor_pane/details_card/request_pane/request_authorization.dart'; +import 'package:apidash/screens/home_page/editor_pane/details_card/request_pane/request_headers.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:apidash/providers/providers.dart'; import 'package:apidash/widgets/widgets.dart'; -import 'request_headers.dart'; import 'request_params.dart'; import 'request_body.dart'; @@ -26,7 +27,7 @@ class EditRestRequestPane extends ConsumerWidget { final hasBody = ref.watch(selectedRequestModelProvider .select((value) => value?.httpRequestModel?.hasBody)) ?? false; - + final authEnabled = ref.watch(authTypeProvider) != AuthType.none; return RequestPane( selectedId: selectedId, codePaneVisible: codePaneVisible, @@ -42,16 +43,19 @@ class EditRestRequestPane extends ConsumerWidget { }, showIndicators: [ paramLength > 0, - headerLength > 0, + authEnabled, + headerLength > 0 && !authEnabled, hasBody, ], tabLabels: const [ kLabelURLParams, + kLabelAuthorization, kLabelHeaders, kLabelBody, ], children: const [ EditRequestURLParams(), + EditRequestAuthorization(), EditRequestHeaders(), EditRequestBody(), ], diff --git a/lib/widgets/request_pane.dart b/lib/widgets/request_pane.dart index 95871d532..5d2537c58 100644 --- a/lib/widgets/request_pane.dart +++ b/lib/widgets/request_pane.dart @@ -37,7 +37,7 @@ class _RequestPaneState extends State @override Widget build(BuildContext context) { final TabController controller = useTabController( - initialLength: widget.children.length, + initialLength: widget.tabLabels.length, vsync: this, ); if (widget.tabIndex != null) {