pyepye / django-magiclink Goto Github PK
View Code? Open in Web Editor NEWPasswordless authentication for Django with magic links.
License: MIT License
Passwordless authentication for Django with magic links.
License: MIT License
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
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()
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:
UserPassesTestMixin
:
you can set any of the parameters of AccessMixin to customize the handling of unauthorized users
AccessMixin
--
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?
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.
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)
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.
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
.
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.
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).
MagicLink
model methods (business logic) and content of helpers.py
to new services.py
module.services.py
, do not use views
or models
or helpers
for that.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).
I was testing this locally using http://localhost:8000/auth/login
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?
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
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=/>])
Do you have any ideas why can it happen?
Can you recreate it?
MagicLink: ==1.0.4
Django: ==3.2.5
Python: 3.8
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.