def test_access_token_expires_after_30_min() -> None: thirtyone_mins_from_now = generate_time_from_now(1860) uow = register_account_helper() access_token = get_access_token_helper(uow) with freeze_time(thirtyone_mins_from_now): with pytest.raises(ExpiredSignatureError): Authentication.verify_token(access_token)
def wrapper(*args: Any, **kwargs: Any) -> Any: if not authorization_header: raise AuthorizationError('No authorization header provided') access_token = authorization_header.split(' ')[1] if not access_token: raise AuthorizationError('No authorization token provided') try: Authentication.verify_token(access_token) except Exception as e: raise AuthorizationError(e) from e return func(*args, **kwargs)
def login_endpoint() -> Tuple[Response, int]: try: if not request.json: raise APIError('Missing request body', 400) uow = UnitOfWork() email = request.json['email'] password = request.json['password'] access_token = get_access_token(uow, email, password) token_store = TokenStore() user = Authentication().get_jwt_identity(access_token) user_id = int(user['user_id']) username = user['username'] refresh_token = set_refresh_token(token_store, user_id, username) res = jsonify({'access_token': access_token}) res.set_cookie('refresh_token', refresh_token, httponly=True, domain='.talel.io', secure=not current_app.config['DEBUG']) return res, 200 except KeyError as error: raise APIError(f'Expected {error} key', 400) from error except AccountError as error: raise APIError(str(error), 401) from error
def register_account(uow: UnitOfWork, email: str, password: str, username: str) -> Account: whitelisted_emails = getenv('WHITELISTED_EMAILS') if whitelisted_emails is not None and email not in whitelisted_emails.split( ','): raise AccountRegistrationError('Email not whitelisted') with uow: if uow.account.get(Account, email=email) is not None: raise AccountRegistrationError( f"Account with the email '{email}' already exists") if uow.user.get(User, username=username): raise AccountRegistrationError( f"Account with the username '{username}' already exists") password_hash = Authentication().generate_password_hash(password) user = User(username) account = Account(email, password_hash, user) account_record = uow.account.add(account) uow.commit() account_record = uow.account.get(Account, email=email) verification_token = account.generate_verification_token account.send_registration_email(verification_token) return account_record
def validate_verification_token(token: str) -> Dict[str, str]: try: verified_token = Authentication.verify_token(token) return verified_token except InvalidSignatureError as error: raise InvalidSignatureError( 'Invalid verification token') from error
def test_can_get_access_token_for_user() -> None: uow = register_account_helper() access_token = get_access_token_helper(uow) username = Authentication().get_jwt_identity(access_token)['username'] assert username == USERNAME_TALEL
def generate_access_token(user_id: int, username: str) -> str: thirty_mins_from_now = generate_time_from_now(seconds=1800) payload = { 'user_id': user_id, 'username': username, 'exp': thirty_mins_from_now } return Authentication.generate_token(payload)
def test_cannot_verify_account_with_invalid_token(self) -> None: invalid_verification_token = Authentication.generate_token( {'email': EMAIL_TALEL}, 'invalidsecretkey') res = self.verify_account_request(invalid_verification_token) res_data = json.loads(res.data) assert res.status_code == 400 assert res_data['error']['message'] == 'Invalid verification token'
def test_cannot_verify_non_registered_account(self) -> None: unknown_verification_token = Authentication.generate_token( {'email': INVALID_EMAIL}) res = self.verify_account_request(unknown_verification_token) res_data = json.loads(res.data) assert res.status_code == 400 assert res_data['error'][ 'message'] == f"No registered account with the email '{INVALID_EMAIL}'"
def set_refresh_token(token_store: TokenStore, user_id: int, username: str) -> str: now = generate_time_from_now(seconds=0) payload = {'user_id': user_id, 'username': username, 'iat': now} refresh_token = Authentication.generate_token(payload) token_store.set_token(user_id, refresh_token) return refresh_token
def test_can_set_refresh_token_for_user() -> None: token_store = FakeTokenStore() refresh_token = set_refresh_token(token_store, INITIAL_USER_ID, USERNAME_TALEL) # type: ignore user_id, username, iat = Authentication().get_jwt_identity( refresh_token).values() assert username == USERNAME_TALEL assert user_id == INITIAL_USER_ID assert iat
def test_cannot_verify_already_verified_account(self) -> None: bianca_verification_token = Authentication.generate_token( {'email': EMAIL_BIANCA}) self.register_account_request(bianca_registration_data) self.verify_account_request(bianca_verification_token) res = self.verify_account_request(bianca_verification_token) res_data = json.loads(res.data) assert res.status_code == 400 assert res_data['error']['message'] == 'Account already verified'
def test_can_verify_account(self) -> None: talel_verification_token = Authentication.generate_token( {'email': EMAIL_TALEL}) res_account = self.register_account_request(talel_registration_data) res_account_data = json.loads(res_account.data) assert not res_account_data['verified'] res_verify = self.verify_account_request(talel_verification_token) res_verify_data = json.loads(res_verify.data) assert res_verify.status_code == 200 assert res_verify_data['verified']
def protected_logout_endpoint() -> Tuple[Response, int]: try: access_token = extract_access_token_from_authorization_header( cast(str, authorization_header)) token_store = TokenStore() user = Authentication().get_jwt_identity(access_token) user_id = int(user['user_id']) assert delete_refresh_token(token_store, user_id) return jsonify({'message': 'Successfully logged out'}), 200 except TokenError as error: raise APIError(str(error), 409) from error
def test_cannot_generate_new_access_token_for_user_with_no_stored_refresh_token( self) -> None: invalid_refresh_token = Authentication.generate_token({ 'user_id': INITIAL_USER_ID + 1986, 'username': USERNAME_TALEL }) res = self.new_access_token_request(invalid_refresh_token) res_data = json.loads(res.data) assert res.status_code == 401 assert res_data['error'][ 'message'] == 'No stored refresh token for user'
def get_access_token(uow: UnitOfWork, email: str, password: str) -> str: error_msg = 'Invalid username or password' with uow: account_record = uow.account.get(Account, email=email) if account_record is None: raise AccountError(error_msg) password_hash = account_record.password if not Authentication().check_password_hash(password, password_hash): raise AccountError(error_msg) access_token = generate_access_token(account_record.user.id, account_record.user.username) return access_token
def generate_authorization_header(user_id: int = INITIAL_USER_ID, username: str = USERNAME_TALEL, access_token: Union[str, None] = None, no_token: bool = False, invalid_token: bool = False) -> Dict[str, str]: if not access_token: access_token = Authentication.generate_token({'user_id': user_id, 'username': username}) if no_token: authorization_header = {'Authorization': 'Bearer '} return authorization_header if invalid_token: authorization_header = {'Authorization': f'Bearer {access_token + "1986"}'} return authorization_header authorization_header = {'Authorization': f'Bearer {access_token}'} return authorization_header
def new_access_token_endpoint() -> Tuple[Response, int]: try: refresh_token = request.cookies.get('refresh_token') or '' token_store = TokenStore() user = Authentication().get_jwt_identity(refresh_token) user_id = int(user['user_id']) username = user['username'] assert verify_refresh_token(token_store, user_id, refresh_token) access_token = generate_access_token(user_id, username) return jsonify({'access_token': access_token}), 200 except InvalidSignatureError as error: raise APIError(str(error), 400) from error except DecodeError as error: raise APIError(str(error), 400) from error except TokenError as error: raise APIError(str(error), 401) from error
def protected_upload_images_endpoint() -> Tuple[Response, int]: try: access_token = extract_access_token_from_authorization_header( cast(str, authorization_header)) user = Authentication().get_jwt_identity(access_token) asset_store = AssetStore() image_streams = get_streams_from_request_files(request.files) user_id = int(user['user_id']) bucket = current_app.config['S3_BUCKET'] image_objects_urls = upload_images(asset_store, image_streams, user_id, bucket) return jsonify(image_objects_urls), 200 except ImageError as error: raise APIError(str(error), 400) from error except client('s3').exceptions.ClientError as error: raise APIError(str(error), 400) from error
def protected_create_article_endpoint() -> Tuple[Response, int]: try: if not request.json: raise APIError('Missing request body', 400) access_token = extract_access_token_from_authorization_header( cast(str, authorization_header)) uow = UnitOfWork() user = Authentication().get_jwt_identity(access_token) user_id = int(user['user_id']) title = request.json['title'] body = request.json['body'] created_article = create_article(uow, user_id, title, body) res_body = ArticleSchema().dump(created_article) return res_body, 201 except KeyError as error: raise APIError(f'Expected {error} key', 400) from error
def verify_account_helper(uow: FakeUnitOfWork, email: str = EMAIL_TALEL) -> None: verification_token = Authentication.generate_token({'email': email}) verify_account(uow, verification_token) # type: ignore
def generate_verification_token(self) -> str: token = Authentication.generate_token({'email': self.email}) return token
from talelio_backend.identity_and_access.authentication import Authentication from talelio_backend.tests.constants import PASSWORD SALT = 'talel' auth = Authentication() def test_can_generate_password_hash_with_new_salt() -> None: password_hash = auth.generate_password_hash(PASSWORD) salt = password_hash.split(',')[1] assert len(salt) == 5 assert salt.isascii() def test_can_generate_password_hash_when_provided_salt() -> None: password_hash = auth.generate_password_hash(PASSWORD, SALT) salt = password_hash.split(',')[1] assert salt == SALT def test_valid_password_returns_true_when_checking_hash() -> None: password_hash = auth.generate_password_hash(PASSWORD) password = auth.check_password_hash(PASSWORD, password_hash) assert password def test_invalid_password_returns_false_when_checking_hash() -> None: