Двухэтапная авторизация django

Понадобилось сделать подтверждение авторизации через SMS… Подумалось, и придумалось вот что:
Переделывать полностью систему авторизации было влом, да и вообще хотелось бы обойтись «малой кровью», поэтому решено было переделать django’ый декоратор login_required — чтобы не только спрашивал логин и пароль, но и активировал сессии через SMS

Идея будет такая:

  1. В профиле пользователя сохраняем телефон и флаг использования SMS-авторизации.
  2. Переписываем декоратор login_required, чтобы после авторизации отправлял на активацию сессии
  3. Заменяем во всех views from django.contrib.auth.decorators import login_required на наш декоратор
  4. ???
  5. PROFIT!

Использован вот этот SMS-шлюз (ибо как-то уже пробовал и работает)
модуль для работы с онным скачать тут

В коде реализована только идея, допиливать можно (и нужно!)
для работы добавить url (/accounts/code) на вьюшку code_prompt
/accounts/code-error — ошибка отправки SMS
добавить шаблон «registration/code.html» — из него методом POST на ту же view отправляется «code» с правильным ответом.
добавить поля в профиль пользователя: phone с телефоном формата 7XXXXXXXXXX и флаг use_sms (boolean)
ну и заменить XXXXXXXXXXXXYYYYYYYYYYYYZZZZZZZZXXXXXXXXXXXXYYYYYYYYYYYYZZZZZZZZ на API_KEY

Что плохо:

  • нет задержки перед отправкой SMS — небольшой скриптик опустошит баланс легко и непринужденно (если логин и пароль пользователя известен)
  • не учитывается количество отправленных смс
  • сохраняется, но не используется время активации сессии

переменные сессии
code — случайный код
code_date — время генерации кода (действителен 300 секунд, при повторном обращении к send_code через 180 и более секунд, после генерации создается новый)
active — дата активации сессии

Но в качестве отправной точки думается хорошо

 
from django.contrib.auth.decorators import login_required as base_login_required
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.contrib import auth
import datetime
import smspilot
import random
 
def login_required(function=None,*args,**kwargs):
    def decorator(function,*args_d,**kwargs_d):
        def wrapper(request,*args, **kwargs):
            base = base_login_required(function,*args_d,**kwargs_d)
            result = base(request,*args,**kwargs)
            if request.user.is_authenticated():
                if request.user.get_profile().use_sms:
                    if  not 'active' in request.session:
                        if send_code(request):
                            return HttpResponseRedirect("/accounts/code)
                        else:
                            return HttpResponseRedirect("/accounts/code-error")
            return result
        return wrapper
    if function:
        return decorator(function,*args,**kwargs)
    return decorator
 
 
 
def generate_code():
    random.seed()
    return str(random.randint(10000,99999))
 
def send_code(request):
    user = request.user
    session = request.session
    gen_code = True
    code = ""
    if 'code' in session and 'code_date' in session:
        if int((datetime.datetime.now() - session['code_date']).total_seconds())< 180:
            code = session['code']
            gen_code=False
 
    if gen_code:
        session["code"] = generate_code()
        session["code_date"] = datetime.datetime.now()
        code = session["code"]
 
    send = True
    if send:
        sms = smspilot.Sender("XXXXXXXXXXXXYYYYYYYYYYYYZZZZZZZZXXXXXXXXXXXXYYYYYYYYYYYYZZZZZZZZ")
        sms.addSMS(user.get_profile().phone,u"Код доступа: %s" % code, u'InfoSerivce')
        result = sms.send()
        try:
            if int(result['send'][0]['status']) == 0:
                return True
        except:
            pass
        return False
    else:
        print "Phone: %s, code %s" %(user.get_profile().phone, code)
        return True
 
 
 
def check_code(code,user,session):
    if 'code' in session and 'code_date' in session:
        if int((datetime.datetime.now() - session['code_date']).total_seconds())< 300:
            if code==session["code"]:
                return True
    if 'code'in session:
        del session['code']
    if 'code_date' in session:
        del session['code_date']
    return False
 
 
@base_login_required()
def code_prompt(request):
    if request.POST:
        if check_code(request.POST['code'], request.user,request.session):
            request.session['active'] = datetime.datetime.now()
            return HttpResponseRedirect("/")
        else:
            if 'active' in request.session:
                del request.session['active']
            auth.logout(request)
            return HttpResponseRedirect("/")
    return render_to_response("registration/code.html", {}, RequestContext(request))