Package: tangerine-auth
Tangerine-AuthTangerine-Auth is a Python library that provides utilities for user authentication and secure handling of keys and encrypted data. It uses bcrypt for password hashing and JWT for token-based authentication.InstallationNOTE: Tangerine-Auth is currently in beta. It is not recommended for production use quite yet. Please feel free to download and play around with it in the meantime!  We are working on adding more tests and documentation. If you find any bugs or have any suggestions, please open an issue on GitHub.Use pip to install the package:pipinstalltangerine-authUsageYuzuThe general steps to integrating Yuzu into your application are as follows:Define two functions for creating a user in your database, and another for finding a user in your database. These functions should take a dictionary of user data as an argument and return a dictionary of user data. The user data dictionary should contain at least an email and password field. The user data dictionary can contain any other fields you want to store in your database. Here is an example:defget_user_by_email(email):conn=psycopg2.connect("postgresql://postgres:<your postgres password>@localhost:5432/local_development")cur=conn.cursor()cur.execute("SELECT * FROM tangerine.users WHERE email =%s",(email,))user=cur.fetchone()cur.close()conn.close()ifuser:return{'_id':user[0],'email':user[1],'password':user[2]}else:returnNonedefcreate_user(user_data):conn=psycopg2.connect("postgresql://<your postgres password>@localhost:5432/local_development")cur=conn.cursor()cur.execute("INSERT INTO tangerine.users (email, password) VALUES (%s,%s) RETURNING id",(user_data['email'],user_data['password']))user_id=cur.fetchone()[0]conn.commit()cur.close()conn.close()return{'_id':user_id,'email':user_data['email'],'password':user_data['password']}Create a KeyLime object and pass it to the Yuzu constructor. The KeyLime object is used for securely handling keys and encrypted data. You can use the KeyLime object to encrypt and decrypt data, and to store and retrieve keys.Create a Yuzu object and pass it the KeyLime object, the function for finding a user in your database, and the function for creating a user in your database.(optional) Yuzu uses bcrypt to hash passwords. You can optionally pass a custom hash function to the Yuzu constructor. The custom hash function should take a password string as an argument and return a hashed password string.After step 3 is complete, you can now use the Yuzu object to sign up a new user, log in a user, log out a user, and verify authentication tokens.Yuzu was built to work with flask as well as tangerine, there are currently two JWT middlewares bundled with Yuzu, one for flask and one for tangerine. Once you have initialized the Yuzu class properly, you can use the Tangerine middleware by calling:app.use(auth.jwt_middleware).The Flask version JWT middleware is still experimental and, could be issue-prone.
It will not be recommended for production use until it has been tested more thoroughly.
The JWT middleware works a bit different in Flask, To use it with Flask, you use it as a decorator:fromflaskimportFlaskfromyuzuimportYuzuapp=Flask(__name__)defget_user_by_email(email):# Logic to create user in DBpassdefcreate_user(user_data):# Logic to create user in DBpassauth=Yuzu(keychain,get_user_by_email,create_user)# Fill with your functions@app.route('/secure_route')@auth.flask_jwt_middleware(auth)defsecure_route():# Your secure code here. This will only run if the JWT is valid.return"This is a secure route"if__name__=="__main__":app.run(debug=True)NOTE: The auth middleware appends the user data to the Ctx object. You can access the user data in your route handler by callingctx.auth.userorctx.get("user")Yuzu is a class that provides user authentication functionalities. It uses bcrypt for password hashing and JWT for creating and verifying authentication tokens.Below are the key methods of the Yuzu class:-`__init__(self,keychain,get_user_by_email,create_user,hash_func:Optional[Callable]=None)`:InitializestheYuzuobject.-`get_config(self,key_name:str)->str`:Fetchestheconfigurationvalueforagivenkey.-`authenticate(self,email:str,password:str)->bool`:Checksifthegivenemailandpasswordarevalid.-`generate_auth_token(self,user_id:str,email:str)->str`:Generatesanauthenticationtokenforthegivenuser.-`verify_auth_token(self,token:str)->dict`:Verifiesifthegivenauthenticationtokenisvalid.-`sign_up(self,user_data:dict)->dict`:Signsupanewuserwiththegivenuserdata.-`login(self,email:str,password:str)->Tuple[str,str]`:Logsinauserwiththegivenemailandpassword.-`logout(self)`:Logsoutthecurrentuser.-`jwt_middleware()`:TangerinemiddlewareforJWTauthentication.-`flask_jwt_middleware(yuzu_instance)`:FlaskmiddlewareforJWTauthentication.KeyLimeKeyLime is a class that provides functionalities for securely handling keys and encrypted data.Below are the key methods of the KeyLime class:-`__init__(self,keychain:Dict[str,bytes]={})`:InitializestheKeyLimeobject.-`add_key(self,key_name:str,key:bytes)`:Addsakeytothekeychain.-`remove_key(self,key_name:str)`:Removesakeyfromthekeychain.-`get_key(self,key_name:str)->bytes`:Fetchesakeyfromthekeychain.-`encrypt(self,key_name:str,message:str)->str`:Encryptsagivenmessageusingakeyfromthekeychain.-`decrypt(self,key_name:str,cipher_text:str)->str`:Decryptsagivenciphertextusingakeyfromthekeychain.The general conept for this is that you want to write two functions, one for finding
a user in your chosen database system, and one for creating a user in your chosen
database system. These functions should take a dictionary of user data as an argument
and return a dictionary of user data. The user data dictionary should contain at least
an email and password field. The user data dictionary can contain any other fields
you want to store in your database.Here is an example of how to use the Yuzu and KeyLime classes:fromtangerineimportTangerine,Ctx,RouterfrompymongoimportMongoClientfromtangerine.key_limeimportKeyLimefromtangerine.yuzuimportYuzuimportjsonimportjwtimporthashlibapp=Tangerine(debug_level=1)client=MongoClient('mongodb://localhost:27017/')keychain=KeyLime({"SECRET_KEY":"ILOVECATS",})defget_user_by_email(email):db=client['mydatabase']users=db['users']query={'email':email}user=users.find_one(query)ifuser:user['_id']=str(user['_id'])# Convert ObjectId to stringreturnuserdefcreate_user(user_data):db=client['mydatabase']users=db['users']result=users.insert_one(user_data)ifresult.inserted_id:user_data['_id']=str(result.inserted_id)# Convert ObjectId to stringreturnuser_dataauth=Yuzu(keychain,get_user_by_email,create_user)# serve static files to any request not starting with /apiapp.static('^/(?!api).*$','./public')# This is how you define a custom middleware.defhello_middle(ctx:Ctx,next)->None:ctx.hello_message=json.dumps({"message":"Hello from middleware!"})next()# ==================== AUTH HANDLERS ====================defapi_hello_world(ctx:Ctx)->None:ctx.body=ctx.hello_messagectx.send(200,content_type='application/json')defsignup(ctx:Ctx)->None:user_data=ctx.request.bodycreated_user=auth.sign_up(user_data)ifcreated_user:ctx.body=json.dumps(created_user)ctx.send(201,content_type='application/json')else:ctx.send(500,content_type='application/json')deflogin(ctx:Ctx)->None:user_data=ctx.request.bodyemail=user_data['email']password=user_data['password']user_id,token=auth.login(email,password)print(ctx.user,"HELLO FROM LOGIN")iftoken:ctx.body=json.dumps({"message":"Logged in successfully","token":token})ctx.set_res_header("Set-Cookie",f"auth_token={token}; HttpOnly; Path=/")ctx.send(200,content_type='application/json')# Set the token as a cookie or in the response headerselse:ctx.body=json.dumps({"message":"Invalid credentials"})ctx.send(401,content_type='application/json')deflogout(ctx:Ctx)->None:auth.logout(ctx)ctx.body=json.dumps({"message":"Logged out successfully"})ctx.send(200,content_type='application/json')@Router.auth_requireddefget_protected_content(ctx:Ctx)->None:ctx.body=json.dumps({"message":"This is protected content. Only authenticated users can see this. I hope you feel special 🍊🍊🍊."})ctx.send(200,content_type='application/json')# ==================== API ROUTES ====================# if you need to bind more variables to your handler, you can pass in a closureapi_router=Router(prefix='/api')api_router.post('/logout',logout)api_router.post('/login',login)api_router.post('/signup',signup)api_router.get('/hello',api_hello_world)# api_router.get('/users', get_and_delete_users)api_router.get('/protected',get_protected_content)app.use(hello_middle)app.use(auth.jwt_middleware)app.use_router(api_router)app.start()Advanced Configuration for YuzuYuzu is designed to be flexible, allowing you to adapt it to the specific needs of your project. One way in which you can customize its behavior is by changing the default password hashing and verification functions.Custom Hashing and Verification FunctionsBy default, Yuzu uses thebcryptlibrary for password hashing and verification. If you want to use a different approach, you can pass your own hashing and verification functions to the Yuzu class constructor. Here's an example of how to do it:Using SHA256 for Hashingimporthashlibdefmy_hash_func(password:str,salt:str=None)->str:returnhashlib.sha256(password.encode()).hexdigest()defmy_check_password_func(password:str,hashed_password:str,salt:str=None)->bool:returnhashlib.sha256(password.encode()).hexdigest()==hashed_passwordauth=Yuzu(keychain,get_user_by_email,create_user,hash_func=my_hash_func,checkpw_func=my_check_password_func)Using Argon2 for Hashing
Argon2 is a modern, secure password hashing algorithm that is recommended by the Password Hashing Competition. Here's an example of how to use it with Yuzu:fromargon2importPasswordHasher,exceptionsph=PasswordHasher(time_cost=16,memory_cost=65536)defmy_hash_func(password:str,salt:str=None)->str:returnph.hash(password)defmy_check_password_func(password:str,hashed_password:str,salt:str=None)->bool:try:returnph.verify(hashed_password,password)exceptexceptions.VerifyMismatchError:returnFalseauth=Yuzu(keychain,get_user_by_email,create_user,hash_func=my_hash_func,checkpw_func=my_check_password_func)These examples allow you to switch from the default bcrypt algorithm to SHA256 or Argon2. You could also modify these functions to change the difficulty of the hash function (e.g., by increasing the number of iterations or the memory usage) as per your specific requirements.Remember that changing these functions can have implications for the security of your application. You should understand the workings of the chosen hash function and its strengths and weaknesses before making a decision.