| 1 |  |  | """User authentification """ | 
            
                                                                                                            
                            
            
                                    
            
            
                | 2 |  |  | # pylint: disable=no-name-in-module, no-self-argument | 
            
                                                                                                            
                            
            
                                    
            
            
                | 3 |  |  | import hashlib | 
            
                                                                                                            
                            
            
                                    
            
            
                | 4 |  |  | import os | 
            
                                                                                                            
                            
            
                                    
            
            
                | 5 |  |  | from datetime import datetime | 
            
                                                                                                            
                            
            
                                    
            
            
                | 6 |  |  | from typing import Literal, Optional | 
            
                                                                                                            
                            
            
                                    
            
            
                | 7 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 8 |  |  | from pydantic import (BaseModel, EmailStr, Field, ValidationInfo, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 9 |  |  |                       field_validator) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 10 |  |  | from typing_extensions import Annotated | 
            
                                                                                                            
                            
            
                                    
            
            
                | 11 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 12 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 13 |  |  | class DocumentBaseModel(BaseModel): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 14 |  |  |     """DocumentBaseModel""" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 15 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 16 |  |  |     id: str = Field(None, alias="_id") | 
            
                                                                                                            
                            
            
                                    
            
            
                | 17 |  |  |     inserted_at: Optional[datetime] = None | 
            
                                                                                                            
                            
            
                                    
            
            
                | 18 |  |  |     updated_at: Optional[datetime] = None | 
            
                                                                                                            
                            
            
                                    
            
            
                | 19 |  |  |     deleted_at: Optional[datetime] = None | 
            
                                                                                                            
                                                                
            
                                    
            
            
                | 20 |  |  |  | 
            
                                                                        
                            
            
                                    
            
            
                | 21 |  |  |     def model_dump(self, **kwargs) -> dict: | 
            
                                                                        
                            
            
                                    
            
            
                | 22 |  |  |         """Model to dict.""" | 
            
                                                                        
                            
            
                                    
            
            
                | 23 |  |  |         values = super().model_dump(**kwargs) | 
            
                                                                        
                            
            
                                    
            
            
                | 24 |  |  |         if "id" in values and values["id"]: | 
            
                                                                        
                            
            
                                    
            
            
                | 25 |  |  |             values["_id"] = values["id"] | 
            
                                                                        
                            
            
                                    
            
            
                | 26 |  |  |         if "exclude" in kwargs and "_id" in kwargs["exclude"]: | 
            
                                                                        
                            
            
                                    
            
            
                | 27 |  |  |             values.pop("_id") | 
            
                                                                        
                            
            
                                    
            
            
                | 28 |  |  |         return values | 
            
                                                                                                            
                            
            
                                    
            
            
                | 29 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 30 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 31 |  |  | def hashing(password: bytes, values: dict) -> str: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 32 |  |  |     """Hash password and return it as string""" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 33 |  |  |     return hashlib.scrypt(password=password, salt=values['salt'], | 
            
                                                                                                            
                            
            
                                    
            
            
                | 34 |  |  |                           n=values['n'], r=values['r'], | 
            
                                                                                                            
                            
            
                                    
            
            
                | 35 |  |  |                           p=values['p']).hex() | 
            
                                                                                                            
                            
            
                                    
            
            
                | 36 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 37 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 38 |  |  | def validate_password(password: str, values: ValidationInfo): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 39 |  |  |     """Check if password has at least a letter and a number""" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 40 |  |  |     upper = False | 
            
                                                                                                            
                            
            
                                    
            
            
                | 41 |  |  |     lower = False | 
            
                                                                                                            
                            
            
                                    
            
            
                | 42 |  |  |     number = False | 
            
                                                                                                            
                            
            
                                    
            
            
                | 43 |  |  |     for char in password: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 44 |  |  |         if char.isupper(): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 45 |  |  |             upper = True | 
            
                                                                                                            
                            
            
                                    
            
            
                | 46 |  |  |         if char.isnumeric(): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 47 |  |  |             number = True | 
            
                                                                                                            
                            
            
                                    
            
            
                | 48 |  |  |         if char.islower(): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 49 |  |  |             lower = True | 
            
                                                                                                            
                            
            
                                    
            
            
                | 50 |  |  |         if number and upper and lower: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 51 |  |  |             return hashing(password.encode(), values.data['hash'].model_dump()) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 52 |  |  |     raise ValueError('value should contain ' + | 
            
                                                                                                            
                            
            
                                    
            
            
                | 53 |  |  |                      'minimun 8 characters, ' + | 
            
                                                                                                            
                            
            
                                    
            
            
                | 54 |  |  |                      'at least one upper case character, ' + | 
            
                                                                                                            
                            
            
                                    
            
            
                | 55 |  |  |                      'at least 1 numeric character [0-9]') | 
            
                                                                                                            
                            
            
                                    
            
            
                | 56 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 57 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 58 |  |  | class HashSubDoc(BaseModel): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 59 |  |  |     """HashSubDoc. Parameters for hash.scrypt function""" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 60 |  |  |     salt: bytes = Field(default=None, validate_default=True) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 61 |  |  |     n: int = 8192 | 
            
                                                                                                            
                            
            
                                    
            
            
                | 62 |  |  |     r: int = 8 | 
            
                                                                                                            
                            
            
                                    
            
            
                | 63 |  |  |     p: int = 1 | 
            
                                                                                                            
                            
            
                                    
            
            
                | 64 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 65 |  |  |     @field_validator('salt', mode='before') | 
            
                                                                                                            
                            
            
                                    
            
            
                | 66 |  |  |     @classmethod | 
            
                                                                                                            
                            
            
                                    
            
            
                | 67 |  |  |     def create_salt(cls, salt): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 68 |  |  |         """Create random salt value""" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 69 |  |  |         return salt or os.urandom(16) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 70 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 71 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 72 |  |  | class UserDoc(DocumentBaseModel): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 73 |  |  |     """UserDocumentModel.""" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 74 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 75 |  |  |     username: str = Field( | 
            
                                                                                                            
                            
            
                                    
            
            
                | 76 |  |  |         min_length=1, max_length=64, pattern=r'^[a-zA-Z0-9_-]+$' | 
            
                                                                                                            
                            
            
                                    
            
            
                | 77 |  |  |     ) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 78 |  |  |     hash: HashSubDoc | 
            
                                                                                                            
                            
            
                                    
            
            
                | 79 |  |  |     state: Literal['active', 'inactive'] = 'active' | 
            
                                                                                                            
                            
            
                                    
            
            
                | 80 |  |  |     email: EmailStr | 
            
                                                                                                            
                            
            
                                    
            
            
                | 81 |  |  |     password: str = Field(min_length=8, max_length=64) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 82 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 83 |  |  |     _validate_password = field_validator('password')(validate_password) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 84 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 85 |  |  |     @staticmethod | 
            
                                                                                                            
                            
            
                                    
            
            
                | 86 |  |  |     def projection() -> dict: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 87 |  |  |         """Base model for projection.""" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 88 |  |  |         return { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 89 |  |  |             "_id": 0, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 90 |  |  |             "username": 1, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 91 |  |  |             "email": 1, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 92 |  |  |             'password': 1, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 93 |  |  |             'hash': 1, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 94 |  |  |             'state': 1, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 95 |  |  |             'inserted_at': 1, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 96 |  |  |             'updated_at': 1, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 97 |  |  |             'deleted_at': 1 | 
            
                                                                                                            
                            
            
                                    
            
            
                | 98 |  |  |         } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 99 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 100 |  |  |     @staticmethod | 
            
                                                                                                            
                            
            
                                    
            
            
                | 101 |  |  |     def projection_nopw() -> dict: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 102 |  |  |         """Model for projection without password""" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 103 |  |  |         return { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 104 |  |  |             "_id": 0, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 105 |  |  |             "username": 1, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 106 |  |  |             "email": 1, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 107 |  |  |             'state': 1, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 108 |  |  |             'inserted_at': 1, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 109 |  |  |             'updated_at': 1, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 110 |  |  |             'deleted_at': 1 | 
            
                                                                                                            
                            
            
                                    
            
            
                | 111 |  |  |         } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 112 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 113 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 114 |  |  | class UserDocUpdate(DocumentBaseModel): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 115 |  |  |     "UserDocUpdate use to validate data before updating" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 116 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 117 |  |  |     username: Optional[Annotated[str, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 118 |  |  |                                  Field(min_length=1, max_length=64, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 119 |  |  |                                        pattern=r'^[a-zA-Z0-9_-]+$')]] = None | 
            
                                                                                                            
                            
            
                                    
            
            
                | 120 |  |  |     email: Optional[EmailStr] = None | 
            
                                                                                                            
                            
            
                                    
            
            
                | 121 |  |  |     hash: Optional[HashSubDoc] = None | 
            
                                                                                                            
                            
            
                                    
            
            
                | 122 |  |  |     password: Optional[Annotated[str, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 123 |  |  |                                  Field(min_length=8, max_length=64)]] = None | 
            
                                                                                                            
                            
            
                                    
            
            
                | 124 |  |  |  | 
            
                                                                                                            
                                                                
            
                                    
            
            
                | 125 |  |  |     _validate_password = field_validator('password')(validate_password) | 
            
                                                        
            
                                    
            
            
                | 126 |  |  |  |