0

I want to send a login link to the users.

I know there are some OneTimePassword apps out there with thousands of features. But I just want some easy and barebon way to login user via login link.

My question is if this is a correct way to go about this. Like best practice and DRY code.

So I've set up a table that stores three rows. 1. 'user' The user 2. 'autogeneratedkey' A autogenerated key 3. 'created_at' A Timestamp

When they login, the'll be sent a mail containing a login link valid for nn minutes. So the login would be something like

https://example.net/login/?username=USERNAME&autogeneratedkey=KEY

The tricky part for me is to figure out a good way to check this and log in the user. I'm just guessing here. But would this be a good approach?

class login(generic.CreateView):
  def get(self, request, *args, **kwargs):
    try:
        autgeneratedkey = self.request.GET.get('autgeneratedkey', '')
        username = self.request.GET.get('username', '')

        obj_key = Login.objects.filter(autgeneratedkey=autgeneratedkey)[0]
        obj_user = Login.objects.filter(userusername=username)[0]

        try:
            if obj_user == obj_key: #Compare the objects if same
                if datetime.datetime.now() < (obj_key.created_at + datetime.timedelta(minutes=10)): #Check so the key is not older than 10min
                    u = CustomUser.objects.get(pk=obj_user.user_id)
                    login(request, u)
                    Login.objects.filter(autgeneratedkey=autgeneratedkey).delete()
                else:
                    return login_fail
            else:
                return login_fail

    except:
        return login_fail

    return redirect('index')

def login_fail(self, request, *args, **kwargs):
    return render(request, 'login/invalid_login.html')

It feels sloppy to call the same post using first the autogeneratedkey then using the username. Also stacking if-else feels tacky.

sumpen
  • 503
  • 6
  • 19

2 Answers2

0

I would not send the username in the get request. Just send an autogenerated key.

http://example.com/login?key=random-long-string

Then this db schema (it's a new table because I don't know if Login is already being used.

LoginKey ( id [PK], user [FK(CustomUser)], key [Unique], expiry )

When a user provides an email, you create a new LoginKey.

Then do something like this:

def get(self, request, *args, **kwargs):
  key = request.GET.get('key', '')
  if not key:
    return login_fail

  login_key = LoginKey.objects.get(key=key)
  if login_key is None or datetime.datetime.now() > login_key.expiry:
    return login_fail

  u = login_key.user
  login(request, u)
  login_key.delete()
  return redirect('index')
blueteeth
  • 3,330
  • 1
  • 13
  • 23
0

Probably you can optimize the code like this:

First assuming you have relationship between User and Login Model like this:

class Login(models.Model):
    user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)

Then you can use a view like this:

class LoginView(generic.View):
  def get(self, request, *args, **kwargs):
    try:
        autgeneratedkey = self.request.GET.get('autgeneratedkey', '')
        username = self.request.GET.get('username', '')           
        user = CustomUser.objects.get(login__autgeneratedkey=autgeneratedkey, username=username, login__created_at__gte=datetime.now()-datetime.timedelta(minutes=10))
        login(request, user)
        user.login_set.all().delete()  # delete all login objects

    except CustomUser.DoesNotExist:
        return login_fail

    return redirect('index')

Just another thing, it is not a good practice to use GET method where the database is updated. GET methods should be idempotent. Its better to use a post method here. Just allow user to click the link(which will be handled by a different template view), then from that template, use ajax to make a POST request to this view.

ruddra
  • 50,746
  • 7
  • 78
  • 101