예제 #1
0
def api_verify_code(request):
    """检查验证码是否正确

    POST /api/account/verify-code
    {
        // 验证码用途,可以是:register, login, reset-password
        "usage": "register",

        // 与 send-code 类似,有两种方式指定发送对象
        // * 提供 phone 或 email 字段
        // * 提供 identity 字段
        "phone": "13912345678",
        "email": "*****@*****.**",
        "identity": "13912345678",

        // 用户输入的验证码
        "code": "123456",
    }

    如果成功,返回 200,否则返回 400。
    """
    usage = request.json.get('usage')
    if usage not in otp_tools.USAGES:
        return api.bad_request(message='Invalid "usage"')

    code = request.json.get('code')

    identity = request.json.get('identity')
    phone = request.json.get('phone')
    email = request.json.get('email')

    if phone:
        if not is_valid_phone(phone):
            return errors.InvalidPhone()
    elif email:
        if not is_valid_email(email):
            return errors.InvalidEmail()
    elif identity:
        if is_valid_phone(identity):
            phone = identity
        elif is_valid_email(identity):
            email = identity
        else:
            return errors.InvalidIdentity()
    else:
        return api.bad_request(message='Missing "phone", "email" or "identity"')

    recipient = phone or email
    engine = 'sms' if phone else 'email'
    valid = otp_tools.verify_code(engine, request, recipient, usage, code, False)
    if valid:
        return api.ok(message='验证码正确')
    else:
        return errors.InvalidVerifyCode()
예제 #2
0
    def authenticate(self, request, identity=None, email=None, code=None, **kwargs):
        if not email and is_valid_email(identity):
            email = identity

        if not email or not code:
            return None

        try:
            user = UserModel.objects.get(email_validated=True, email=email)
        except UserModel.DoesNotExist:
            return None
        except FieldError:
            return None

        usage = kwargs.get('usage', USAGES.login)
        valid = verify_code('email', request, email, usage, code, True)

        if valid and self.user_can_authenticate(user):
            return user
예제 #3
0
def api_send_code(request):
    """发送验证码,此函数仅支持发送注册、短信登录、重置密码三种验证码

    POST /api/account/send-code
    {
        // 验证码用途,可以是:register, login, reset-password
        "usage": "register",

        // 有两种方式指定发送对象,
        // * 提供 phone 字段,则会发送短信, 提供 email 字段,则会发送邮件
        // * 提供 identity 字段,则会自动判断 identity 是手机号还是邮箱,从而执行相应的操作
        // phone、email、identity 三个参数只能提供一个
        "phone": "13912345678",
        "email": "*****@*****.**",
        "identity": "13912345678",
    }

    * 如果缺少参数(一般只在开发阶段),会返回 400,message 描述错误信息。
    * 如果传递的 phone、email 或 identity 非法,会返回 InvalidPhone, InvalidEmail 或 InvalidIdentity
    * 如果在静默期内重复请求,会返回 RateExceeded
    * 其他所有情况都会返回 200(比如被后端认定为恶意请求而没有发送,比如登录请求中提供的
      username 不存在而未发送等等),这些错误无需告知用户。

    登录、重置密码验证码,后端会查找用户,如果用户不存在,则不会发送验证码。
    如果用户存在,但是该用户的 phone_validated 为 False(即手机号未验证),
    也不会发送验证码。邮箱也是如此。即发送验证码的前置条件是相应的短信或邮箱已经验证过。

    对于注册验证码,我们不检查账号是否已经存在,也不会因为账号已存在而不发送短信。原因:
    * 如果我们告诉用户账号已存在,那么有心人可以遍历手机号来判断我们有哪些注册账号
    * 如果我们不告诉用户账号已存在、也不发送验证码,那么用户会很奇怪(用户收不到验证码,
      会觉得很奇怪)。让用户正常收验证码,在注册时会提示账号已存在。
    """
    usage = request.json.get('usage')
    if usage not in otp_tools.USAGES:
        return api.bad_request(message='Invalid "usage"')

    identity = request.json.get('identity')
    phone = request.json.get('phone')
    email = request.json.get('email')

    if phone:
        if not is_valid_phone(phone):
            return errors.InvalidPhone()
    elif email:
        if not is_valid_email(email):
            return errors.InvalidEmail()
    elif identity:
        if is_valid_phone(identity):
            phone = identity
        elif is_valid_email(identity):
            email = identity
        else:
            return errors.InvalidIdentity()
    else:
        return api.bad_request(message='Missing "phone", "email" or "identity"')

    # 对于登录、重置密码的验证码,我们需要校验用户存在
    if usage in ['login', 'reset-password']:
        if phone:
            user = User.objects.filter(phone=phone, phone_validated=True).first()
        else:
            user = User.objects.filter(email=email, email_validated=True).first()

        if not user or not user.is_active:
            # 用户不存在或用户已锁定,不发送验证码,但仍然返回“验证码已发送”
            return api.ok(message='验证码已发送')

    recipient = phone or email
    engine = 'sms' if phone else 'email'
    vcode, result = otp_tools.generate_code(engine, request, recipient, usage=usage)
    if result == otp_tools.GENERATE_RESULTS.silent:
        return errors.RateExceeded()

    return api.ok(message='验证码已发送')
예제 #4
0
파일: tools.py 프로젝트: verseboys/Cardpc
def validate_recipient(engine, recipient):
    if engine == 'sms' and is_valid_phone(recipient):
        return True
    if engine == 'email' and is_valid_email(recipient):
        return True
    return False
예제 #5
0
파일: account.py 프로젝트: verseboys/Cardpc
def api_register(request):
    """
    注册 API

    POST /api/account/register
    {
        // 与登录 API 类似,可以明确指定 phone+code 或 email+code 或 identity+code
        "identity": "13912345678",
        "code": "123456",

        "phone": "13912345678",
        "code": "123456",

        "email": "*****@*****.**",
        "code": "123456",

        "password": "******",
    }
    """
    code = request.json.get('code')
    if not code:
        return errors.InvalidVerifyCode()

    password = request.json.get('password')
    if not password:
        return api.bad_request('Missing "password"')

    identity = request.json.get('identity')
    phone = request.json.get('phone')
    email = request.json.get('email')

    if phone:
        if not is_valid_phone(phone):
            return errors.InvalidPhone()
    elif email:
        if not is_valid_email(email):
            return errors.InvalidEmail()
    elif identity:
        if is_valid_phone(identity):
            phone = identity
        elif is_valid_email(identity):
            email = identity
        else:
            return errors.InvalidIdentity()
    else:
        return api.bad_request(
            message='Missing "phone", "email" or "identity"')

    if phone:
        find_user_args = dict(phone=phone, phone_validated=True)
        create_user_args = dict(phone=phone,
                                phone_validated=True,
                                password=password)
        vcode_engine = 'sms'
        recipient = phone
    else:
        create_user_args = dict(email=email,
                                email_validated=True,
                                password=password)
        vcode_engine = 'email'
        recipient = email

    valid = otp_tools.verify_code(vcode_engine, request, recipient, 'register',
                                  code, True)
    if not valid:
        return errors.InvalidVerifyCode()

    # 由于数据库中不宜为 phone、email 添加 unique contraint
    # (主要是 Django 里的实现,如果要添加 unique contraint,那么 phone、email
    # 比如允许为 null,但 django 中 email 并未声明允许为 null,一些地方的代码
    # 也有这样的假定,因此我们尽量不打破这种惯例)
    # 我们手动判断邮箱或手机号是否存在,如果存在则报错。这会存在 race condition,
    # 有可能两个并发的请求导致创建了邮箱或手机号相同的账号,不过这种概率应该很低。
    # 除非两个请求同时运行上述 verify_code 并且都返回了 True (极小概率时间),
    # 而且这里又同时读取数据库、创建新用户,才会导致问题。因此暂时不考虑这个问题。

    if phone:
        if User.objects.filter(phone=phone, phone_validated=True).exists():
            return errors.PhoneExists()
        user = User.objects.create_user(phone=phone,
                                        phone_validated=True,
                                        password=password)
    else:
        if User.objects.filter(email=email, email_validated=True).exists():
            return errors.EmailExists()
        user = User.objects.create_user(email=email,
                                        email_validated=True,
                                        password=password)

    return api.ok(data=serialize_user(user))