Authentication, JWT, and permission scoping for Sanic

Overview

Sanic JWT

Latest PyPI version Python versions Version status MIT License

Build Status Documentation Codacy Badge Test Coverage Code style: black

Sanic JWT adds authentication protection and endpoints to Sanic.

It is both easy to get up and running, and extensible for the developer. It can act to protect endpoints and also provide authentication scoping, all wrapped into a nice JWT.

Read the documentation | View the source code


What do I do?

It's easy: (1) install, (2) initialize, and (3) authenticate.

Install:

pip install sanic-jwt

Initialize:

from sanic import Sanic
from sanic_jwt import Initialize

def my_authenticate(request, *args, **kwargs):
    ...

app = Sanic()
Initialize(
    app,
    authenticate=my_authenticate
)

Authenticate:

http://localhost/auth

Can I customize it?

Definitely! Sanic JWT is made to allow developers to customize the operation to fit their needs. Check out the documentation to learn how.

Comments
  • is_authenticated raises unauthorized

    is_authenticated raises unauthorized

    Hi! I've noticed that is_authenticated raises unauthorized instead of returning False To me, is_authenticated will be useful before check the payload and retrieve the user giving the fact that extract_payload also raises 400 when the call is anonymous

    am I correct or misunderstanding something?

    enhancement opinions welcome 
    opened by Garito 29
  • Overhaul initialization and configuration

    Overhaul initialization and configuration

    Goal

    Stabilize the package to be able to call it a stable v 1.0 release.

    Approach

    This PR encompasses a number of changes that will allow the developer both greater simplicity to get up and running and greater flexibility to customize for specific use cases.

    In doing so, Sanic JWT is migrating away from the initialize method towards a class based Initialize.

    While I am striving to maintain backwards compatibility as much as possible (for example, initialize is now a wrapper around the new class), there may be some things that break from older versions.

    Target Release Date

    March 1, 2018

    New Features

    • As discussed, there will be a new implementation with Initialize
    • Settings can be configured via the new Configuration class
    • All responses for all endpoints can be extendable and interchangeable

    Example

    This can still be installed very easily on a simple setup:

    from sanic_jwt import Initialize
    
    Initialize(app, authenticate=my_authenticate_method, url_prefix='/myjwt')
    

    Or, you can customize by subclassing:

    from sanic_jwt import Authentication, Configuration, Initialize, Response
    
    class MyAuthentication(Authentication):
        async def build_payload(self, *args, **kwargs):
            payload = super().build_payload(*args, **kwargs)
            payload.update({'foo': 'bar'})
            return payload
    
    class MyConfig(Configuration):
        access_token_name = 'custom-token'
        url_prefix = '/myjwt'
    
    class MyResponse(Response):
        @staticmethod
        def extend_authenticate(request,
                                user=None,
                                access_token=None,
                                refresh_token=None):
            return {'foo': 'bar'}
    
    class MyInitialize(Initialize):
        authentication_class = MyAuthentication
        configuration_class = MyConfig
        response_class = MyResponse
    
    MyInitialize(
        app,
        authenticate=lambda: True,
    )
    
    opened by ahopkins 29
  • Access User Detail in protected routes

    Access User Detail in protected routes

    Is there a way to inject user in protected routes? Something like this?

    @auth_bp.post("delete_account")
    @protected()
    async def delete_account(request):
        email = request['user]['email'] #access user details from request object
        logger.info("Deleting Account for {}".format(email))
        resp = auth_service.delete_account(email)
        return resp
    
    enhancement opinions welcome 
    opened by jaymalik217 26
  • Check init methods for awaitable

    Check init methods for awaitable

    Should run a check to make sure the following methods if implemented are awaitable:

    store_refresh_token retrieve_user retrieve_refresh_token authenticate

    bug enhancement warning documentation 
    opened by ahopkins 24
  • retrieve_user not async?

    retrieve_user not async?

    Hi! Is this error:

    2017-11-03 02:13:53 - (sanic)[ERROR]: Exception occurred while handling uri: "http://localhost:8000/auth/me"
    Traceback (most recent call last):
      File "/Users/garito/Lab/sanic/testRestAPI2/env/lib/python3.6/site-packages/sanic/app.py", line 503, in handle_request
        response = await response
      File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/coroutines.py", line 109, in __next__
        return self.gen.send(None)
      File "/Users/garito/Lab/sanic/testRestAPI2/env/lib/python3.6/site-packages/sanic_jwt/blueprint.py", line 80, in retrieve_user
        me = user.to_dict() if hasattr(user, 'to_dict') else dict(user)
    AssertionError: yield from wasn't used with future
    

    because retrieve_user can't be a coroutine? (async def retrieve_user)

    Thanks!

    opened by Garito 24
  • "Mixed" auth methods - headers and cookies

    Reading the docs and code, I saw that SANIC_JWT_COOKIE_SET is indeed a switch between using a cookie or a http header to authorize the request. Since I would like to use the same endpoint for browsers (that can use cookies) and other consumers (that doesn't use cookies, like a simple cURL call), is there a way to accomplish this "mixed" behavior? I can help with PRs, if needed.

    enhancement 
    opened by vltr 23
  • Async OPTIONS function not being awaited

    Async OPTIONS function not being awaited

    Hi. I have the following error:

    <CoroWrapper Tester.options() running at test.py:18, created at /fidessa/uatutils/Tools/Delivery/.pyenv/versions/3.6.4/lib/python3.6/asyncio/coroutines.py:85> was never yielded from
    Coroutine object created at (most recent call last, truncated to 10 last lines):
      File "/fidessa/uatutils/Tools/Delivery/main_venv/UGUI_UAT-480w8wdy/lib/python3.6/site-packages/sanic/server.py", line 609, in serve
        loop.run_forever()
      File "/fidessa/uatutils/Tools/Delivery/.pyenv/versions/3.6.4/lib/python3.6/asyncio/coroutines.py", line 126, in send
        return self.gen.send(value)
      File "/fidessa/uatutils/Tools/Delivery/main_venv/UGUI_UAT-480w8wdy/lib/python3.6/site-packages/sanic/app.py", line 556, in handle_request
        response = await response
      File "/fidessa/uatutils/Tools/Delivery/.pyenv/versions/3.6.4/lib/python3.6/asyncio/coroutines.py", line 110, in __next__
        return self.gen.send(None)
      File "/fidessa/uatutils/Tools/Delivery/main_venv/UGUI_UAT-480w8wdy/lib/python3.6/site-packages/sanic_jwt/decorators.py", line 42, in decorated_function
        return await utils.call(f, request, *args, **kwargs)
      File "/fidessa/uatutils/Tools/Delivery/.pyenv/versions/3.6.4/lib/python3.6/asyncio/coroutines.py", line 110, in __next__
        return self.gen.send(None)
      File "/fidessa/uatutils/Tools/Delivery/main_venv/UGUI_UAT-480w8wdy/lib/python3.6/site-packages/sanic_jwt/utils.py", line 44, in call
        fn = fn(*args, **kwargs)
      File "/fidessa/uatutils/Tools/Delivery/main_venv/UGUI_UAT-480w8wdy/lib/python3.6/site-packages/sanic/views.py", line 63, in view
        return self.dispatch_request(*args, **kwargs)
      File "/fidessa/uatutils/Tools/Delivery/main_venv/UGUI_UAT-480w8wdy/lib/python3.6/site-packages/sanic/views.py", line 46, in dispatch_request
        return handler(request, *args, **kwargs)
      File "/fidessa/uatutils/Tools/Delivery/.pyenv/versions/3.6.4/lib/python3.6/asyncio/coroutines.py", line 85, in debug_wrapper
        return CoroWrapper(gen, None)
    /fidessa/uatutils/Tools/Delivery/.pyenv/versions/3.6.4/lib/python3.6/asyncio/coroutines.py:126: RuntimeWarning: coroutine 'Tester.options' was never awaited
      return self.gen.send(value)
    

    I ran into this issue in my application and was also able to reproduce it with the following code:

    from sanic_cors.core import ALL_METHODS
    from sanic.views import HTTPMethodView
    from sanic.response import text
    from sanic_jwt import Authentication, initialize, protected
    from sanic import Sanic, Blueprint
    
    
    class TestMethodView(HTTPMethodView):
        async def options(self, *args, **kwargs):
            return text('ok')
    
    class Tester(TestMethodView):
        decorators = [protected()]
    
        async def get(self, request):
            return text('ok')
    
    bp = Blueprint('bp')
    bp.add_route(Tester.as_view(), '/test', methods=ALL_METHODS)
    
    
    class CustomAuth(Authentication):
        async def authenticate(self, request, *args, **kwargs):
            return {'username': 'Rich', 'password': 'not secure'}
    
        async def retrieve_user(self, request, payload, *args, **kwargs):
            if payload:
                user_id = payload.get('username', None)
                passwd = payload.get('password', None)
                return {'username': user_id, 'password': passwd}
    
        async def extend_payload(self, payload, user=None, *args, **kwargs):
            if user:
                payload.update({'extra_info': 'awesome!'})
            return payload
    
    
    def make_app():
        app = Sanic(__name__)
        app.config.SANIC_JWT_AUTHORIZATION_HEADER_PREFIX = 'JWT'
        app.config.SANIC_JWT_EXPIRATION_DELTA = 360000
        app.config.SANIC_JWT_USER_ID = 'username'
    
        initialize(app, authentication_class=CustomAuth)
        app.blueprint(bp)
        return app 
    
    make_app().go_fast(debug=True, host='0.0.0.0', port=9000)
    

    I should note that ALL_METHODS from sanic_cors is just a list that contains both 'GET' and 'OPTIONS'

    Replication steps:

    1. Log in
    2. Send an OPTIONS request to /test

    I'm using

    python==3.6.4
    sanic-jwt==1.1.0
    sanic==0.7.0
    

    I've been able to trace the issue to the call function in sanic-jwt/utils.py. The issue appears to be that somehow the options function isn't registered as a coroutine like the get function is. Maybe this issue needs to go to Sanic? I'm not sure. But the issue only occurs with sanic-jwt because the Sanic code automatically calls functions and then checks to see if they are awaitable. Relevant Sanic code:

                    # Run response handler
                    response = handler(request, *args, **kwargs)
                    if isawaitable(response):
                        response = await response
    

    I was able to solve the issue for myself by changing call to:

    async def call(fn, *args, **kwargs):
        if callable(fn):
            fn = fn(*args, **kwargs)
        if inspect.iscoroutinefunction(fn) or inspect.isawaitable(fn):
            fn = await fn                                                                                                                                                                                                                                          
        return fn
    

    I'd be more than happy to raise a PR for this. This is my first gh issue, so please let me know if I do something wrong.

    Please let me know if you need any further information.

    bug example 
    opened by rafmagns-skepa-dreag 16
  • Configuration getter

    Configuration getter

    One of the new ways in Version 1 to create settings is with the setter function on the Configuration class.

    from sanic_jwt import Configuration
    
    class MyConfiguration(Configuration):
        def set_access_token_name(self):
            return 'jwt'
    
    Initialize(
        app,
        configuration_class=MyConfiguration)
    

    Then, the Initialization class goes and installs all the various config settings on a config object.

    >>> print(config.access_token_name)
    'jwt'
    

    I propose that we change the way that config is used throughout the backend to make it a callable. Instead of doing config.access_token_name it would become config.get('access_token_name').

    Why?

    Then, we can not only have dynamic attributes set at initialization, but also when called.

    class MyConfiguration(Configuration):
        async def get_access_token_name(self):
            return await some_fancy_function()
    
    enhancement 
    opened by ahopkins 13
  • Add support for PyJwt_2_0_0

    Add support for PyJwt_2_0_0

    Goal

    PyJWT just released v2.0.0 which breaks sanic-jwt due to https://github.com/jpadilla/pyjwt/issues/529

    Approach

    Type checking before returning access token.

    opened by rahulraina7 12
  • custom authentication control

    custom authentication control

    I'm trying to add in a feature for people to create permanent api tokens for use with my application in conjunction with the normal access/refresh tokens that users experience either via a browser or via CLI. The use case is they want an API token that can be used to easily script out access with my application. When a user creates one of these new API tokens for themselves, I save that token and all of the user's information into a database so I can look it up later. This should theoretically allow me to look up a user's information based on this new token (and allow it to be revoked by simply removing it from the database). However, I'm having some trouble getting this to work.

    I added in the following to successfully return either the normal header token or my new api token:

    class MyConfig(Configuration):
        def get_authorization_header(self, request):
            if "apitoken" in request.headers:
                return "apitoken"
            return "authorization"
    
        def get_authorization_header_prefix(self, request):
            if "apitoken" in request.headers:
                return ""
            return "Bearer"
    

    The above seems to be working, but I can't seem to add in my own custom verification/authentication check anywhere. For an individual endpoint that I create in the my_views section, I can successfully do the following to get all of the user's api tokens (given normal access token or new api token):

    async def get(self, request, *args, **kwargs):
            try:
                payload = self.instance.auth.extract_payload(request, verify=True)
                try:
                    user = await utils.call(
                        self.instance.auth.retrieve_user, request, payload=payload
                    )
                    user_id = await self.instance.auth._get_user_id(user)
                except Exception as e:
                    return json({"status": "error", "error": "failed to get user from token"})
            except Exception as e:
                print("can't parse token as JWT, gonna try database lookup")
                try:
                    if 'apitoken' in request.headers:
                        try:
                            query = await db_model.apitokens_query()
                            db_apitoken = await db_objects.get(query, token=request.headers.get('apitoken'))
                            user_id = db_apitoken.operator.id
                        except Exception as e:
                            print("Failed to find api token in db")
                            return json({"status": "error", "error": "Failed to find api token in db"})
                    else:
                        return json({"failed to find token to authenticate with"})
                except Exception as e:
                    raise e
            query = await db_model.operator_query()
            operator = await db_objects.get(query, id=user_id)
            query = await db_model.apitokens_query()
            tokens = await db_objects.execute(query.where(db_model.APITokens.operator == operator))
            return json({"status": "success", "tokens": [t.to_json() for t in tokens]})
    

    I can't get this to work for any arbitrary endpoint though. With debug True I get: {"reasons":["Not enough segments."],"exception":"Unauthorized"}, with debug False I get {"reasons":["Auth required."],"exception":"Unauthorized"}.

    I've tried extending the responses class, but it never seems to be called:

    class MyResponses(Responses):
        @staticmethod
        async def extend_retrieve_user(request, user=None, payload=None):
            print("called extend retrieve user")
            return await retrieve_user(request, payload)
    
        @staticmethod
        async def extend_verify(request, user=None, payload=None):
            print("called extend verify")
            return {"Verify": True}
    

    and I've even tried extending authenticate:

    class MyAuthentication(Authentication):
        async def authenticate(self, request, *args, **kwargs):
            print("called authenticate")
            return {"user_id": 1}
    

    But i still don't see my print statements showing up, so these aren't getting called. I have all of the following added to my Initialize function as well:

    authenticate=app.routes.authentication.authenticate,
               retrieve_user=app.routes.authentication.retrieve_user,
               cookie_set=True,
               cookie_strict=False,
               cookie_access_token_name='access_token',
               cookie_refresh_token_name='refresh_token',
               cookie_httponly=True,
               scopes_enabled=True,
               add_scopes_to_payload=app.routes.authentication.add_scopes_to_payload,
               scopes_name='scope',
               secret='apfell_secret jwt for signing here',
               url_prefix='/',
               class_views=my_views,
               path_to_authenticate='/auth',
               path_to_retrieve_user='/me',
               path_to_verify='/verify',
               path_to_refresh='/refresh',
               refresh_token_enabled=True,
               expiration_delta=14400,  # initial token expiration time
               store_refresh_token=app.routes.authentication.store_refresh_token,
               retrieve_refresh_token=app.routes.authentication.retrieve_refresh_token,
               configuration_class=MyConfig,
               responses_class=MyResponses,
               authentication_class=MyAuthentication,
               debug=True)
    

    What am I missing to enable this kind of override check?

    question 
    opened by its-a-feature 10
  • Disable blueprint or configure blueprint name?

    Disable blueprint or configure blueprint name?

    For some reason I need two auth instance in a sanic app. But the name of blueprint was hardcode which can't change and can't disable too. So there will be a problem. Can we make add blueprint name configureable?

    documentation 
    opened by jiamo 10
  • The extend_verify method doesn't seem to be useful?

    The extend_verify method doesn't seem to be useful?

    As the title:

    I hope to return some additional user information when requesting the /auth/verify interface, but the parameters of user and payload are not returned from the interface processing class VerifyEndpoint.

    It seems that the extend_verify method is of no use?

    opened by YaoJusheng 0
  • When user secret enabled, user id configuration is not working

    When user secret enabled, user id configuration is not working

    sanic_jwt/authentication.py Line 262: Use configured user_id instead of hardcoded 'user_id'

    user_id = payload.get('user_id') => user_id = payload.get(self.config.user_id())

    Even with a fix for this on the source code, authentication fails when user secret enabled.

    In my case, User object has a ID field named different than user_id.

    I wanted to create a PR to contribute, however, contribution documentation returns 404.

    opened by yusufertekin 0
  • Async extra verifications

    Async extra verifications

    Goal

    I had to make jwt token invalidation, and the best way i have found - is to make it inside extra verifications, but they were not async it is also discussed here #219

    New Features/Changes

    Possibility to use sync/async functions for extra_verifications

    opened by jekel 11
Releases(v1.8.0)
  • v1.8.0(Jun 28, 2022)

    What's Changed

    • Upgrade PyJWT by @ahopkins in https://github.com/ahopkins/sanic-jwt/pull/227

    Full Changelog: https://github.com/ahopkins/sanic-jwt/compare/v1.7.0...v1.8.0

    Source code(tar.gz)
    Source code(zip)
  • v1.7.0(Aug 12, 2021)

  • v1.6.0(Jan 5, 2021)

  • v1.3.2(May 16, 2019)

  • v1.3.1(Apr 25, 2019)

  • v1.3.0(Apr 24, 2019)

    Added

    • #40. Page redirection for static page protection
    • Support to be able to individually protect class-based view methods without the decorators property
    Source code(tar.gz)
    Source code(zip)
  • v1.2.2(Mar 14, 2019)

    Changed

    • #148. Exception message on refresh token intialization

    Fixed

    • #147. protected decorator properly applied to built in views when initialized on a blueprint
    Source code(tar.gz)
    Source code(zip)
  • 1.2.1(Dec 4, 2018)

  • v1.2.0(Nov 8, 2018)

  • v1.1.4(Aug 6, 2018)

  • v1.1.3(Aug 6, 2018)

    Changed

    • Exception handling to consistently have a exception and reasons key
    • reasons in exception handling to be consistently formatted
    • 400 responses for debug turned off, and 401 when turned on

    Fixed

    • #110. Preflight methods now properly handled
    • #114. Proper use of utils.call to allow for sync and async retrieve_user functions
    • #116. Proper error reporting on malformed tokens
    • #118. Proper error reporting on expired token for /auth/me and /auth/refresh by applying @protected decorators
    Source code(tar.gz)
    Source code(zip)
  • v1.1.2(Jun 18, 2018)

  • v1.1.1(Jun 14, 2018)

  • v1.1.0(Jun 2, 2018)

    Added

    • New handler method: override_scope_validator
    • New handler method: destructure_scopes
    • New decorator method: inject_user
    • Decorator methods copied to Initialize class for convenience
    • New convenience method for extracting user_id from request
    • Feature for decoupling authentication mode for microservices
    • Ability to have custom generated refresh tokens
    • Subclasses are tested for consistency on Initialize

    Changed

    • Authentication.is_authenticated to Authentication._check_authentication
    • Authentication.verify to Authentication._verify
    • Authentication.get_access_token to Authentication.generate_access_token
    • Authentication.get_refresh_token to Authentication.generate_refresh_token
    • Authentication.retrieve_scopes to Authentication.extract_scopes
    • Method for getting and setting configurations made dynamic

    Fixed

    • Verification that a custom payload extender supplies all of the enabled claims
    • abort bug when using Sanic’s convenience method for exceptions
    Source code(tar.gz)
    Source code(zip)
  • v1.0.2(Mar 4, 2018)

  • v1.0.1(Feb 27, 2018)

    Added

    • OPTIONS handler method for BaseEndpoint

    Fixed

    • Some tests for claims that were not using UTC timestamps
    • Consistency of docs with class_views
    Source code(tar.gz)
    Source code(zip)
  • v1.0.0(Feb 25, 2018)

    Added

    • Initialize class
    • New methods for adding configuration settings
    • Customizable components
    • Customizable responses
    • Ability to fallback to header based authentication if cookie based fails
    • Initialize on a blueprint and isolate configuration

    Fixed

    • @protected implementation on class based views
    • Usage of signing algorithms with public and private keys

    Deprecated

    • SANIC_JWT_PAYLOAD_HANDLER
    • SANIC_JWT_HANDLER_PAYLOAD_EXTEND
    • SANIC_JWT_HANDLER_PAYLOAD_SCOPES
    Source code(tar.gz)
    Source code(zip)
Owner
Adam Hopkins
the brew·mas·ter (noun) /bro͞o ˈmastər/
Adam Hopkins
Get inside your stronghold and make all your Django views default login_required

Stronghold Get inside your stronghold and make all your Django views default login_required Stronghold is a very small and easy to use django app that

Mike Grouchy 384 Nov 23, 2022
Creation & manipulation of PyPI tokens

PyPIToken: Manipulate PyPI API tokens PyPIToken is an open-source Python 3.6+ library for generating and manipulating PyPI tokens. PyPI tokens are ver

Joachim Jablon 8 Nov 01, 2022
Some scripts to utilise device code authorization for phishing.

OAuth Device Code Authorization Phishing Some scripts to utilise device code authorization for phishing. High level overview as per the instructions a

Daniel Underhay 6 Oct 03, 2022
Simplifying third-party authentication for web applications.

Velruse is a set of authentication routines that provide a unified way to have a website user authenticate to a variety of different identity provider

Ben Bangert 253 Nov 14, 2022
Object Moderation Layer

django-oml Welcome to the documentation for django-oml! OML means Object Moderation Layer, the idea is to have a mixin model that allows you to modera

Angel Velásquez 12 Aug 22, 2019
Implementation of Supervised Contrastive Learning with AMP, EMA, SWA, and many other tricks

SupCon-Framework The repo is an implementation of Supervised Contrastive Learning. It's based on another implementation, but with several differencies

Ivan Panshin 132 Dec 14, 2022
User Authentication in Flask using Flask-Login

User-Authentication-in-Flask Set up & Installation. 1 .Clone/Fork the git repo and create an environment Windows git clone https://github.com/Dev-Elie

ONDIEK ELIJAH OCHIENG 31 Dec 11, 2022
Authware API wrapper for Python 3.5+

AuthwarePy Asynchronous wrapper for Authware in Python 3.5+ View our documentation 📲 Installation Run this to install the library via pip: pip instal

Authware 3 Feb 09, 2022
Quick and simple security for Flask applications

Note This project is non maintained anymore. Consider the Flask-Security-Too project as an alternative. Flask-Security It quickly adds security featur

Matt Wright 1.6k Dec 19, 2022
Easy and secure implementation of Azure AD for your FastAPI APIs 🔒 Single- and multi-tenant support.

Easy and secure implementation of Azure AD for your FastAPI APIs 🔒 Single- and multi-tenant support.

Intility 220 Jan 05, 2023
This script will pull and analyze syscalls in given application(s) allowing for easier security research purposes

SyscallExtractorAnalyzer This script will pull and analyze syscalls in given application(s) allowing for easier security research purposes Goals Teach

Truvis Thornton 18 Jul 09, 2022
Spotify User Token Generator Template

Spotify User Token Generator Template Quick Start $ pip3 install -r requirements

Arda Soyer 1 Feb 01, 2022
RSA Cryptography Authentication Proof-of-Concept

RSA Cryptography Authentication Proof-of-Concept This project was a request by Structured Programming lectures in Computer Science college. It runs wi

Dennys Marcos 1 Jan 22, 2022
Brute force a JWT token. Script uses multithreading.

JWT BF Brute force a JWT token. Script uses multithreading. Tested on Kali Linux v2021.4 (64-bit). Made for educational purposes. I hope it will help!

Ivan Šincek 5 Dec 02, 2022
Minimal authorization through OO design and pure Ruby classes

Pundit Pundit provides a set of helpers which guide you in leveraging regular Ruby classes and object oriented design patterns to build a simple, robu

Varvet 7.8k Jan 02, 2023
Per object permissions for Django

django-guardian django-guardian is an implementation of per object permissions [1] on top of Django's authorization backend Documentation Online docum

3.3k Jan 01, 2023
Authentication Module for django rest auth

django-rest-knox Authentication Module for django rest auth Knox provides easy to use authentication for Django REST Framework The aim is to allow for

James McMahon 878 Jan 04, 2023
Library - Recent and favorite documents

Thingy Thingy is used to quickly access recent and favorite documents. It's an XApp so it can work in any distribution and many desktop environments (

Linux Mint 23 Sep 11, 2022
Django Admin Two-Factor Authentication, allows you to login django admin with google authenticator.

Django Admin Two-Factor Authentication Django Admin Two-Factor Authentication, allows you to login django admin with google authenticator. Why Django

Iman Karimi 9 Dec 07, 2022
A Python library to create and validate authentication tokens

handshake A Python library to create and validate authentication tokens. handshake is used to generate and validate arbitrary authentication tokens th

0 Apr 26, 2022