async def send_mail(msg: EmailMsg): '''Sends an email''' # Get settings settings = get_settings() # Build message smtp_msg = EmailMessage() smtp_msg['Subject'] = msg.subject # pylint: disable=no-member smtp_msg['From'] = get_address(settings.EMAIL_FROM.name, settings.EMAIL_FROM.email) smtp_msg['To'] = msg.recipient smtp_msg.set_content(msg.text) smtp_msg.add_alternative(msg.html, subtype='html') # Get security mail_sec = settings.EMAIL_SECURITY if mail_sec == EmailSecurity.TLS_SSL: sec_opts = {'use_tls': True} elif mail_sec == EmailSecurity.STARTTLS: sec_opts = {'start_tls': True} else: # Unsecure sec_opts = {} # Send mail await aiosmtplib.send(smtp_msg, hostname=settings.EMAIL_HOSTNAME, port=settings.EMAIL_PORT, username=settings.EMAIL_USERNAME, password=settings.EMAIL_PASSWORD.get_secret_value(), **sec_opts)
async def test_create_access_token_with_expire(get_jwt_key, jwt_encode, freezer): '''Should return an access token''' # Create mocks get_jwt_key.return_value = 'test-private-key' jwt_encode.return_value = 'test-access-token' # Get settings settings = get_settings() # Call function delta = timedelta(minutes=123456) token = await auth.create_access_token(user_id='test-user-id', expires_delta=delta) # Assert results assert token == 'test-access-token' get_jwt_key.assert_called_with('private') jwt_encode.assert_called_with( { 'sub': 'user:test-user-id', 'exp': datetime.utcnow() + delta, }, 'test-private-key', algorithm=settings.JWT_ALG, )
async def validate_access_token(token: str) -> AccessTokenData: '''Validates and extracts User ID from token Raises InvalidTokenError: Provided token is invalid ''' # Get settings settings = get_settings() # Decode JWT token try: jwt_key_public = get_jwt_key('public') payload = jwt.decode(token, jwt_key_public, algorithms=[settings.JWT_ALG]) except jwt.PyJWTError: raise InvalidTokenError(f'Unable to decode token "{token}"') # Extract user ID try: user_id = payload.get("sub").split(':')[1] except IndexError: raise InvalidTokenError( f"Token payload doesn't contain valid user ID. Payload: {payload!r}" ) # Build access token data try: return AccessTokenData(user_id=user_id) except ValidationError: raise InvalidTokenError( f'JWT token contains invalid user ID: "{user_id!r}"' )
def assert_email_send(args, kwargs): '''Helper to assert the email.send mock''' # Get settings settings = get_settings() # Assert results smtp_msg = args[0] assert smtp_msg['Subject'] == 'test-subject' assert smtp_msg[ 'From'] == f'{settings.EMAIL_FROM.name} <{settings.EMAIL_FROM.email}>' assert smtp_msg['To'] == 'TestUser <*****@*****.**>' assert kwargs['hostname'] == settings.EMAIL_HOSTNAME assert kwargs['port'] == settings.EMAIL_PORT assert kwargs['username'] == settings.EMAIL_USERNAME assert kwargs['password'] == settings.EMAIL_PASSWORD.get_secret_value()
def prepare_register_verification(recipient: Address, secret: str) -> EmailMsg: '''Prepare mail with registration verification link''' # Get settings settings = get_settings() # Build message registration_link = f'{settings.FRONTEND_URL}/register/verify?token={secret}' msg = TEMPLATE_REGISTER_TEXT.format(registration_link=registration_link) msg_html = TEMPLATE_REGISTER_HTML.format( registration_link=registration_link) return EmailMsg( recipient=recipient, subject='Verify your Kinky Harbor account', text=msg, html=msg_html, )
def prepare_reset_password(recipient: Address, user_id: str, token: str) -> EmailMsg: '''Prepare mail with link to reset your password''' # Get settings settings = get_settings() # Build message link = f'{settings.FRONTEND_URL}/login/reset-password?user={user_id}&token={token}' msg = TEMPLATE_RESET_PASSWORD_TEXT.format(reset_password_link=link) msg_html = TEMPLATE_RESET_PASSWORD_HTML.format(reset_password_link=link) return EmailMsg( recipient=recipient, subject='Password reset for Kinky Harbor', text=msg, html=msg_html, )
def prepare_register_email_exist(recipient: Address) -> EmailMsg: '''Prepare mail with link to request a password reset''' # Get settings settings = get_settings() # Build message reset_password_link = f'{settings.FRONTEND_URL}/login/request-reset/' msg = TEMPLATE_REGISTER_EMAIL_EXISTS_TEXT.format( reset_password_link=reset_password_link) msg_html = TEMPLATE_REGISTER_EMAIL_EXISTS_HTML.format( reset_password_link=reset_password_link) return EmailMsg( recipient=recipient, subject='Registration attempt at Kinky Harbor', text=msg, html=msg_html, )
async def create_access_token(*, user_id: str, expires_delta: timedelta = None): '''Generates an access token containing provided data''' # Get settings settings = get_settings() # Prepare contents if expires_delta: expire = datetime.utcnow() + expires_delta else: minutes = settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES expire = datetime.utcnow() + timedelta(minutes=minutes) data = { "sub": f"user:{user_id}", "exp": expire, } # Generate token jwt_key_private = get_jwt_key('private') return jwt.encode(data, jwt_key_private, algorithm=settings.JWT_ALG)
async def test_success_validate_access_token(get_jwt_key, jwt_decode): '''Should return an access token''' # Create mocks get_jwt_key.return_value = 'test-public-key' jwt_decode.return_value = {'sub': 'user:507f1f77bcf86cd799439011'} # Get settings settings = get_settings() # Call function data = await auth.validate_access_token(token='test-jwt-token') # Assert results get_jwt_key.assert_called_with('public') jwt_decode.assert_called_with( 'test-jwt-token', 'test-public-key', algorithms=[settings.JWT_ALG], ) assert data == AccessTokenData(user_id='507f1f77bcf86cd799439011', )
async def test_fail_invalid_token_other(get_jwt_key, jwt_decode, payload): '''Should throw InvalidTokenError''' # Create mocks get_jwt_key.return_value = 'test-public-key' jwt_decode.return_value = payload # Get settings settings = get_settings() # Call function with pytest.raises(auth.InvalidTokenError): await auth.validate_access_token(token='test-jwt-token') # Assert results get_jwt_key.assert_called_with('public') jwt_decode.assert_called_with( 'test-jwt-token', 'test-public-key', algorithms=[settings.JWT_ALG], )
# Close database connections @app.on_event("shutdown") async def close_repos(): '''Close DB client of repositories on application shutdown''' logging.info("Database repositories: Closing ...") await app.state.repos['refresh_token'].close() await app.state.repos['stats'].close() await app.state.repos['user'].close() await app.state.repos['verif_token'].close() logging.info("Database repositories: Closed") # Add CORS app.add_middleware( CORSMiddleware, allow_origins=get_settings().CORS, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) @app.get('/', include_in_schema=False) async def redirect_to_docs(): '''Redirect root page to docs.''' return RedirectResponse(url='/docs') # Allows debugging of the application # https://fastapi.tiangolo.com/tutorial/debugging/ if __name__ == '__main__':
def get_default_db(client: motor.AsyncIOMotorClient): '''Returns client for default database''' return client[get_settings().MONGO_DATABASE]
def create_db_client(): '''Returns instance of database client''' return motor.AsyncIOMotorClient(get_settings().MONGO_HOST)