Django Expired Session Message API - django

I'm currently using SESSION_COOKIE_AGE = 60*60 to expire a Django session in 1 hour. I need to give a message to the user saying their session has expired on the login page.
Is there a way to test if the session has been expired? Or is there a message api for expired sessions in Django?
I poked around and didn't see anything for setting an expired session message.
Thanks!

To display the message that session is expired you can check if session exists in your logout view and change the success message accordingly
class Logout(View):
def get(self, request):
if request.session:
messages.success(request, 'Successfully Logged Out')
else:
messages.error(request, 'Session Expired Please Login Again')
logout(request)
return redirect(reverse('login'))

The warning typically provided to a user is an invitation to login :-).
What you could do is check SESSION_COOKIE_AGE (which provides the age of the cookie in seconds) and, if the user's session is about to expire, provide a warning to that effect.

I encouraged same problem and solved shown as below:
Django redirecting to LOGIN_URL after session expired.
So I pointed login url to logout view in settings.py for show message to user
and redirect to our login view.
settings.py:
LOGIN_URL = reverse_lazy('account:logout')
views.py:
class LogoutView(RedirectView):
url = reverse_lazy('account:login') # Our login view
def get(self, request, **kwargs):
# If session expired django clean request.session object.
# If user came to this view by clicking to logout button request.session not comes empty
if request.session.is_empty():
messages.error(request, "Your session has expired. Please login again to continue checking out.")
logout(request)
if request.GET.get('next'):
self.url = '{}?next={}'.format(self.url, request.GET.get('next'))
return super(LogoutView, self).get(request, **kwargs)

Related

Django testcase for logout API with LoginRequired mixin, but for anonymous user it is redirecting to login API with ?next= parameter

But what if I want to check without login. For example, I have one api logout which is having LoginRequired mixin, I want to test that anyone cannot hit logout if hes not logged in. So I created testcase as below.
#pytest.mark.django_db
def test_user_logout_logged_in_user(self, client):
url = reverse('logout')
response = client.get(url)
assert response.status_code == 302
assertRedirects(response, reverse('login'))
but I got this error,
AssertionError: '/auth/login-view/?next=%2Fauth%2Flogout-view%2F' != '/auth/login-view/'
Means I get ?next={something for after login api}.
How do I check that redirected api is login api.
My logout api is as below:
class LogoutView(LoginRequiredMixin, View):
"""
description: This is user logout view.
GET request will log out user and redirects to home page.
"""
def get(self, request):
return LogoutService.logout_user(request=request)
class LogoutService:
#staticmethod
def logout_user(request):
logout(request)
messages.success(request, ACCOUNT_LOGOUT_SUCCESS)
return redirect('home')
Django test for a page that redirects
I tried this answer, but it is for logged-in user. I want to test for un authenticated user.
To test that a view redirects with the "next" parameter correctly you need to add the next get parameter to the login URL when testing
#pytest.mark.django_db
def test_login_required_redirect(self, client):
url = reverse('someview')
response = client.get(url)
assertRedirects(response, f"{reverse('login')}?next={url}")
Don't use LoginRequiredMixin for your logout view, you set next to your logout view this way and that will cause the user to instantly logout after they login - handle the redirect manually in your view
class LogoutView(View):
def get(self, request):
if not request.user.is_authenticated:
return redirect('login')
return LogoutService.logout_user(request=request)

Programmatic login succeeds, but not recognized

I have a magnet link feature where my web app sends a login URL with a username & one-time token encrypted. When clicking the link on the email, the user is sent to an authentication where I programmatically log in and then redirect to the member page.
Authentication and login are successful, but when I redirect to the main page, Django sends the user back to the login page. However, when I click the logo, the main page shows.
authentication & login code
def magnet_link_login(request, *args, **kwargs):
if request.user and request.user.id != None:
return redirect('stream:home')
magnet_token = request.GET['key'] if 'key' in request.GET else ''
decrypted_token = decrypt_text(magnet_token)
split_texts = decrypted_token.split('&')
if len(split_texts)<2:
#invalid and redirect to an invalid magnet link page
pass
uname = split_texts[0].split('=')[1]
auth_token = split_texts[1].split('=')[1]
#fetch the user record
acc = Account.objects.get(slug=uname)
if not acc:
#error and redirect to an invalid account name page
pass
#validate and login
try:
logged_user = authenticate(username=acc.username,password=auth_token)
if logged_user is not None:
login(request, logged_user)
return redirect('stream:home')
else:
return redirect('invalid-link')
except Exception as e:
print(e)
return redirect('invalid-link')
My member page (stream:home) is a CBV.
class Home(LoginRequiredMixin, ListView):
paginate_by = 6
context_object_name = 'content_list'
***SOME STUFF HERE***
def get_queryset(self):
*** SOME STUFF HERE***
def get_context_data(self, **kwargs):
*** SOME STUFF HERE***
return context
Except for LoginRequiredMixin at the CBV, I do not check login explicitly and redirect to the login page (the login page is defined at the settings.py). I have already checked handful of threads dealing with similar issues, but my issue is not resolved.
What am I doing wrong here?

DJANGO ALL AUTHEMAIL_CONFIRMATION_AUTHENTICATED_REDIRECT_URL

I am trying to have two different redirects...one for normal login and another for redirect after email confirmation
ACCOUNT_EMAIL_CONFIRMATION_AUTHENTICATED_REDIRECT_URL = '/profile'
LOGIN_REDIRECT_URL = '/'
But when I enable login, AUTHENTICATED REDIRECT goes to LOGIN_REDIRECT but when I disable Login it goes to the EMAIL_CONFIRMATION_REDIRECT route.
When I try printing the adapter settings for email_confirmation redirect url below it shows only the LOGIN_REDIRECT
def get_email_confirmation_redirect_url(self, request):
""" The URL to return to after successful e-mail confirmation. """
if request.user.is_authenticated:
if app_settings.EMAIL_CONFIRMATION_AUTHENTICATED_REDIRECT_URL:
return \
app_settings.EMAIL_CONFIRMATION_AUTHENTICATED_REDIRECT_URL
else:
return self.get_login_redirect_url(request)
else:
return app_settings.EMAIL_CONFIRMATION_ANONYMOUS_REDIRECT_URL
I tried overriding this get_email_confirmation_redirect_url in the adapter but still wont work. It is not picking the REDIRECT before I login and reverify.
Since ACCOUNT_EMAIL_CONFIRMATION_AUTHENTICATED_REDIRECT_URL = '/profile' was not working if the user is not logged in, I decided to override DefaultAccountAdapter in Django Allauth. My login was that if the time the user joined the app and the time logged in exceeds a certain threshold, then the redirection would be different. So I created an adapter in my users app as below:
class AccountAdapter(DefaultAccountAdapter):
def get_login_redirect_url(self, request):
expiry = 90 #seconds
assert request.user.is_authenticated
if (request.user.last_login - request.user.date_joined).seconds < expiry:
url = 'profile/'
else:
url = settings.LOGIN_REDIRECT_URL
return resolve_url(url)
I then passed this adapter in my settings.py

Force password change on first login (Django)

A client requires the ability for managers to add users to a company (with a random one time password) where the user must change their password before accessing anything. I am developing the app in Django 2.2
I made a custom user, replacing username with an email address and I added a change_password bool flag to the user. My change_password form/function works properly, but redirecting does not.
urls.py
path('change-password/', views.change_password, name='change-password'),
views.py
class Login(LoginView):
def form_valid(self, form):
# form is valid (= correct password), now check if user requires to set own password
if form.get_user().change_password:
return HttpResponseRedirect(reverse('change-password'))
else:
auth_login(self.request, form.get_user())
return HttpResponseRedirect(self.get_success_url())
def change_password(request):
if request.method == 'POST':
form = PasswordChangeForm(data=request.POST, user=request.user)
if form.is_valid():
form.save()
request.user.change_password = False
request.user.save()
update_session_auth_hash(request, request.user)
return redirect(reverse('user_view'))
else:
return redirect(reverse('change-password'))
else:
form = PasswordChangeForm(user=request.user)
args = {'form': form}
return render(request, 'users/change_password.html', args)
The expected behavior is to redirect to change-password if the change_password flag is True, however, while the app does redirect to change-password, upon Submission the following error is thrown:
NotImplementedError: Django doesn't provide a DB representation for AnonymousUser.
If I add the decorator #login_required to my change_password function this error goes away, however, I am redirected back to the login page with the URL: users/login/?next=/users/change-password/
The problem is that in form_valid method you are calling form.get_user() which authenticates/gets the user and checks for the change_password correctly, but it does not log the user in, meaning that the user making the requests is still anonymous to the system. So while the user gets redirected they are not authenticated, which means that the request.user objects is of type AnonymousUser which does not live in the database hence the Django doesn't provide a DB representation for AnonymousUser error.
And when you use the #login_required decorator the user gets redirected to the login page because it is not a logged in user and the decorator requires the user to be logged in to see the view it is decorating.
The URL that you see users/login/?next=/users/change-password/ is basically how the login_required decorator works and it is doing two things:
1. redirect anonymous user to the login page (the users/login part of the URL)
2. once they have successfully logged in redirect them back from where they came from (?next=/users/change-password/)
My suggestion is that you pass the username of the user that tried to log in but has to change their password to the change_password view and have a form waiting for the user there that asks for the current password, new one and a confirmation of the new password. It is the simplest way to do what you want to do, but you will have to confirm that the users current password is correct again though.
Sorry for the confusing first answer, I didn't read the question right the first time, hopefully this makes more sense :)

Logging in user through Django rest framework

I'm trying to log in a user through DRF view.
#list_route(methods=['post'])
def login(self, request, *args, **kwargs):
login_form = LoginForm(request, data=request.data or None)
if login_form.is_valid():
_login(request._request, login_form.get_user())
else:
raise forms.ValidationError("login wrong")
return Response({})
Above code runs without exception, but it doesn't seem to actually log in the user.
When I refresh the browser, the user is still not logged in.
How can I log in the user?
Please don't use DRF to log people in with session.
The warning a few lines after the SessionAuthentication gives you more details about it.
Seems that the _login() method doesn't do what you expect.
As you mention in your your comment. you were using:
from django.contrib.auth.views import login as _login
but this method:
Displays the login form and handles the login action.
You should (as you do) use:
from django.contrib.auth import login as _login
which:
Persist a user id and a backend in the request. This way a user doesn't
have to reauthenticate on every request. Note that data set during
the anonymous session is retained when the user logs in.