User-related REST API based on the awesome Django REST Framework

Overview

Django REST Registration

CircleCI Build Status Codecov Coverage PyPi Version Documentation Status

User registration REST API, based on Django REST Framework.

Documentation

Full documentation for the project is available at https://django-rest-registration.readthedocs.io/.

Requirements

  • Django (1.10+, 2.0+, 3.0+) and Django-REST-Framework (3.3+)
  • Python 3.4 or higher (no Python 2 support!)

Features

  • Supported views:
    • registration (sign-up) with verification
    • login/logout (sign-in), session- or token-based
    • user profile (retrieving / updating)
    • reset password
    • change password
    • register (change) e-mail
  • Views are compatible with django-rest-swagger
  • Views can be authenticated via session or auth token
  • Modeless (uses the user defined by settings.AUTH_USER_MODEL and also uses cryptographic signing instead of profile models)
  • Uses password validation
  • Heavily tested (Above 98% code coverage)

Current limitations

Installation & Configuration

You can install Django REST Registration latest version via pip:

pip install django-rest-registration

Then, you should add it to the INSTALLED_APPS so the app templates for notification emails can be accessed:

INSTALLED_APPS=(
    ...

    'rest_registration',
)

After that, you can use the urls in your urlconfig, for instance (using new Django 2.x syntax):

api_urlpatterns = [
    ...

    path('accounts/', include('rest_registration.api.urls')),
]


urlpatterns = [
    ...

    path('api/v1/', include(api_urlpatterns)),
]

In Django 1.x you can use old url instead of path.

You can configure Django REST Registraton using the REST_REGISTRATION setting in your Django settings (similarly to Django REST Framework).

Below is sample, minimal config you can provide in your django settings which will satisfy the system checks:

REST_REGISTRATION = {
    'REGISTER_VERIFICATION_ENABLED': False,
    'RESET_PASSWORD_VERIFICATION_ENABLED': False,
    'REGISTER_EMAIL_VERIFICATION_ENABLED': False,
}

However, the preferred base configuration would be:

REST_REGISTRATION = {
    'REGISTER_VERIFICATION_URL': 'https://frontend-host/verify-user/',
    'RESET_PASSWORD_VERIFICATION_URL': 'https://frontend-host/reset-password/',
    'REGISTER_EMAIL_VERIFICATION_URL': 'https://frontend-host/verify-email/',

    'VERIFICATION_FROM_EMAIL': '[email protected]',
}

The frontend urls are not provided by the library but should be provided by the user of the library, because Django REST Registration is frontend-agnostic. The frontend urls will receive parameters as GET query and should pass them to corresponding REST API views via HTTP POST request.

In case when any verification is enabled (which is the default!), your Django application needs to be properly configured so it can send e-mails.

You can read more about basic configuration here.

You can read more about detailed configuration here.

Configuration options

You can find all REST_REGISTRATION configuration options here.

Contributing

If you want to contribute, please refer to separate document CONTRIBUTING.md.

Comments
  • Token authentication with expiring token.

    Token authentication with expiring token.

    It would be a nice feature if rest-registration provides inbuilt token authentication with expiring token. JWT provides expiring tokens but handling logout with blacklisting token is a headache that I feel but in the case of expiring token, it doesn't need any blacklisting on logout. It will increase the strength of authentication with the token mechanism.

    type:feature-request 
    opened by itzmanish 11
  • Swagger does not pick up django-rest-registration endpoints.

    Swagger does not pick up django-rest-registration endpoints.

    So I did setup everything like we you have here in example, and it seems like django-rest-swagger does not pick endpoints.

    Here are my urls:

    from django.conf import settings
    from django.urls import path, re_path, include, reverse_lazy
    from django.conf.urls.static import static
    from django.contrib import admin
    from django.views.generic.base import RedirectView
    from rest_framework.routers import DefaultRouter
    from rest_framework.authtoken import views
    from .users.views import UserViewSet, UserCreateViewSet
    
    from rest_framework_swagger.views import get_swagger_view
    
    schema_view = get_swagger_view(title='Luken API')
    
    router = DefaultRouter()
    router.register(r'users', UserViewSet)
    router.register(r'users', UserCreateViewSet)
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('accounts/', include('rest_registration.api.urls')),
        path('api/v1/', include(router.urls)),
        path('api/swagger/', schema_view),
    
        re_path(r'^$', RedirectView.as_view(url=reverse_lazy('api-root'), permanent=False)),
    
    ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
    

    Here is the result.

    screen

    If you notice accounts are not being displayed.

    opened by polinom 10
  • Switch to class-based views

    Switch to class-based views

    Class-based views greatly simplify adding extra logic into views. I am therefore proposing to refactor the views into class-based views.

    Here are some examples why this is useful, at least as far as the user registration view is concerned:

    1.) For user registration, I would like to record the client IP address. This is best done in the register view, by calling serializer_class() with an extra context argument, like serializer_class(data=request.data, context={'request': request}). This way, the serializer can access self.context['request'].META.get('REMOTE_ADDR') and populate a field on the model instance it creates.

    As far as I can see, this is currently not possible, except by replacing the view by a copy of it and modifying it. This violates the DRY principle and is also detached from future upstream changes in this view.

    If the registration view was a class-based view inheriting from DRF's GenericAPIView (e.g. a CreateAPIView), the following few lines of code would be sufficient to solve this (assuming the upstream registration view is called DefaultRegisterUserView):

    class RegisterUserView(DefaultRegisterUserView):
        def get_serializer_context(self):
            return {'request': self.request}
    
    class RegisterUserSerializer(DefaultRegisterUserSerializer):
        def create(self, validated_data):
            data = validated_data.copy()
            data['registration_remote_ip'] = self.context['request'].META.get('REMOTE_ADDR')
            return super().create(data)
    

    ... plus the two lines in urls.py and settings.py where the view and the serializer are configured.

    2.) Also, one may consider changing the response of the registration view such that information like "user with this email exists" is not leaked. Instead, 202 Accepted could be an appropriate status code in all cases, with the view redirecting to the password reset view if the email address is already taken, and otherwise sending the regular registration verification email.

    (It would be the frontend's responsibility to react with a proper message, like "We have sent you an email with further instructions", which would be true in all cases.)

    This way, one could expose a unified register+reset endpoint which acts as a general "give me access to an account with this email address" endpoint. The frontend could of course present it in different variations for registration and for password reset purposes.

    I am not proposing that these endpoints be merged (that would be another interesting issue); I am rather making the point that if the registration endpoint was a class-based view, one could easily achieve the above with something like (rough sketch):

    class RegisterUserView(DefaultRegisterUserView):
        def post(request, *args, **kwargs):
            try:
                # Try to register user as usual
                return super().post(request, *args, *kwargs)
            except ValidationError as e:
                # Perform password reset if account exists
                email_error = e.message_dict.get('email')
                if email_error and email_error.endswith(' already exists.'):
                    return PasswortResetView.as_view()(request)
    
                # For other errors, continue as usual
                raise e
    
    type:feature-request release:next-major-version 
    opened by peterthomassen 9
  • *_VERIFICATION_EMAIL_TEMPLATES is invalid

    *_VERIFICATION_EMAIL_TEMPLATES is invalid

    I try to use the package without any TEMPLATES option. I've added in settings minimal REST_REGISTRATION config, but get REGISTER_EMAIL_/REGISTER_VERIFICATION_/RESET_PASSWORD_VERIFICATION_EMAIL_TEMPLATES is invalid (3 SystemCheck errors). Also I've tried to set this options in {}, '', None, but unsuccessfully. Minimal config to launch server:

    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'APP_DIRS': True,
        },
    ]
    

    Do I something wrong understand with using the package or Django..?

    type:question 
    opened by ruslankrivoshein 8
  • Using username instead of user_id in signer

    Using username instead of user_id in signer

    I am trying to use username instead of user_id. But i am getting this error File "/home/krishna/Projects/django/cms/account/views.py", line 79, in _calculate_salt if registration_settings.REGISTER_VERIFICATION_ONE_TIME_USE: File "/home/krishna/anaconda3/envs/django/lib/python3.7/site-packages/rest_registration/utils/nested_settings.py", line 38, in __getattr__ self=self, attr=attr)) AttributeError: Invalid REST_REGISTRATION setting: 'REGISTER_VERIFICATION_ONE_TIME_USE' However this is not related to username or userid data but yet i am getting this error. I tried to trackdown this error in nested_settings.py and settings_field.py but there is no problem.

    Also i am using whole view.py from rest-registration/api/views/register.py

    type:feature-request priority:high 
    opened by itzmanish 8
  • Verify email does not seems to behave like expected

    Verify email does not seems to behave like expected

    So I hit resgiter email and receive a following email:

    Please verify your account by clicking on this link:
    
    http://localhost:8000/verify-account/?user_id=cb185c07-143f-4d99-ac64-18a280e221ff&timestamp=1519787713&signature=4uB1gS9O32bPa7skbCvEh88XCec
    

    I want to activate this account, so I take user_id, timestamp signature and email address end send this to our POST /api/v1/accounts/verify-email/

    I get response:

    {
      "detail": "Invalid signature"
    }
    

    And btw,

    http://localhost:8000/verify-account/?user_id=cb185c07-143f-4d99-ac64-18a280e221ff&timestamp=1519787713&signature=4uB1gS9O32bPa7skbCvEh88XCec

    urls that i get in email is not working

    opened by polinom 8
  • Invalid Signature When Verifying Email Address and More Using Postman

    Invalid Signature When Verifying Email Address and More Using Postman

    Checklist

    • [X] I searched existing issues before opening this one
    • [X] I reproduced the bug with the newest version

    Describe the bug

    The views that require posting in a JSON body, such as verify-email, will fail with an Invalid Signature error when using Postman. This has been reported in some form in #11 and #42, but neither had a solution.

    Expected behavior

    Posting into these endpoints succeeds and returns a 2XX error code as expected, like it does with cURL.

    Actual behavior

    The problem appears to occur due to some temporary headers that Postman is setting. This problem has been reported with a number of APIs in other forms in https://community.getpostman.com/t/disable-temporary-headers/5271 and other tickets. After some testing, I believe it comes down to the "Accept-Encoding: gzip, deflate" header. When I include that one with cURL, I receive an error relating to binary data coming back or an invalid signature error. Excluding that single temporary header that Postman sets (which is not supported by the tool itself) allows the request to succeed.

    Steps to reproduce

    Steps to reproduce the behavior:

    1. Create a new request in Postman to POST to the verify-email endpoint with correct data
    2. Send the request and receive an error

    Diagnostic info

    (Please provide below contents of your Django settings.py file (after removing all sensitive information like secrets), or at least provide specific settings mentioned below)

    REST_REGISTRATION = {
        'REGISTER_VERIFICATION_URL': 'https://mysite.example/verify-user/',
        'RESET_PASSWORD_VERIFICATION_URL': 'https://mysite.example/reset-password/',
        'REGISTER_EMAIL_VERIFICATION_URL': 'https://mysite.example/verify-email/',
    
        'VERIFICATION_FROM_EMAIL': '[email protected]',
    
        # Excluding this and using the normal id field does not change the behavior
        'USER_VERIFICATION_ID_FIELD': 'uuid',
    }
    
    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': (
            'knox.auth.TokenAuthentication',
        ),
        'DEFAULT_PERMISSION_CLASSES': [
            'rest_framework.permissions.IsAuthenticated'
        ],
        'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
        'PAGE_SIZE': 25,
        'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.NamespaceVersioning',
    }
    
    INSTALLED_APPS = (
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'api.apps.ApiConfig',
        'knox',
        'oauth2_provider',
        'rest_framework',
        'rest_registration',
        'rolepermissions',
        'simple_history',
    )
    

    Additional context

    I've also requested that Postman provide a method for excluding temporary headers because of this problem (as have many other people), but I'm not sure what their release timeframe is. Hopefully this is simpler to reproduce and fix on this end.

    type:bug 
    opened by beittenc 7
  • fix profile view with api renderer

    fix profile view with api renderer

    This is fix for bug, that occurs when using rest_framework.renderers.BrowsableAPIRenderer with profile view. It throws following error:

    Traceback (most recent call last):
      File "/home/petr/soubory/programovani/climboard/env/lib/python3.6/site-packages/django/core/handlers/exception.py", line 35, in inner
        response = get_response(request)
      File "/home/petr/soubory/programovani/climboard/env/lib/python3.6/site-packages/django/core/handlers/base.py", line 158, in _get_response
        response = self.process_exception_by_middleware(e, request)
      File "/home/petr/soubory/programovani/climboard/env/lib/python3.6/site-packages/django/core/handlers/base.py", line 156, in _get_response
        response = response.render()
      File "/home/petr/soubory/programovani/climboard/env/lib/python3.6/site-packages/django/template/response.py", line 106, in render
        self.content = self.rendered_content
      File "/home/petr/soubory/programovani/climboard/env/lib/python3.6/site-packages/rest_framework/response.py", line 73, in rendered_content
        ret = renderer.render(self.data, accepted_media_type, context)
      File "/home/petr/soubory/programovani/climboard/env/lib/python3.6/site-packages/rest_framework/renderers.py", line 719, in render
        context = self.get_context(data, accepted_media_type, renderer_context)
      File "/home/petr/soubory/programovani/climboard/env/lib/python3.6/site-packages/rest_framework/renderers.py", line 652, in get_context
        raw_data_put_form = self.get_raw_data_form(data, view, 'PUT', request)
      File "/home/petr/soubory/programovani/climboard/env/lib/python3.6/site-packages/rest_framework/renderers.py", line 555, in get_raw_data_form
        serializer = view.get_serializer(instance=instance)
    TypeError: _get_serializer() got an unexpected keyword argument 'instance'
    
    opened by PetrDlouhy 7
  • Why is there a check for a specific backend?

    Why is there a check for a specific backend?

    At some point a check for a specific authentication backend was introduced. I get this error message:

    django.core.management.base.SystemCheckError: SystemCheckError: System check identified some issues:
    
    ERRORS:
    ?: (rest_registration.E014) invalid authentication backends configuration: LOGIN_DEFAULT_SESSION_AUTHENTICATION_BACKEND is not in AUTHENTICATION_BACKENDS
    

    I implemented my own authentication backend but use rest-registration for the signup process. This error prevents me from upgrading django to a more recent version and is also very restraining. People should be able to use the rest-registration package standalone without it being opinionated about which authentication backend to use.

    This could be changed to a warning to indicate that if you have a problem, you should look into the authentication backends setting.

    type:bug state:needs-answer 
    opened by saevarom 6
  • Return user profile after successful login

    Return user profile after successful login

    With my custom login view I get the user data after successful login via the login endpoint. Unfortunately with django-rest-registration all I get is:

    { "detail": "Login successful" }

    Which means that I have to make another request to the profile endpoint to completely login a user in my frontend. Is this how it should be done? It seems unnecessary to do 2 request if it could be done in one. Curious to hear how others handle this.

    type:feature-request 
    opened by kris7ian 6
  • Default register output serializer should include a token when appropriate

    Default register output serializer should include a token when appropriate

    If the client does not get an AuthToken after registration, the user must provide credentials immediately after registering.

    I was able to add a token to the default serializer with the following custom serializer, but the token should really be created in the view instead.

    from rest_registration.api.serializers import DefaultUserProfileSerializer
    from rest_framework import serializers
    from rest_framework.authtoken.models import Token
    
    
    class MyRegistrationSerializer(DefaultUserProfileSerializer):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.Meta.fields += ('token',)
    
        token = serializers.SerializerMethodField()
    
        def get_token(self, user):
            token = Token.objects.create(user=user)
            return token.key
    

    and in settings.py:

    REST_REGISTRATION = {
    # ...
        'REGISTER_OUTPUT_SERIALIZER_CLASS': '...MyRegistrationSerializer',
    }
    
    closed-as:wontfix 
    opened by ckeeney 6
  • Allow superuser to not be verified

    Allow superuser to not be verified

    Description

    This PR aims to fix #144: when a custom flag field is used, and one tries to change the email of a superuser created by createsuperuser whose flag field is not enabled.

    The strategy employed is to disable user verification in rest_registration.utils.users.get_user_by_lookup_dict if the requested user is a superuser (i.e. is_superuser is True). In other words, a superuser can be unverified.

    Two tests check if, when setting a custom flag field, the email address can be changed for an unverified superuser, and cannot be changed for an unverified normal user.

    Improvements

    Disabling user verification could be made optional. Either with an extra argument to the function (required_verified_superuser defaults to False), or with a settings (SUPERUSER_MUST_BE_VALIDATED defaults to False).

    Why the change is needed?

    See #144.

    Related issues, pull requests, links

    #144

    opened by Neraste 0
  • Cannot change superuser email when using custom flag field and createsuperuser

    Cannot change superuser email when using custom flag field and createsuperuser

    Describe the bug

    I changed the default flag field with USER_VERIFICATION_FLAG_FIELD and created a superuser with the Django command createsuperuser. Changing this superuser's email results in an error when verifying the email.

    Expected behavior

    Superuser email is changed.

    Actual behavior

    Error 400 with the error message: "User not found."

    Steps to reproduce

    1. Create a Boolean field in user model and set USER_VERIFICATION_FLAG_FIELD to this field;
    2. Create a superuser with Django admin command createsuperuser;
    3. Register a new mail for this superuser;
    4. Validate the new email.

    Possible explanation

    This is due to the fact createsuperuser will not enable the custom flag field (as it sets username, email and password by default), hence this superuser is not considered as valid and cannot be retrieved.

    In rest_registration.api.views.register_email.process_verify_email_data, get_user_by_verification_id is called with argument require_verified True by default.

    Associated PR

    See #145.

    type:bug 
    opened by Neraste 3
  • Differentiate bad signature from already used signatures

    Differentiate bad signature from already used signatures

    Checklist

    • [x] I read Contribution Guidelines
    • [x] I searched the documentation to ensure that the requested feature is not already implemented and described
    • [x] I searched existing issues before opening this one

    Is your feature request related to a problem? Please describe.

    From what I read in the source code there are no specific exception when a signature had already been used. It just raise a BadSignature: https://github.com/apragacz/django-rest-registration/blob/master/rest_registration/utils/verification.py#L9 This is because the salt used is not the same after the account had been registered and from the comments this seems by design: https://github.com/apragacz/django-rest-registration/blob/master/rest_registration/api/views/register.py#L46

    The problem is it can lead to a bad user experience. For example, in our setup we have REGISTER_VERIFICATION_AUTO_LOGIN and REGISTER_VERIFICATION_ONE_TIME_USE enabled. If a user follows the verification link on a device, he got registered and logged in. Later on he follows the link on an other device and here we are only able to show a generic error message: "The link is invalid."

    Describe the solution you'd like

    • We would like a specific exception to be raised like SignatureAlreadyUsed OR
    • if there was a way to override the verify_registration endpoint we could first check if a user is verified and if not go on with the usual verification

    In both cases this will allow us to display a relevant error message to our users like "You are already verified, please log in"

    Describe alternatives you've considered

    We tried to enable REGISTER_VERIFICATION_AUTO_LOGIN and disable REGISTER_VERIFICATION_ONE_TIME_USE but this leads to this error:

    REGISTER_VERIFICATION_AUTO_LOGIN is enabled, but REGISTER_VERIFICATION_ONE_TIME_USE is not enabled. This can allow multiple logins using the verification url.

    This is indeed not ideal for security.

    type:feature-request state:research priority:low 
    opened by ppawlak 1
Releases(0.7.3)
REST implementation of Django authentication system.

djoser REST implementation of Django authentication system. djoser library provides a set of Django Rest Framework views to handle basic actions such

Sunscrapers 2.2k Jan 01, 2023
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
Simple Login - Login Extension for Flask - maintainer @cuducos

Login Extension for Flask The simplest way to add login to flask! Top Contributors Add yourself, send a PR! How it works First install it from PyPI. p

Flask Extensions 181 Jan 01, 2023
Ready to use and customizable Authentications and Authorisation management for FastAPI ⚡

AuthenticationX 💫 Ready-to-use and customizable Authentications and Oauth2 management for FastAPI ⚡ Source Code: https://github.com/yezz123/AuthX Doc

Yasser Tahiri 404 Dec 27, 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
Multi-user accounts for Django projects

django-organizations Summary Groups and multi-user account management Author Ben Lopatin (http://benlopatin.com) Status Separate individual user ident

Ben Lopatin 1.1k Jan 02, 2023
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
python-social-auth and oauth2 support for django-rest-framework

Django REST Framework Social OAuth2 This module provides OAuth2 social authentication support for applications in Django REST Framework. The aim of th

1k Dec 22, 2022
Simple two factor authemtication system, made by me.

Simple two factor authemtication system, made by me. Honestly, i don't even know How 2FAs work I just used my knowledge and did whatever i could. Send

Refined 5 Jan 04, 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
MikroTik Authentication POCs

Proofs of concept which successfully authenticate with MikroTik Winbox and MAC Telnet servers running on RouterOS version 6.45.1+

Margin Research 56 Dec 08, 2022
A simple Boilerplate to Setup Authentication using Django-allauth 🚀

A simple Boilerplate to Setup Authentication using Django-allauth, with a custom template for login and registration using django-crispy-forms.

Yasser Tahiri 13 May 13, 2022
Phishing Abusing Microsoft 365 OAuth Authorization Flow

Microsoft365_devicePhish Abusing Microsoft 365 OAuth Authorization Flow for Phishing Attack This is a simple proof-of-concept script that allows an at

bigb0ss 11 Dec 11, 2022
Toolkit for Pyramid, a Pylons Project, to add Authentication and Authorization using Velruse (OAuth) and/or a local database, CSRF, ReCaptcha, Sessions, Flash messages and I18N

Apex Authentication, Form Library, I18N/L10N, Flash Message Template (not associated with Pyramid, a Pylons project) Uses alchemy Authentication Authe

95 Nov 28, 2022
FastAPI extension that provides JWT Auth support (secure, easy to use, and lightweight)

FastAPI JWT Auth Documentation: https://indominusbyte.github.io/fastapi-jwt-auth Source Code: https://github.com/IndominusByte/fastapi-jwt-auth Featur

Nyoman Pradipta Dewantara 468 Jan 01, 2023
Ready to use and customizable Authentications and Authorisation management for FastAPI ⚡

AuthenticationX 💫 Ready-to-use and customizable Authentications and Oauth2 management for FastAPI ⚡

Yasser Tahiri 408 Jan 05, 2023
Simple yet powerful authorization / authentication client library for Python web applications.

Authomatic Authomatic is a framework agnostic library for Python web applications with a minimalistic but powerful interface which simplifies authenti

1k Dec 28, 2022
A flask extension for managing permissions and scopes

Flask-Pundit A simple flask extension to organize resource authorization and scoping. This extension is heavily inspired by the ruby Pundit library. I

Anurag Chaudhury 49 Dec 23, 2022
Graphical Password Authentication System.

Graphical Password Authentication System. This is used to increase the protection/security of a website. Our system is divided into further 4 layers of protection. Each layer is totally different and

Hassan Shahzad 12 Dec 16, 2022
An open source Flask extension that provides JWT support (with batteries included)!

Flask-JWT-Extended Features Flask-JWT-Extended not only adds support for using JSON Web Tokens (JWT) to Flask for protecting views, but also many help

Landon Gilbert-Bland 1.4k Jan 04, 2023