GithubHelp home page GithubHelp logo

pyepye / django-magiclink Goto Github PK

View Code? Open in Web Editor NEW
90.0 90.0 11.0 855 KB

Passwordless authentication for Django with magic links.

License: MIT License

Python 88.63% HTML 11.37%
authentication django magiclink passwordless

django-magiclink's People

Contributors

dependabot[bot] avatar pyepye avatar sunnyr avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

django-magiclink's Issues

Security

Please, check django.contrib.auth views and decorators.

IMHO we should include

@method_decorator(sensitive_post_parameters())
    @method_decorator(csrf_protect)
    @method_decorator(never_cache)

decorators and do some additional checks for next url using url_has_allowed_host_and_scheme.

Move LoginVerify checks to UserPassesTestMixin's methods

Could you please consider adopting UserPassesTestMixin in LoginVerify view and moving verification logic to test_func?

I like how light and customizable django-magiclink is.
But I've stuck trying to add some logic depending on the results of verification, before redirecting to LOGIN_REDIRECT_URL

Currently I think I plan to work it around by comparing response redirect URL with LOGIN_REDIRECT_URL — if same, it will mean that user is verified and logged in:

@method_decorator(never_cache, name='dispatch')
class CustomLoginVerify(LoginVerify):
    def get(self, request, *args, **kwargs):
        response = super().get(request, *args, **kwargs)
        if not request.user.is_anonymous:
            some_additional_activity_or_checks()
            return redirect_to_say_complete_profile()
        return response

But with UserPassesTestMixin response of get will mean that user is verified and logged in.

--
Here are quick links to docs:

--
Maybe I could make a PR, but am not sure if this approach is correct and update is needed.
Do I even not see how to solve it other way?
What do you think?

E-Mail enumeration through the login form

I've noticed, while building an app using django-magiclink, that the behavior of the Login view differs between a valid and invalid email addresses.
Depending on the context of the application this could be bad, as it enables third parties to enumerate valid account addresses.

Testing possible remediations I've subclassed the Login view in my app and do something like this currently:

class CustomLogin(Login):
    # ...
    def post(self, request, *args, **kwargs):
        logout(request)
        context = self.get_context_data(**kwargs)
        context['require_signup'] = settings.REQUIRE_SIGNUP
        form = LoginForm(request.POST)
        if not form.is_valid():
            if form.errors.get("email", False) and settings.NO_EMAIL_ENUMERATION:  # This could be a good setting to disable by default
                if len(form.errors) == 1:
                    sent_url = get_url_path(settings.LOGIN_SENT_REDIRECT)
                    return HttpResponseRedirect(sent_url)
                form.errors.pop("email")
            context['login_form'] = form
            return self.render_to_response(context)
        # ...

I'd be happy to contribute some improvements in that direction if this fits with whats best for the project, just let me know.

No module named 'packaging'

Just installed and run runserver

Get:

$ python manage.py runserver
Watching for file changes with StatReloader
Exception in thread django-main-thread:
Traceback (most recent call last):
  File "/Users/USERNAME/.pyenv/versions/3.8.6/lib/python3.8/threading.py", line 932, in _bootstrap_inner
    self.run()
...
File "/Users/USERNAME/.local/share/virtualenvs/project-G1DqfJzJ/lib/python3.8/site-packages/magiclink/__init__.py", line 2, in <module>
    from packaging import version
ModuleNotFoundError: No module named 'packaging'

What am I doing wrong?
Django: 3.2.4

hashed email instead of plaintext

It would be very useful if there was a setting such that the email value is hashed instead of plaintext wherever it is used. This would alleviate having to deal with personally identifiable information in things like request logs and database tables. Ideally, the email would be hashed along with a configurable secret (just a random 32 char or more string) to prevent easily reversing the hash with rainbow tables.

Use the magiclink token in REST calls

Is it possible to configure magiclink authenticate backend to allow users call my REST API? Can I send the token that was generated by magiclink in Autorization header to authenticate users?

Refactoring suggestions

Thanks for creating this app, it looks very useful!

I suggest to do some changes to get modular design (and move it a little bit closer to DDD).

  1. Move all MagicLink model methods (business logic) and content of helpers.py to new services.py module.
  2. [In future] create all new business logic functions in services.py, do not use views or models or helpers for that.
  3. Do not use request and response class instances in services.py, this is Django view layer, not business logic layer. views.py may import any services.py functions and use data from request.

This will allow to easy replace Django to Starlette, for example in future (and reuse business logic services.py).

don't specify loginverify failed redirect external url

you have already define login failed overide template name variable,
MAGICLINK_LOGIN_FAILED_TEMPLATE_NAME = 'magiclink/login_failed.html'
but i want direct redirect frontend url so please find any solution and add MAGICLINK_LOGIN_FAILED_URL

i have find one solution
add settings.py in overide variable MAGICLINK_LOGIN_FAILED_TEMPLATE_NAME = False
MAGICLINK_LOGIN_FAILED_URL = "your login failed url"
@method_decorator(never_cache, name='dispatch')
class LoginVerify(TemplateView):
template_name = settings.LOGIN_FAILED_TEMPLATE_NAME

def get(self, request, *args, **kwargs):
    token = request.GET.get('token')
    email = request.GET.get('email')
    user = authenticate(request, token=token, email=email)
    if not user:
        if settings.LOGIN_FAILED_TEMPLATE_NAME:
            context = self.get_context_data(**kwargs)
            # The below settings are left in for backward compatibility
            context['ONE_TOKEN_PER_USER'] = settings.ONE_TOKEN_PER_USER
            context['REQUIRE_SAME_BROWSER'] = settings.REQUIRE_SAME_BROWSER
            context['REQUIRE_SAME_IP'] = settings.REQUIRE_SAME_IP
            context['ALLOW_SUPERUSER_LOGIN'] = settings.ALLOW_SUPERUSER_LOGIN  # NOQA: E501
            context['ALLOW_STAFF_LOGIN'] = settings.ALLOW_STAFF_LOGIN

            try:
                magiclink = MagicLink.objects.get(token=token)
            except MagicLink.DoesNotExist:
                error = 'A magic link with that token could not be found'
                context['login_error'] = error
                return self.render_to_response(context)

            try:
                magiclink.validate(request, email)
            except MagicLinkError as error:
                context['login_error'] = str(error)

            return self.render_to_response(context)
        else:
            **return HttpResponseRedirect(settings.MAGICLINK_LOGIN_FAILED_URL)**

    login(request, user)
    log.info(f'Login successful for {email}')

    magiclink = MagicLink.objects.get(token=token)
    response = HttpResponseRedirect(magiclink.redirect_url)
    if settings.REQUIRE_SAME_BROWSER:
        cookie_name = f'magiclink{magiclink.pk}'
        response.delete_cookie(cookie_name, magiclink.cookie_value)
    return response

add line return HttpResponseRedirect(settings.MAGICLINK_LOGIN_FAILED_URL) and remove raise Http404()

Wrong email entered msg and first login redirect

Description:

This is my old login view:

def login_view(request):
    form = LoginForm(request.POST or None)

    msg = None

    if request.method == "POST":

        if form.is_valid():
            username = form.cleaned_data.get("username")
            password = form.cleaned_data.get("password")
            user = authenticate(username=username, password=password)
            if user is not None and user.last_login == None:
                login(request, user)
                return redirect("/onboarding/")
            elif user is not None:
                login(request, user)
                return redirect("/")
            else:
                msg = '¡Wrong credentials!'
        else:
            msg = '¡Error in the form validation process!'

    return render(request, "accounts/login.html", {"form": form, "msg": msg})

In this login I could use the msg to warn if the user had made a mistake with the credentials. It could also redirect users to a specific page if it was the first time they logged in. (/onboarding/)

Now I can't find the solution for these two doubts.

My actual settings:

# Magic Link Configuration

LOGIN_URL = 'magiclink:login'
MAGICLINK_EMAIL_TEMPLATE_NAME_HTML = 'accounts/login_email.html'
MAGICLINK_EMAIL_TEMPLATE_NAME_TEXT = 'accounts/login_email_text.txt'
MAGICLINK_LOGIN_TEMPLATE_NAME = 'accounts/login.html'
MAGICLINK_LOGIN_SENT_TEMPLATE_NAME = 'accounts/login_sent.html'
MAGICLINK_LOGIN_FAILED_TEMPLATE_NAME = 'accounts/login_failed.html'
MAGICLINK_REQUIRE_SIGNUP = True
MAGICLINK_REQUIRE_SAME_BROWSER = False
LOGIN_REDIRECT_URL = "home"  # Route defined in home/urls.py
LOGOUT_REDIRECT_URL = "home"  # Route defined in home/urls.py
TEMPLATE_DIR = os.path.join(CORE_DIR, "apps/templates")  # ROOT dir for templates

docs email send configuration

First of all thanks a lot for this package 👍
Sorry if my question sounds naive but how so you configure the email sending (e.g. over SMTP) ? Perhaps this can be added in the README.

I believe that nowadays the best way to send emails is through a provide (e.g. sendgrid). I am using Django since a few years but I never had to use it to send emails.

Cookie not deleted with LoginVerify

Description

Debugging why cookie not deleted with LoginVerify if REQUIRE_SAME_BROWSER,
found that it is set properly:

set-cookie: 
magiclink498=e214549f-0d66-491a-a494-20964daa649e; 
Path=/

But for deletion, browser gets this:

set-cookie: 
magiclink498=""; 
expires=Thu, 01 Jan 1970 00:00:00 GMT; 
Max-Age=0; 
Path=e214549f-0d66-491a-a494-20964daa649e

Setting breakpoint after response.delete_cookie(cookie_name, magiclink.cookie_value) shows:

(Pdb) response.cookies.values()
dict_values([<Morsel: magiclink498=""; expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Path=e214549f-0d66-491a-a494-20964daa649e>])

While after response.set_cookie(cookie_name, magiclink.cookie_value):

(Pdb) response.cookies.values()
dict_values([<Morsel: magiclink498=e214549f-0d66-491a-a494-20964daa649e; Path=/>])

Question

Do you have any ideas why can it happen?
Can you recreate it?

System

MagicLink: ==1.0.4
Django: ==3.2.5
Python: 3.8

Anonimize/not store IP addresses

We can't afford keeping IP address, at least not anonymized ones
And I cant' find a way to customize this, because you save it in create_magiclink() helper

Could you please add some solution?
Either not store them at all, if MAGICLINK_REQUIRE_SAME_IP is set to False
Or at least anonymize it by default or by option (by removing last part for IP4? not sure about IP6)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.