- 
                Notifications
    
You must be signed in to change notification settings  - Fork 4.1k
 
feat(auth): validatePassword method/PasswordPolicy Support #17439
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
          
     Merged
      
      
    
  
     Merged
                    Changes from 22 commits
      Commits
    
    
            Show all changes
          
          
            31 commits
          
        
        Select commit
          Hold shift + click to select a range
      
      ec8d519
              
                feat(auth): PasswordPolicy Support
              
              
                MichaelVerdon 55e8400
              
                feat: license headers and impl start
              
              
                MichaelVerdon 1823649
              
                feat: Password Policy Logic finished
              
              
                MichaelVerdon 226fcde
              
                feat: add unit tests
              
              
                MichaelVerdon 5ab2b37
              
                feat: expose method
              
              
                MichaelVerdon 1d23e57
              
                fix: rename method
              
              
                MichaelVerdon 928f5e2
              
                chore: refactor, make explicit as possible
              
              
                MichaelVerdon 8060d70
              
                feat: add e2e
              
              
                MichaelVerdon 21c9ad1
              
                feat: change field types
              
              
                MichaelVerdon 7d8d60f
              
                chore: add license headers
              
              
                MichaelVerdon c21b3bc
              
                chore: fix analyze
              
              
                MichaelVerdon 70b7e49
              
                chore: format-ci
              
              
                MichaelVerdon 1be1baa
              
                chore: remove duplicate
              
              
                MichaelVerdon b0ef9e2
              
                chore: undo accidental deletion
              
              
                MichaelVerdon 9936e46
              
                chore: fix analyze
              
              
                MichaelVerdon 9c8554d
              
                fix: expose apis
              
              
                MichaelVerdon de5e14d
              
                chore: formatting
              
              
                MichaelVerdon 0555134
              
                chore: sort dependencies alphabeticaly
              
              
                MichaelVerdon b13f33e
              
                chore: more e2e tests
              
              
                MichaelVerdon 03e3f8d
              
                chore: refactor
              
              
                MichaelVerdon 0b78622
              
                chore: refactor
              
              
                MichaelVerdon 69391a1
              
                chore: refactor
              
              
                MichaelVerdon 87899f8
              
                chore: fix
              
              
                MichaelVerdon 0e68d2b
              
                chore: create internals
              
              
                MichaelVerdon fa7c0c2
              
                chore: run format
              
              
                MichaelVerdon 27f3476
              
                fix: shift into platform_interface
              
              
                MichaelVerdon 15705e1
              
                fix: readd method
              
              
                MichaelVerdon 18c5d51
              
                fix: pass apikey through method instead
              
              
                MichaelVerdon cfb5190
              
                format: melos run format
              
              
                MichaelVerdon 3cd99e1
              
                chore: remove import
              
              
                MichaelVerdon 4871d79
              
                chore: keep internals internal
              
              
                MichaelVerdon File filter
Filter by extension
Conversations
          Failed to load comments.   
        
        
          
      Loading
        
  Jump to
        
          Jump to file
        
      
      
          Failed to load files.   
        
        
          
      Loading
        
  Diff view
Diff view
There are no files selected for viewing
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
        
          
          
            49 changes: 49 additions & 0 deletions
          
          49 
        
  packages/firebase_auth/firebase_auth/lib/src/password_policy/password_policy.dart
  
  
      
      
   
        
      
      
    
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| // Copyright 2025, the Chromium project authors. Please see the AUTHORS file | ||
| // for details. All rights reserved. Use of this source code is governed by a | ||
| // BSD-style license that can be found in the LICENSE file. | ||
| class PasswordPolicy { | ||
| final Map<String, dynamic> policy; | ||
| 
     | 
||
| // Backend enforced minimum | ||
| late final int minPasswordLength; | ||
| late final int? maxPasswordLength; | ||
| late final bool? containsLowercaseCharacter; | ||
| late final bool? containsUppercaseCharacter; | ||
| late final bool? containsNumericCharacter; | ||
| late final bool? containsNonAlphanumericCharacter; | ||
| late final int schemaVersion; | ||
| late final List<String> allowedNonAlphanumericCharacters; | ||
| late final String enforcementState; | ||
| 
     | 
||
| PasswordPolicy(this.policy) { | ||
| initialize(); | ||
| } | ||
| 
     | 
||
| void initialize() { | ||
| final Map<String, dynamic> customStrengthOptions = | ||
| policy['customStrengthOptions'] ?? {}; | ||
| 
     | 
||
| minPasswordLength = customStrengthOptions['minPasswordLength'] ?? 6; | ||
| maxPasswordLength = customStrengthOptions['maxPasswordLength']; | ||
| containsLowercaseCharacter = | ||
| customStrengthOptions['containsLowercaseCharacter']; | ||
| containsUppercaseCharacter = | ||
| customStrengthOptions['containsUppercaseCharacter']; | ||
| containsNumericCharacter = | ||
| customStrengthOptions['containsNumericCharacter']; | ||
| containsNonAlphanumericCharacter = | ||
| customStrengthOptions['containsNonAlphanumericCharacter']; | ||
| 
     | 
||
| schemaVersion = policy['schemaVersion'] ?? 1; | ||
| allowedNonAlphanumericCharacters = List<String>.from( | ||
| policy['allowedNonAlphanumericCharacters'] ?? | ||
| customStrengthOptions['allowedNonAlphanumericCharacters'] ?? | ||
| [], | ||
| ); | ||
| 
     | 
||
| final enforcement = policy['enforcement'] ?? policy['enforcementState']; | ||
| enforcementState = enforcement == 'ENFORCEMENT_STATE_UNSPECIFIED' | ||
| ? 'OFF' | ||
| : (enforcement ?? 'OFF'); | ||
| } | ||
| } | 
        
          
          
            49 changes: 49 additions & 0 deletions
          
          49 
        
  packages/firebase_auth/firebase_auth/lib/src/password_policy/password_policy_api.dart
  
  
      
      
   
        
      
      
    
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| // Copyright 2025, the Chromium project authors. Please see the AUTHORS file | ||
| // for details. All rights reserved. Use of this source code is governed by a | ||
| // BSD-style license that can be found in the LICENSE file. | ||
| 
     | 
||
| import 'package:firebase_auth/firebase_auth.dart'; | ||
| import 'package:http/http.dart' as http; | ||
| import 'dart:convert'; | ||
| import 'dart:core'; | ||
| 
     | 
||
| class PasswordPolicyApi { | ||
| final FirebaseAuth _auth; | ||
| final String _apiUrl = | ||
| 'https://identitytoolkit.googleapis.com/v2/passwordPolicy?key='; | ||
| 
     | 
||
| PasswordPolicyApi(this._auth); | ||
| 
     | 
||
| final int _schemaVersion = 1; | ||
| 
     | 
||
| Future<PasswordPolicy> fetchPasswordPolicy() async { | ||
| try { | ||
| final String _apiKey = _auth.app.options.apiKey; | ||
| final response = await http.get(Uri.parse('$_apiUrl$_apiKey')); | ||
| if (response.statusCode == 200) { | ||
| final policy = json.decode(response.body); | ||
| 
     | 
||
| // Validate schema version | ||
| final _schemaVersion = policy['schemaVersion']; | ||
| if (!isCorrectSchemaVersion(_schemaVersion)) { | ||
| throw Exception( | ||
| 'Schema Version mismatch, expected version 1 but got $policy', | ||
| ); | ||
| } | ||
| 
     | 
||
| Map<String, dynamic> rawPolicy = json.decode(response.body); | ||
| return PasswordPolicy(rawPolicy); | ||
| } else { | ||
| throw Exception( | ||
| 'Failed to fetch password policy, status code: ${response.statusCode}', | ||
| ); | ||
| } | ||
| } catch (e) { | ||
| throw Exception('Failed to fetch password policy: $e'); | ||
| } | ||
| } | ||
| 
     | 
||
| bool isCorrectSchemaVersion(int schemaVersion) { | ||
| return _schemaVersion == schemaVersion; | ||
| } | ||
| } | 
        
          
          
            92 changes: 92 additions & 0 deletions
          
          92 
        
  packages/firebase_auth/firebase_auth/lib/src/password_policy/password_policy_impl.dart
  
  
      
      
   
        
      
      
    
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| // Copyright 2025, the Chromium project authors. Please see the AUTHORS file | ||
| // for details. All rights reserved. Use of this source code is governed by a | ||
| // BSD-style license that can be found in the LICENSE file. | ||
| import 'dart:core'; | ||
| import 'password_policy.dart'; | ||
| import 'password_validation_status.dart'; | ||
| 
     | 
||
| class PasswordPolicyImpl { | ||
| final PasswordPolicy _policy; | ||
| 
     | 
||
| PasswordPolicyImpl(this._policy); | ||
| 
     | 
||
| // Getter to access the policy | ||
| PasswordPolicy get policy => _policy; | ||
| 
     | 
||
| PasswordValidationStatus isPasswordValid(String password) { | ||
| PasswordValidationStatus status = PasswordValidationStatus(true, _policy); | ||
| 
     | 
||
| _validatePasswordLengthOptions(password, status); | ||
| _validatePasswordCharacterOptions(password, status); | ||
| 
     | 
||
| return status; | ||
| } | ||
| 
     | 
||
| void _validatePasswordLengthOptions( | ||
| String password, | ||
| PasswordValidationStatus status, | ||
| ) { | ||
| int minPasswordLength = _policy.minPasswordLength; | ||
| int? maxPasswordLength = _policy.maxPasswordLength; | ||
| 
     | 
||
| status.meetsMinPasswordLength = password.length >= minPasswordLength; | ||
| if (!status.meetsMinPasswordLength) { | ||
| status.isValid = false; | ||
| } | ||
| if (maxPasswordLength != null) { | ||
| status.meetsMaxPasswordLength = password.length <= maxPasswordLength; | ||
| if (!status.meetsMaxPasswordLength) { | ||
| status.isValid = false; | ||
| } | ||
| } | ||
| } | ||
| 
     | 
||
| void _validatePasswordCharacterOptions( | ||
| String password, | ||
| PasswordValidationStatus status, | ||
| ) { | ||
| bool? requireLowercase = _policy.containsLowercaseCharacter; | ||
| bool? requireUppercase = _policy.containsUppercaseCharacter; | ||
| bool? requireDigits = _policy.containsNumericCharacter; | ||
| bool? requireSymbols = _policy.containsNonAlphanumericCharacter; | ||
| 
     | 
||
| if (requireLowercase ?? false) { | ||
| status.meetsLowercaseRequirement = password.contains(RegExp('[a-z]')); | ||
| if (!status.meetsLowercaseRequirement) { | ||
| status.isValid = false; | ||
| } | ||
| } | ||
| if (requireUppercase ?? false) { | ||
| status.meetsUppercaseRequirement = password.contains(RegExp('[A-Z]')); | ||
| if (!status.meetsUppercaseRequirement) { | ||
| status.isValid = false; | ||
| } | ||
| } | ||
| if (requireDigits ?? false) { | ||
| status.meetsDigitsRequirement = password.contains(RegExp('[0-9]')); | ||
| if (!status.meetsDigitsRequirement) { | ||
| status.isValid = false; | ||
| } | ||
| } | ||
| if (requireSymbols ?? false) { | ||
| // Check if password contains any non-alphanumeric characters | ||
| bool hasSymbol = false; | ||
| if (_policy.allowedNonAlphanumericCharacters.isNotEmpty) { | ||
| // Check against allowed symbols | ||
| for (final String symbol in _policy.allowedNonAlphanumericCharacters) { | ||
| if (password.contains(symbol)) { | ||
| hasSymbol = true; | ||
| break; | ||
| } | ||
| } | ||
| } else { | ||
| // Check for any non-alphanumeric character | ||
| hasSymbol = password.contains(RegExp('[^a-zA-Z0-9]')); | ||
| } | ||
| status.meetsSymbolsRequirement = hasSymbol; | ||
| if (!hasSymbol) { | ||
| status.isValid = false; | ||
| } | ||
| } | ||
| } | ||
| } | 
        
          
          
            19 changes: 19 additions & 0 deletions
          
          19 
        
  packages/firebase_auth/firebase_auth/lib/src/password_policy/password_validation_status.dart
  
  
      
      
   
        
      
      
    
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| // Copyright 2025, the Chromium project authors. Please see the AUTHORS file | ||
| // for details. All rights reserved. Use of this source code is governed by a | ||
| // BSD-style license that can be found in the LICENSE file. | ||
| import 'password_policy.dart'; | ||
| 
     | 
||
| class PasswordValidationStatus { | ||
| bool isValid; | ||
| final PasswordPolicy passwordPolicy; | ||
| 
     | 
||
| // Initialize all fields to true by default (meaning they pass validation) | ||
| bool meetsMinPasswordLength = true; | ||
| bool meetsMaxPasswordLength = true; | ||
| bool meetsLowercaseRequirement = true; | ||
| bool meetsUppercaseRequirement = true; | ||
| bool meetsDigitsRequirement = true; | ||
| bool meetsSymbolsRequirement = true; | ||
| 
     | 
||
| PasswordValidationStatus(this.isValid, this.passwordPolicy); | ||
| } | 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
      
      Oops, something went wrong.
        
    
  
      
      Oops, something went wrong.
        
    
  
  Add this suggestion to a batch that can be applied as a single commit.
  This suggestion is invalid because no changes were made to the code.
  Suggestions cannot be applied while the pull request is closed.
  Suggestions cannot be applied while viewing a subset of changes.
  Only one suggestion per line can be applied in a batch.
  Add this suggestion to a batch that can be applied as a single commit.
  Applying suggestions on deleted lines is not supported.
  You must change the existing code in this line in order to create a valid suggestion.
  Outdated suggestions cannot be applied.
  This suggestion has been applied or marked resolved.
  Suggestions cannot be applied from pending reviews.
  Suggestions cannot be applied on multi-line comments.
  Suggestions cannot be applied while the pull request is queued to merge.
  Suggestion cannot be applied right now. Please check back later.
  
    
  
    
Uh oh!
There was an error while loading. Please reload this page.