Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Enhance authentication flow with loading indicators and error handlin… #2346

Merged
merged 3 commits into from
Dec 15, 2024

Conversation

Mozart299
Copy link
Contributor

@Mozart299 Mozart299 commented Dec 15, 2024

…g; improve Hive data retrieval safety

Summary of Changes (What does this PR do?)

  • Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.

Status of maturity (all need to be checked before merging):

  • I've tested this locally
  • I consider this code done
  • This change ready to hit production in its current state
  • The title of the PR states what changed and the related issues number (used for the release note).
  • I've included issue number in the "Closes #ISSUE-NUMBER" part of the "What are the relevant tickets?" section to link the issue.
  • I've updated corresponding documentation for the changes in this PR.
  • I have written unit and/or e2e tests for my change(s).

How should this be manually tested?

  • Please include the steps to be done inorder to setup and test this PR.

What are the relevant tickets?

Screenshots (optional)

Summary by CodeRabbit

  • New Features

    • Enhanced error handling and loading state management in the app.
    • Improved form validation logic for login functionality.
    • Introduced token validation and caching for authentication processes.
  • Bug Fixes

    • Added default value handling in theme retrieval to prevent null values.
    • Introduced type safety for user ID retrieval in user profile loading.
    • Improved error handling in data retrieval methods.
  • Documentation

    • Updated method signatures for improved clarity and type safety.
  • Chores

    • Added logging capabilities for better error tracking in data retrieval.
    • Introduced a new dependency for JWT decoding functionalities.

@Mozart299 Mozart299 requested a review from Baalmart December 15, 2024 08:39
Copy link

coderabbitai bot commented Dec 15, 2024

📝 Walkthrough
📝 Walkthrough

Walkthrough

This pull request focuses on enhancing type safety, error handling, and robustness across multiple files in the mobile application. The changes primarily involve improving null handling, adding more precise type annotations, and implementing better error logging mechanisms. Key modifications span critical components like authentication, theme management, user profile loading, and repository interactions.

Changes

File Change Summary
mobile-v3/lib/main.dart Enhanced FutureBuilder with explicit type parameter and improved error/loading state management. Added isValidToken method for token validation.
mobile-v3/lib/src/app/auth/pages/login_page.dart Updated authBloc to non-nullable type and improved form validation logic with comprehensive null checks.
mobile-v3/lib/src/app/other/theme/repository/theme_repository.dart Added default theme value in getTheme method to ensure valid string return.
mobile-v3/lib/src/app/profile/repository/user_repository.dart Declared userId as final and added null check for improved error handling.
mobile-v3/lib/src/app/shared/repository/base_repository.dart Introduced _cachedToken for caching and improved error handling in token retrieval methods.
mobile-v3/lib/src/app/shared/repository/hive_repository.dart Refined getData method with improved error handling and type specificity.
mobile-v3/pubspec.yaml Added new dependency jwt_decoder for JWT decoding functionalities.

Possibly related PRs

  • Add connectivity management with connectivity_plus package #2325: This PR adds connectivity management using the connectivity_plus package, which is relevant to the changes in the main PR that enhance the handling of the authentication token retrieval process and manage connectivity state through the ConnectivityBloc.
  • App logging #2343: This PR introduces logging functionality, which aligns with the main PR's improvements in error handling and logging during the authentication token retrieval process.

Suggested Labels

ready for review

Suggested Reviewers

  • Baalmart
  • OchiengPaul442
  • Codebmk

Poem

🚀 Null safety dance, code's new stance
Errors caught with grace and might
Repositories shine so bright
Type casting leads the way tonight
Robust code takes its flight! 🛡️

Tip

CodeRabbit's docstrings feature is now available as part of our Early Access Program! Simply use the command @coderabbitai generate docstrings to have CodeRabbit automatically generate docstrings for your pull request. This feature will be included in our Pro Plan when released.


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai or @coderabbitai title anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🔭 Outside diff range comments (2)
mobile-v3/lib/src/app/auth/pages/login_page.dart (1)

Line range hint 85-89: Enhance form validation for better security

While the null safety improvements are good, the validation could be strengthened for better security and user experience.

Consider implementing:

 validator: (value) {
   if (value == null || value.isEmpty) {
     return "This field cannot be blank.";
   }
+  if (value.length < 8) {
+    return "Password must be at least 8 characters long";
+  }
+  if (!value.contains(RegExp(r'[A-Z]')) ||
+      !value.contains(RegExp(r'[a-z]')) ||
+      !value.contains(RegExp(r'[0-9]'))) {
+    return "Password must contain uppercase, lowercase, and numbers";
+  }
   return null;
 },

For email validation:

 validator: (value) {
   if (value == null || value.isEmpty) {
     return "This field cannot be blank.";
   }
+  final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
+  if (!emailRegex.hasMatch(value)) {
+    return "Please enter a valid email address";
+  }
   return null;
 },

Also applies to: 103-107

mobile-v3/lib/src/app/profile/repository/user_repository.dart (1)

Line range hint 16-22: Add error handling for profile response

The profile request lacks error handling, which could lead to crashes if the server returns an error response.

Consider adding try-catch and response validation:

+    try {
       Response profileResponse =
           await createAuthenticatedGetRequest("/api/v2/users/${userId}", {});
+      if (profileResponse.statusCode != 200) {
+        throw Exception('Failed to load profile: ${profileResponse.statusCode}');
+      }
       ProfileResponseModel model =
           profileResponseModelFromJson(profileResponse.body);
       return model;
+    } catch (e) {
+      logError('Error loading user profile: $e');
+      rethrow;
+    }
🧹 Nitpick comments (5)
mobile-v3/lib/src/app/auth/pages/login_page.dart (2)

133-137: Improve form submission UX

The null safety improvements are good, but the user experience during form submission could be enhanced.

Consider these improvements:

 onTap: loading
     ? null
     : () {
+        setState(() => error = null);  // Clear previous errors
         final currentForm = formKey.currentState;
         if (currentForm != null &&
             currentForm.validate()) {
+          // Provide immediate feedback
+          ScaffoldMessenger.of(context).showSnackBar(
+            SnackBar(content: Text('Signing in...')),
+          );
           authBloc.add(LoginUser(
               emailController.text.trim(),
               passwordController.text.trim()));
         }
     },

Line range hint 36-52: Consider implementing a more comprehensive loading and error handling strategy

While the current implementation handles basic loading states and errors, consider implementing a more robust error handling strategy that aligns with the PR objectives:

  1. Use a dedicated error handling widget for consistent error display
  2. Implement retry mechanisms for failed authentication attempts
  3. Add loading indicators for intermediate states (e.g., password validation)

Would you like me to provide a sample implementation of a reusable error handling widget?

mobile-v3/lib/src/app/other/theme/repository/theme_repository.dart (2)

19-19: Consider refactoring theme handling

While the null handling is good, we can improve the code structure:

  1. Extract theme values as constants:
class ThemeConstants {
  static const String light = 'light';
  static const String dark = 'dark';
  static const String defaultTheme = light;
}
  1. Consider using an enum for type safety:
enum AppTheme { light, dark }

This would make the code more maintainable and type-safe.


Line range hint 11-23: Consolidate theme persistence logic

The theme switching logic could be more concise.

Consider this refactor:

  Future<String> getTheme(bool switchTheme) async {
    String theme = await HiveRepository.getData("theme", "themeBox") ?? "light";

    if (switchTheme) {
-     if (theme == "light") {
-       await HiveRepository.saveData("themeBox", "theme", "dark");
-     } else {
-       await HiveRepository.saveData("themeBox", "theme", "light");
-     }
-     String newTheme = await HiveRepository.getData("theme", "themeBox") ?? "light";
-     return newTheme;
+     final newTheme = theme == "light" ? "dark" : "light";
+     await HiveRepository.saveData("themeBox", "theme", newTheme);
+     return newTheme;
    }

    return theme;
  }
mobile-v3/lib/main.dart (1)

165-169: Enhance loading state UI

While the CircularProgressIndicator is functional, consider adding a branded loading screen for a better user experience.

Consider this enhancement:

 if (snapshot.connectionState == ConnectionState.waiting) {
   return Scaffold(
-    body: Center(child: CircularProgressIndicator()),
+    body: Center(
+      child: Column(
+        mainAxisAlignment: MainAxisAlignment.center,
+        children: [
+          CircularProgressIndicator(),
+          SizedBox(height: 16),
+          Text('Loading AirQo...'),
+        ],
+      ),
+    ),
   );
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9a82558 and de2cb03.

📒 Files selected for processing (6)
  • mobile-v3/lib/main.dart (2 hunks)
  • mobile-v3/lib/src/app/auth/pages/login_page.dart (5 hunks)
  • mobile-v3/lib/src/app/other/theme/repository/theme_repository.dart (1 hunks)
  • mobile-v3/lib/src/app/profile/repository/user_repository.dart (1 hunks)
  • mobile-v3/lib/src/app/shared/repository/base_repository.dart (1 hunks)
  • mobile-v3/lib/src/app/shared/repository/hive_repository.dart (2 hunks)
🔇 Additional comments (3)
mobile-v3/lib/src/app/shared/repository/hive_repository.dart (1)

2-2: Consider adding loggy initialization

While adding loggy for error logging is great, we should ensure it's properly initialized.

Let's check if loggy is initialized in the app:

✅ Verification successful

Loggy is already properly initialized

The package is correctly initialized in mobile-v3/lib/main.dart with proper configuration including a pretty printer and log levels.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for loggy initialization
rg "with\s+Loggy" --type dart

Length of output: 31


Script:

#!/bin/bash
# Let's try different patterns to find Loggy initialization
rg "Loggy\." --type dart -A 3

# Also search for main function which might contain initialization
ast-grep --pattern 'void main() {
  $$$
}'

Length of output: 284

mobile-v3/lib/main.dart (2)

173-174: LGTM: Proper error logging

Good use of logError with context for debugging purposes.


184-186: LGTM: Informative logging

Good practice to include navigation context in logs.

@@ -17,7 +17,7 @@ class LoginPage extends StatefulWidget {

class _LoginPageState extends State<LoginPage> {
String? error;
AuthBloc? authBloc;
late AuthBloc authBloc;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Consider a safer initialization pattern for AuthBloc

The current implementation using late could lead to runtime errors if build() is called before initState() completes or if context.read() fails.

Consider this safer approach:

-  late AuthBloc authBloc;
+  AuthBloc? authBloc;

   @override
   void initState() {
     super.initState();
-    authBloc = context.read<AuthBloc>();
+    try {
+      authBloc = context.read<AuthBloc>();
+    } catch (e) {
+      print('Failed to initialize AuthBloc: $e');
+    }
   }

Also applies to: 32-32

Comment on lines +18 to +26
static Future<String?> getData(String key, String boxName) async {
try {
var box = await Hive.openBox(boxName);
var value = box.get(key);
return value is String ? value : null;
} catch (e) {
logError('Error retrieving data from Hive: $e');
return null;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Potential null safety concerns in getData usage 🚨

The verification reveals several instances where getData's null return isn't properly handled:

  • user_repository.dart: Unsafe cast with as String which could throw at runtime
  • base_repository.dart: Dangerous non-null assertion (!) in authenticated request
  • Other usages properly handle null with null-coalescing (??) or null-aware patterns

Suggested fixes:

  • In user_repository.dart: Replace as String with null check and error handling
  • In base_repository.dart: Add proper null handling for token instead of force unwrapping
🔗 Analysis chain

Excellent improvements to type safety and error handling! 👍

The changes to getData method significantly improve robustness:

  • Type-safe return value with Future<String?>
  • Proper error handling with try-catch
  • Explicit type checking for returned values
  • Error logging for debugging

Let's verify the usage of this method across the codebase to ensure we're handling null returns:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for getData calls that might need null handling
rg "getData\(" --type dart -B 2 -A 2

Length of output: 4827

@@ -11,7 +11,7 @@ class UserImpl extends UserRepository {
@override
Future<ProfileResponseModel> loadUserProfile() async {
String userId =
await HiveRepository.getData("userId", HiveBoxNames.authBox);
await HiveRepository.getData("userId", HiveBoxNames.authBox) as String;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid force casting and add null handling

The current implementation might crash if getData returns null. Consider using null-safe operators and adding proper error handling.

Here's a safer implementation:

-    String userId =
-        await HiveRepository.getData("userId", HiveBoxNames.authBox) as String;
+    final userId = await HiveRepository.getData("userId", HiveBoxNames.authBox);
+    if (userId == null) {
+      throw Exception('User ID not found in storage');
+    }

Comment on lines 55 to 56
String token =
(await HiveRepository.getData("token", HiveBoxNames.authBox))!;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid using null assertion operator for token retrieval

The current implementation assumes the token will never be null, which could lead to runtime exceptions. Consider handling the null case explicitly for better error handling.

Here's a safer approach:

-    String token =
-        (await HiveRepository.getData("token", HiveBoxNames.authBox))!;
+    final token = await HiveRepository.getData("token", HiveBoxNames.authBox);
+    if (token == null) {
+      throw Exception('Authentication token not found');
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
String token =
(await HiveRepository.getData("token", HiveBoxNames.authBox))!;
final token = await HiveRepository.getData("token", HiveBoxNames.authBox);
if (token == null) {
throw Exception('Authentication token not found');
}

Comment on lines 181 to 189
// Check if token exists and is not null
final token = snapshot.data;
if (token == null || token.isEmpty) {
logInfo(
'No authentication token found. Navigating to WelcomeScreen');
return WelcomeScreen();
} else {
logInfo('Authentication token found. Navigating to NavPage');
return NavPage();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance token validation logic

The current implementation only checks if the token is null or empty. Consider adding token format validation and expiration check.

Suggested enhancement:

- final token = snapshot.data;
- if (token == null || token.isEmpty) {
+ final token = snapshot.data;
+ if (!isValidToken(token)) {
   logInfo('No authentication token found. Navigating to WelcomeScreen');
   return WelcomeScreen();
 } else {

Add this helper method:

bool isValidToken(String? token) {
  if (token == null || token.isEmpty) return false;
  try {
    // Add JWT validation if using JWT tokens
    return true;
  } catch (e) {
    logError('Token validation failed', e);
    return false;
  }
}

mobile-v3/lib/main.dart Outdated Show resolved Hide resolved
Copy link
Collaborator

@Baalmart Baalmart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • thanks @Mozart299 please review all the AI Bot review comments and pick address any that appear interesting. When full done with everything, please let me know so that I can merge.
  • Please note that you need to fully test out all the AI suggestions since some of them are sometimes not accurate.

…en validation; enhance user profile loading and repository token management
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🔭 Outside diff range comments (1)
mobile-v3/lib/src/app/shared/repository/base_repository.dart (1)

Line range hint 77-98: Add Bearer prefix and reduce code duplication

There are two issues here:

  1. Missing "Bearer" prefix in Authorization header (security concern)
  2. Duplicate code with createGetRequest method

Consider refactoring to use createGetRequest:

  Future<Response> createAuthenticatedGetRequest(
      String path, Map<String, String> queryParams) async {
    String? token = await _getToken();
    if (token == null) {
      throw Exception('Authentication token not found');
    }
-
-    print(token);
-
-    String url = ApiUtils.baseUrl + path;
-
-    Response response = await http
-        .get(Uri.parse(url).replace(queryParameters: queryParams), headers: {
-      "Accept": "*/*",
-      "Authorization": "${token}",
-      "Content-Type": "application/json",
-    });
-
-    if (response.statusCode != 200) {
-      throw new Exception(json.decode(response.body)['message']);
-    }
-
-    return response;
+    return createGetRequest(path, queryParams);
  }
🧹 Nitpick comments (2)
mobile-v3/lib/src/app/shared/repository/base_repository.dart (2)

8-15: Consider adding cache invalidation mechanism

The token caching implementation is good for performance, but it needs a way to invalidate the cache when the token expires or is updated.

Consider adding a method to clear the cache:

+ void clearTokenCache() {
+   _cachedToken = null;
+ }

Line range hint 19-43: Enhance error handling structure

The error handling is improved but could benefit from a more structured approach using custom exceptions.

Consider creating a dedicated exception class:

class ApiException implements Exception {
  final String message;
  final int statusCode;
  
  ApiException(this.message, this.statusCode);
  
  @override
  String toString() => 'ApiException: $message (Status: $statusCode)';
}

Then update the error handling:

-      throw new Exception(errorMessage);
+      throw ApiException(errorMessage, response.statusCode);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between de2cb03 and 7de82ad.

📒 Files selected for processing (4)
  • mobile-v3/lib/main.dart (3 hunks)
  • mobile-v3/lib/src/app/auth/pages/login_page.dart (5 hunks)
  • mobile-v3/lib/src/app/profile/repository/user_repository.dart (1 hunks)
  • mobile-v3/lib/src/app/shared/repository/base_repository.dart (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • mobile-v3/lib/src/app/profile/repository/user_repository.dart
  • mobile-v3/lib/src/app/auth/pages/login_page.dart
🔇 Additional comments (5)
mobile-v3/lib/src/app/shared/repository/base_repository.dart (1)

12-12: Verify token sanitization in HiveRepository

Let's ensure the token is properly sanitized when stored and retrieved.

mobile-v3/lib/main.dart (4)

36-36: LGTM: Proper initialization of Hive storage

The Hive initialization is correctly placed and wrapped in appropriate error handling.


165-169: LGTM: Well-implemented loading state

The loading indicator provides clear feedback during authentication state retrieval.


171-190: Enhanced error handling implementation

The error handling has been improved with:

  • User-friendly error message
  • Retry functionality
  • Proper error logging

225-234: 🛠️ Refactor suggestion

Consider implementing JWT validation

The token validation helper is a good start, but the JWT validation is currently a placeholder. Consider implementing proper JWT validation to enhance security.

Here's a suggested implementation:

 bool isValidToken(String? token) {
   if (token == null || token.isEmpty) return false;
   try {
-    // Add JWT validation if using JWT tokens
+    final parts = token.split('.');
+    if (parts.length != 3) return false;
+    
+    // Validate token structure
+    if (!_isBase64(parts[0]) || !_isBase64(parts[1])) return false;
+    
+    // TODO: Add signature verification if needed
+    
     return true;
   } catch (e) {
     logError('Token validation failed', e, StackTrace.current);
     return false;
   }
 }
+
+bool _isBase64(String str) {
+  try {
+    return base64Url.decode(str).isNotEmpty;
+  } catch (e) {
+    return false;
+  }
+}

Likely invalid or redundant comment.


print(response.statusCode);

if (response.statusCode != 200) {
throw new Exception(json.decode(response.body)['message']);
throw new Exception(json.decode(response.body));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Maintain consistent error handling across methods

The error handling here is less robust compared to the post request implementation.

Apply the same error message extraction pattern:

-      throw new Exception(json.decode(response.body));
+      final responseBody = json.decode(response.body);
+      final errorMessage = responseBody is Map && responseBody.containsKey('message')
+          ? responseBody['message']
+          : 'An error occurred';
+      throw new Exception(errorMessage);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
throw new Exception(json.decode(response.body));
final responseBody = json.decode(response.body);
final errorMessage = responseBody is Map && responseBody.containsKey('message')
? responseBody['message']
: 'An error occurred';
throw new Exception(errorMessage);

Comment on lines +192 to +200
// Check if token exists and is not null
final token = snapshot.data;
if (!isValidToken(token)) {
logInfo(
'No authentication token found. Navigating to WelcomeScreen');
return WelcomeScreen();
} else {
logInfo('Authentication token found. Navigating to NavPage');
return NavPage();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Token validation implementation needs improvement

The current token validation is too basic and lacks proper JWT validation as indicated by the commented code. Here are the key issues:

  • isValidToken() only checks for null/empty values but doesn't validate the token's integrity or expiration
  • Direct navigation to NavPage() exists in login_page.dart which could potentially bypass proper token validation
  • Token storage is implemented but there's no proper validation between storage and usage

Consider:

  • Implementing proper JWT token validation including signature and expiration checks
  • Adding token refresh mechanism
  • Ensuring all routes to NavPage go through proper token validation
🔗 Analysis chain

Verify token validation flow

The token validation logic looks good, but let's verify the integration with the auth system.


Let me gather more information about the token validation implementation.


Let me check the login flow to ensure proper token handling.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for proper token usage across the codebase
echo "Checking token validation patterns..."

# Look for token retrieval patterns
rg -l "HiveRepository.getData.*token.*authBox" 

# Check for potential token validation bypasses
ast-grep --pattern 'if ($TOKEN == null || $TOKEN.isEmpty)' 

# Look for direct NavPage navigation that might bypass token validation
ast-grep --pattern 'return NavPage();'

Length of output: 320


Script:

#!/bin/bash
# Check the implementation of isValidToken function
ast-grep --pattern 'isValidToken($TOKEN) {
  $$$
}'

# Look for token validation related code with context
rg "isValidToken|token.*validation" -A 3

# Check for other NavPage navigations with context
rg "NavPage\(\)" -A 2

# Examine token retrieval implementation
rg "getData.*token.*authBox" -A 3

Length of output: 1755


Script:

#!/bin/bash
# Check login implementation and token storage
rg "login.*token" -A 5

# Look for token storage implementation
rg "setData.*token.*authBox" -A 3

# Check for token cleanup/logout
rg "removeData.*token.*authBox|clearToken|logout" -A 3

# Look for any direct NavPage navigation in auth flows
ast-grep --pattern 'onPressed: () {
  $$$
  NavPage();
  $$$
}'

Length of output: 13443

@Mozart299
Copy link
Contributor Author

@Baalmart you can merge now and i deploy to see if error has been fixed

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🔭 Outside diff range comments (1)
mobile-v3/lib/src/app/shared/repository/base_repository.dart (1)

Line range hint 82-103: Fix token prefix and standardize error handling

Two issues need attention:

  1. The Authorization header is missing the "Bearer" prefix
  2. Error handling doesn't follow the same robust pattern as other methods
     Response response = await http
         .get(Uri.parse(url).replace(queryParameters: queryParams), headers: {
       "Accept": "*/*",
-      "Authorization": "${token}",
+      "Authorization": "Bearer ${token}",
       "Content-Type": "application/json",
     });
 
     if (response.statusCode != 200) {
-      throw new Exception(json.decode(response.body)['message']);
+      final responseBody = json.decode(response.body);
+      final errorMessage = responseBody is Map && responseBody.containsKey('message')
+          ? responseBody['message']
+          : 'An error occurred';
+      throw new Exception(errorMessage);
     }
🧹 Nitpick comments (3)
mobile-v3/lib/src/app/shared/repository/base_repository.dart (2)

8-15: Consider adding token validation in _getToken method

While caching improves performance, consider adding token validation (e.g., expiration check) before returning the cached token. This would prevent using an expired token for requests.

 Future<String?> _getToken() async {
   if (_cachedToken == null) {
     _cachedToken = await HiveRepository.getData("token", HiveBoxNames.authBox);
   }
+  if (_cachedToken != null && !isTokenValid(_cachedToken!)) {
+    _cachedToken = null;
+    return null;
+  }
   return _cachedToken;
 }

54-74: Consider extracting common headers to a separate method

The headers construction could be made more DRY by extracting common headers into a separate method.

+  Map<String, String> _getCommonHeaders(String? token) {
+    final headers = {
+      "Accept": "*/*",
+      "Content-Type": "application/json",
+    };
+    if (token != null) {
+      headers["Authorization"] = "Bearer $token";
+    }
+    return headers;
+  }

   Future<Response> createGetRequest(
       String path, Map<String, String> queryParams) async {
     String? token = await _getToken();
     String url = ApiUtils.baseUrl + path;
-    Map<String, String> headers = {
-      "Accept": "*/*",
-      "Content-Type": "application/json",
-    };
-    if (token != null) {
-      headers["Authorization"] = "Bearer $token";
-    }
+    final headers = _getCommonHeaders(token);
mobile-v3/lib/main.dart (1)

162-202: Improved error handling and loading states

The FutureBuilder implementation now properly handles all states with appropriate user feedback. The loading indicator and error retry mechanism are good additions.

However, consider adding a more descriptive error message that could help with troubleshooting.

- Text('Unable to verify authentication status'),
+ Text('Unable to verify authentication status: ${snapshot.error?.toString() ?? 'Unknown error'}'),
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7de82ad and 47bb227.

⛔ Files ignored due to path filters (1)
  • mobile-v3/pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • mobile-v3/lib/main.dart (3 hunks)
  • mobile-v3/lib/src/app/shared/repository/base_repository.dart (2 hunks)
  • mobile-v3/pubspec.yaml (1 hunks)
🔇 Additional comments (3)
mobile-v3/lib/src/app/shared/repository/base_repository.dart (1)

Line range hint 19-43: LGTM! Robust error handling implementation

The changes implement proper token validation and structured error handling. The Bearer prefix is correctly added, and the error message extraction is well-implemented.

mobile-v3/pubspec.yaml (1)

58-58: Verify jwt_decoder version for security patches

The jwt_decoder package is correctly added with a caret version constraint. However, let's ensure we're using the most secure version.

✅ Verification successful

jwt_decoder version 2.0.1 is secure and up-to-date

The specified version ^2.0.1 is actually the latest available version, and the package shows strong health indicators:

  • Latest version: 2.0.1 (you're using the most recent release)
  • High popularity score: 0.99
  • Dart 3 compatible and null-safe
  • No known security vulnerabilities
  • Active usage with 628k+ downloads in the last 30 days
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check latest version and security advisories for jwt_decoder
echo "Checking latest version..."
curl -s https://pub.dev/packages/jwt_decoder.json | jq -r '.latest.version'

echo "Checking for known vulnerabilities..."
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: PUB, package: "jwt_decoder") {
    nodes {
      advisory {
        summary
        severity
        publishedAt
      }
      vulnerableVersionRange
    }
  }
}'

Length of output: 518


Script:

#!/bin/bash
# Let's try an alternative approach to check the package version
# Check if jwt_decoder is used in the codebase and its version from pub.dev
echo "Checking jwt_decoder usage..."
rg "jwt_decoder" -l

echo "Checking version from pub.dev API..."
curl -s "https://pub.dev/api/packages/jwt_decoder" | jq -r '.versions[] | .version' | sort -V

# Also check the package score and popularity
echo "Checking package metrics..."
curl -s "https://pub.dev/api/packages/jwt_decoder/metrics" | jq -r '.score'

Length of output: 1098

mobile-v3/lib/main.dart (1)

193-201: Verify token cleanup on expiration

When a token is invalid or expired, ensure it's properly cleaned up from storage.

✅ Verification successful

Token cleanup is properly handled on invalidation

Based on the codebase analysis, the token validation and cleanup process is well-implemented:

  1. Token validation is properly handled in isValidToken() which checks for:

    • Null or empty tokens
    • Token expiration using JwtDecoder.isExpired()
  2. Token cleanup is managed through:

    • Token storage in HiveRepository with proper box isolation (authBox)
    • Automatic redirection to WelcomeScreen when token is invalid
    • Token validation before each authenticated request in BaseRepository

The current implementation follows security best practices by:

  • Validating tokens before use
  • Redirecting to authentication when tokens are invalid
  • Using secure storage (HiveRepository) for token management
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for token cleanup implementation
echo "Checking token cleanup patterns..."

# Look for token removal on expiration/invalidation
rg "removeData.*token.*authBox|clearToken|logout" -A 3

# Check for token cleanup in auth flows
ast-grep --pattern 'if (!isValidToken($TOKEN)) {
  $$$
}'

Length of output: 13455


Script:

#!/bin/bash
# Let's check for token validation and cleanup implementations
echo "Checking token validation and cleanup patterns..."

# Look for token validation implementation
ast-grep --pattern 'isValidToken($TOKEN) {
  $$$
}'

# Check for token storage and cleanup in auth-related files
rg "HiveRepository|authBox|token" -g "*.dart" -A 3

# Look for auth state management
rg "auth.*state|token.*state" -g "*.dart" -A 3

Length of output: 21400

Comment on lines +226 to +238
bool isValidToken(String? token) {
if (token == null || token.isEmpty) return false;
try {
if (JwtDecoder.isExpired(token)) {
logInfo('Token has expired');
return false;
}
return true;
} catch (e) {
logError('Token validation failed', e, StackTrace.current);
return false;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance JWT validation checks

While the current implementation handles expiration and basic validation, consider adding more comprehensive JWT checks.

 bool isValidToken(String? token) {
   if (token == null || token.isEmpty) return false;
   try {
+    final Map<String, dynamic> decodedToken = JwtDecoder.decode(token);
+    
+    // Validate required claims
+    if (!decodedToken.containsKey('sub') || !decodedToken.containsKey('iat')) {
+      logInfo('Token missing required claims');
+      return false;
+    }
+
     if (JwtDecoder.isExpired(token)) {
       logInfo('Token has expired');
       return false;
     }
+
+    // Check if token is not used before its issuance
+    final iat = DateTime.fromMillisecondsSinceEpoch(decodedToken['iat'] * 1000);
+    if (iat.isAfter(DateTime.now())) {
+      logInfo('Token used before issuance');
+      return false;
+    }
+
     return true;
   } catch (e) {
     logError('Token validation failed', e, StackTrace.current);
     return false;
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
bool isValidToken(String? token) {
if (token == null || token.isEmpty) return false;
try {
if (JwtDecoder.isExpired(token)) {
logInfo('Token has expired');
return false;
}
return true;
} catch (e) {
logError('Token validation failed', e, StackTrace.current);
return false;
}
}
bool isValidToken(String? token) {
if (token == null || token.isEmpty) return false;
try {
final Map<String, dynamic> decodedToken = JwtDecoder.decode(token);
// Validate required claims
if (!decodedToken.containsKey('sub') || !decodedToken.containsKey('iat')) {
logInfo('Token missing required claims');
return false;
}
if (JwtDecoder.isExpired(token)) {
logInfo('Token has expired');
return false;
}
// Check if token is not used before its issuance
final iat = DateTime.fromMillisecondsSinceEpoch(decodedToken['iat'] * 1000);
if (iat.isAfter(DateTime.now())) {
logInfo('Token used before issuance');
return false;
}
return true;
} catch (e) {
logError('Token validation failed', e, StackTrace.current);
return false;
}
}

@Baalmart Baalmart merged commit 5e6bc96 into staging Dec 15, 2024
31 checks passed
@Baalmart Baalmart deleted the null-check-operator-fix branch December 15, 2024 09:53
@Baalmart Baalmart mentioned this pull request Dec 15, 2024
1 task
@coderabbitai coderabbitai bot mentioned this pull request Jan 30, 2025
7 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants