def generate_phone_verification_code(phone): phone = normalize_number(phone) db_code = VC.query \ .filter(VC.phone == phone) \ .first() if db_code is None: db_code = db_models.VerificationCode() db.session.add(db_code) elif (time_.utcnow() - db_code.updated_at).total_seconds() < 10: # If the client has requested a verification code already within # the last 10 seconds, # throw a rate limit error, so they can't just keep creating codes # and guessing them # rapidly. raise PhoneVerificationError( 'Please wait briefly before requesting' ' a new verification code.') db_code.phone = phone db_code.code = random_numeric_token() db_code.expires_at = time_.utcnow() + datetime.timedelta( minutes=CODE_EXPIRATION_TIME_MINUTES) db.session.commit() try: send_code_via_sms(phone, db_code.code) except TwilioRestException as e: db.session.rollback() raise PhoneVerificationError('Could not send' ' verification code.') return VerificationServiceResponse()
def verify_phone(self, req): addr = numeric_eth(req.eth_address) db_code = VC.query \ .filter(VC.eth_address == addr) \ .filter(VC.phone == req.phone) \ .first() if db_code is None: raise service_utils.req_error( code='NOT_FOUND', path='phone', message='The given phone number was not found.') if req.code != db_code.code: raise service_utils.req_error( code='INVALID', path='code', message='The code you provided is invalid.') if time_.utcnow() > db_code.expires_at: raise service_utils.req_error( code='EXPIRED', path='code', message='The code you provided has expired.') db_identity = db_models.Identity(eth_address=addr, phone=req.phone, verified=True) db.session.add(db_identity) db.session.commit() return verification.VerifyPhoneResponse( attestation=generate_signed_attestation(addr))
def verify_phone(phone, code, eth_address): phone = normalize_number(phone) db_code = VC.query \ .filter(VC.phone == phone) \ .first() if db_code is None: raise PhoneVerificationError( 'The given phone number was not found.') if code != db_code.code: raise PhoneVerificationError('The code you provided' ' is invalid.') if time_.utcnow() > db_code.expires_at: raise PhoneVerificationError('The code you provided' ' has expired.') # TODO: determine what the text should be data = 'phone verified' # TODO: determine claim type integer code for phone verification signature = attestations.generate_signature(signing_key, eth_address, CLAIM_TYPES['phone'], data) return VerificationServiceResponse({ 'signature': signature, 'claim_type': CLAIM_TYPES['phone'], 'data': data })
def generate_email_verification_code(email): db_code = VC.query \ .filter(func.lower(VC.email) == func.lower(email)) \ .first() if db_code is None: db_code = db_models.VerificationCode() db.session.add(db_code) elif (time_.utcnow() - db_code.updated_at).total_seconds() < 10: # If the client has requested a verification code already within # the last 10 seconds, throw a rate limit error, so they can't just # keep creating codes and guessing them rapidly. raise EmailVerificationError( 'Please wait briefly before requesting' ' a new verification code.') db_code.email = email db_code.code = random_numeric_token() db_code.expires_at = time_.utcnow() + datetime.timedelta( minutes=CODE_EXPIRATION_TIME_MINUTES) db.session.commit() send_code_via_email(email, db_code.code) return VerificationServiceResponse()
def test_generate_phone_verification_rate_limit_exceeded( session, mock_normalize_number): vc_obj = VerificationCodeFactory.build() vc_obj.updated_at = utcnow() + datetime.timedelta(seconds=9) session.add(vc_obj) session.commit() phone = vc_obj.phone with pytest.raises(PhoneVerificationError) as service_err: VerificationService.generate_phone_verification_code(phone) assert str(service_err.value) == ('Please wait briefly before requesting a' ' new verification code.')
def test_generate_email_verification_code_email_already_in_db( MockHttpClient, session): vc_obj = VerificationCodeFactory.build() expires_at = vc_obj.expires_at vc_obj.created_at = utcnow() - datetime.timedelta(seconds=10) vc_obj.updated_at = utcnow() - datetime.timedelta(seconds=10) session.add(vc_obj) session.commit() email = vc_obj.email resp = VerificationService.generate_email_verification_code(email) assert isinstance(resp, VerificationServiceResponse) assert VC.query.filter(VC.email == email).count() == 1 db_code = VC.query.filter(VC.email == email).first() assert db_code is not None assert db_code.code is not None assert len(db_code.code) == 6 assert db_code.expires_at is not None assert db_code.created_at is not None assert db_code.updated_at is not None assert db_code.updated_at >= db_code.created_at assert db_code.expires_at > expires_at
def generate_phone_verification_code(self, req): addr = numeric_eth(req.eth_address) db_code = VC.query \ .filter(VC.eth_address == addr) \ .first() if db_code is None: db_code = db_models.VerificationCode(eth_address=addr) db.session.add(db_code) elif (time_.utcnow() - db_code.updated_at).total_seconds() < 10: # If the client has requested a verification code already within the last 10 seconds, # throw a rate limit error, so they can't just keep creating codes and guessing them # rapidly. raise service_utils.req_error( code='RATE_LIMIT_EXCEEDED', message= 'Please wait briefly before requesting a new verification code.' ) db_code.phone = req.phone db_code.code = random_numeric_token() db_code.expires_at = time_.utcnow() + datetime.timedelta( minutes=CODE_EXPIRATION_TIME_MINUTES) db.session.commit() send_code_via_sms(req.phone, db_code.code) return verification.GeneratePhoneVerificationCodeResponse()
def test_verify_phone_expired_code(session, mock_normalize_number): vc_obj = VerificationCodeFactory.build() vc_obj.expires_at = utcnow() - datetime.timedelta(days=1) session.add(vc_obj) session.commit() args = { 'eth_address': str_eth(sample_eth_address), 'phone': vc_obj.phone, 'code': vc_obj.code } with pytest.raises(PhoneVerificationError) as service_err: VerificationService.verify_phone(**args) assert str(service_err.value) == 'The code you provided has expired.'
def test_generate_phone_verification_code_phone_already_in_db( mock_send_sms, session, mock_normalize_number): vc_obj = VerificationCodeFactory.build() expires_at = vc_obj.expires_at vc_obj.created_at = utcnow() - datetime.timedelta(seconds=10) vc_obj.updated_at = utcnow() - datetime.timedelta(seconds=10) session.add(vc_obj) session.commit() phone = vc_obj.phone resp = VerificationService.generate_phone_verification_code(phone) assert isinstance(resp, VerificationServiceResponse) assert VC.query.filter(VC.phone == phone).count() == 1 db_code = VC.query.filter(VC.phone == phone).first() assert db_code is not None assert db_code.code is not None assert len(db_code.code) == 6 assert db_code.expires_at is not None assert db_code.created_at is not None assert db_code.updated_at is not None assert db_code.updated_at >= db_code.created_at assert db_code.expires_at > expires_at
def verify_email(email, code, eth_address): db_code = VC.query \ .filter(func.lower(VC.email) == func.lower(email)) \ .first() if db_code is None: raise EmailVerificationError('The given email was' ' not found.') if code != db_code.code: raise EmailVerificationError('The code you provided' ' is invalid.') if time_.utcnow() > db_code.expires_at: raise EmailVerificationError('The code you provided' ' has expired.') # TODO: determine what the text should be data = 'email verified' # TODO: determine claim type integer code for email verification signature = attestations.generate_signature(signing_key, eth_address, CLAIM_TYPES['email'], data) return VerificationServiceResponse({ 'signature': signature, 'claim_type': CLAIM_TYPES['email'], 'data': data })
def get_future_expiry(): return utcnow() + datetime.timedelta(seconds=20)