├── .gitignore ├── LICENSE ├── README.md └── backend ├── __init__.py ├── apps ├── __init__.py ├── todo │ ├── models.py │ └── routers.py └── user │ ├── __init__.py │ ├── auth.py │ ├── models.py │ └── routers.py ├── config └── __init__.py ├── main.py ├── requirements.in └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | .vscode 132 | .envrc 133 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2021 MongoDB 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FARM-Authentication 2 | 3 | This code is an example FARM (FastAPI, React, MongoDB) project with Authentication. 4 | 5 | It was written to accompany the MongoDB developer article "[Adding Authentication to your FARM Stack App](https://developer.mongodb.com/how-to/FARM-Stack-Authentication)". You should probably read that blog post before running the code here, but if you want to get started quickly short instructions are below. 6 | 7 | ## Installation 8 | 9 | Install into your currently active [Python environment](https://docs.python.org/3/tutorial/venv.html) with: 10 | 11 | ```bash 12 | python3 -m pip install -r requirements.txt 13 | ``` 14 | 15 | ## Configuration 16 | 17 | You'll need to set the following environment variables before running the project: 18 | 19 | ```bash 20 | # The following will work on Linux & OSX: 21 | export DEBUG_MODE=True 22 | export DB_URL="mongodb+srv://:@/farmstack?retryWrites=true&w=majority" 23 | export JWT_SECRET_KEY="" 24 | export REALM_APP_ID="" 25 | ``` 26 | 27 | ## Run It 28 | 29 | Run the code with the following command: 30 | 31 | ```bash 32 | uvicorn main:app --reload 33 | ``` 34 | -------------------------------------------------------------------------------- /backend/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongodb-developer/FARM-Auth/71ee987fa47867115101ae82062696e8dec726e5/backend/__init__.py -------------------------------------------------------------------------------- /backend/apps/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongodb-developer/FARM-Auth/71ee987fa47867115101ae82062696e8dec726e5/backend/apps/__init__.py -------------------------------------------------------------------------------- /backend/apps/todo/models.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | import uuid 3 | from pydantic import BaseModel, Field 4 | 5 | 6 | class TaskModel(BaseModel): 7 | id: str = Field(default_factory=uuid.uuid4, alias="_id") 8 | name: str = Field(...) 9 | completed: bool = False 10 | 11 | class Config: 12 | allow_population_by_field_name = True 13 | schema_extra = { 14 | "example": { 15 | "id": "00010203-0405-0607-0809-0a0b0c0d0e0f", 16 | "name": "My important task", 17 | "completed": True, 18 | } 19 | } 20 | 21 | 22 | class UpdateTaskModel(BaseModel): 23 | name: Optional[str] 24 | completed: Optional[bool] 25 | 26 | class Config: 27 | schema_extra = { 28 | "example": { 29 | "name": "My important task", 30 | "completed": True, 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /backend/apps/todo/routers.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Body, Request, HTTPException, status, Depends 2 | from fastapi.responses import JSONResponse 3 | from fastapi.encoders import jsonable_encoder 4 | 5 | from apps.user.models import User 6 | from .models import TaskModel, UpdateTaskModel 7 | 8 | 9 | def get_todo_router(app): 10 | 11 | router = APIRouter() 12 | 13 | @router.post( 14 | "/", 15 | response_description="Add new task", 16 | ) 17 | async def create_task( 18 | request: Request, 19 | user: User = Depends(app.fastapi_users.get_current_active_user), 20 | task: TaskModel = Body(...), 21 | ): 22 | task = jsonable_encoder(task) 23 | new_task = await request.app.db["tasks"].insert_one(task) 24 | created_task = await request.app.db["tasks"].find_one( 25 | {"_id": new_task.inserted_id} 26 | ) 27 | 28 | return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_task) 29 | 30 | @router.get("/", response_description="List all tasks") 31 | async def list_tasks( 32 | request: Request, 33 | user: User = Depends(app.fastapi_users.get_current_active_user), 34 | ): 35 | tasks = [] 36 | for doc in await request.app.db["tasks"].find().to_list(length=100): 37 | tasks.append(doc) 38 | return tasks 39 | 40 | @router.get("/{id}", response_description="Get a single task") 41 | async def show_task( 42 | id: str, 43 | request: Request, 44 | user: User = Depends(app.fastapi_users.get_current_active_user), 45 | ): 46 | if (task := await request.app.db["tasks"].find_one({"_id": id})) is not None: 47 | return task 48 | 49 | raise HTTPException(status_code=404, detail=f"Task {id} not found") 50 | 51 | @router.put("/{id}", response_description="Update a task") 52 | async def update_task( 53 | id: str, 54 | request: Request, 55 | user: User = Depends(app.fastapi_users.get_current_active_user), 56 | task: UpdateTaskModel = Body(...), 57 | ): 58 | task = {k: v for k, v in task.dict().items() if v is not None} 59 | 60 | if len(task) >= 1: 61 | update_result = await request.app.db["tasks"].update_one( 62 | {"_id": id}, {"$set": task} 63 | ) 64 | 65 | if update_result.modified_count == 1: 66 | if ( 67 | updated_task := await request.app.db["tasks"].find_one({"_id": id}) 68 | ) is not None: 69 | return updated_task 70 | 71 | if ( 72 | existing_task := await request.app.db["tasks"].find_one({"_id": id}) 73 | ) is not None: 74 | return existing_task 75 | 76 | raise HTTPException(status_code=404, detail=f"Task {id} not found") 77 | 78 | @router.delete("/{id}", response_description="Delete Task") 79 | async def delete_task( 80 | id: str, 81 | request: Request, 82 | user: User = Depends(app.fastapi_users.get_current_active_user), 83 | ): 84 | delete_result = await request.app.db["tasks"].delete_one({"_id": id}) 85 | 86 | if delete_result.deleted_count == 1: 87 | return JSONResponse(status_code=status.HTTP_204_NO_CONTENT) 88 | 89 | raise HTTPException(status_code=404, detail=f"Task {id} not found") 90 | 91 | return router 92 | -------------------------------------------------------------------------------- /backend/apps/user/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongodb-developer/FARM-Auth/71ee987fa47867115101ae82062696e8dec726e5/backend/apps/user/__init__.py -------------------------------------------------------------------------------- /backend/apps/user/auth.py: -------------------------------------------------------------------------------- 1 | from fastapi_users.authentication import CookieAuthentication 2 | from fastapi_users.utils import JWT_ALGORITHM, generate_jwt 3 | from config import settings 4 | 5 | 6 | class MongoDBRealmJWTAuthentication(CookieAuthentication): 7 | def __init__(self, *args, **kwargs): 8 | super(MongoDBRealmJWTAuthentication, self).__init__(*args, **kwargs) 9 | self.token_audience = settings.REALM_APP_ID 10 | 11 | async def _generate_token(self, user): 12 | data = { 13 | "user_id": str(user.id), 14 | "sub": str(user.id), 15 | "aud": self.token_audience, 16 | "external_user_id": str(user.id), 17 | } 18 | return generate_jwt(data, self.lifetime_seconds, self.secret, JWT_ALGORITHM) 19 | 20 | 21 | jwt_authentication = MongoDBRealmJWTAuthentication( 22 | secret=settings.JWT_SECRET_KEY, 23 | lifetime_seconds=settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES * 60, 24 | cookie_name="FARM_auth", 25 | cookie_secure=settings.SECURE_COOKIE, 26 | ) 27 | -------------------------------------------------------------------------------- /backend/apps/user/models.py: -------------------------------------------------------------------------------- 1 | from fastapi_users.models import BaseUser, BaseUserCreate, BaseUserUpdate, BaseUserDB 2 | 3 | 4 | class User(BaseUser): 5 | pass 6 | 7 | 8 | class UserCreate(BaseUserCreate): 9 | pass 10 | 11 | 12 | class UserUpdate(User, BaseUserUpdate): 13 | pass 14 | 15 | 16 | class UserDB(User, BaseUserDB): 17 | pass 18 | -------------------------------------------------------------------------------- /backend/apps/user/routers.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Request 2 | from config import settings 3 | 4 | from .models import UserDB 5 | from .auth import jwt_authentication 6 | 7 | 8 | def get_users_router(app): 9 | users_router = APIRouter() 10 | 11 | def on_after_register(user: UserDB, request: Request): 12 | print(f"User {user.id} has registered.") 13 | 14 | def on_after_forgot_password(user: UserDB, token: str, request: Request): 15 | print(f"User {user.id} has forgot their password. Reset token: {token}") 16 | 17 | users_router.include_router( 18 | app.fastapi_users.get_auth_router(jwt_authentication), 19 | prefix="/auth/jwt", 20 | tags=["auth"], 21 | ) 22 | users_router.include_router( 23 | app.fastapi_users.get_register_router(on_after_register), 24 | prefix="/auth", 25 | tags=["auth"], 26 | ) 27 | users_router.include_router( 28 | app.fastapi_users.get_reset_password_router( 29 | settings.JWT_SECRET_KEY, after_forgot_password=on_after_forgot_password 30 | ), 31 | prefix="/auth", 32 | tags=["auth"], 33 | ) 34 | users_router.include_router( 35 | app.fastapi_users.get_users_router(), prefix="/users", tags=["users"] 36 | ) 37 | 38 | return users_router 39 | -------------------------------------------------------------------------------- /backend/config/__init__.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseSettings 2 | 3 | 4 | class CommonSettings(BaseSettings): 5 | APP_NAME: str = "FARM Auth" 6 | DEBUG_MODE: bool = False 7 | 8 | 9 | class ServerSettings(BaseSettings): 10 | HOST: str = "0.0.0.0" 11 | PORT: int = 8000 12 | 13 | 14 | class DatabaseSettings(BaseSettings): 15 | REALM_APP_ID: str 16 | DB_URL: str 17 | DB_NAME: str 18 | 19 | 20 | class AuthSettings(BaseSettings): 21 | JWT_SECRET_KEY: str 22 | JWT_ACCESS_TOKEN_EXPIRE_MINUTES: int = 15 23 | SECURE_COOKIE: bool = False 24 | 25 | 26 | class Settings(CommonSettings, ServerSettings, DatabaseSettings, AuthSettings): 27 | pass 28 | 29 | 30 | settings = Settings() 31 | -------------------------------------------------------------------------------- /backend/main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from fastapi_users import FastAPIUsers 3 | from fastapi_users.db import MongoDBUserDatabase 4 | import uvicorn 5 | from motor.motor_asyncio import AsyncIOMotorClient 6 | from config import settings 7 | 8 | from apps.user.auth import jwt_authentication 9 | from apps.user.models import User, UserCreate, UserUpdate, UserDB 10 | from apps.user.routers import get_users_router 11 | from apps.todo.routers import get_todo_router 12 | 13 | app = FastAPI() 14 | 15 | 16 | @app.on_event("startup") 17 | async def configure_db_and_routes(): 18 | app.mongodb_client = AsyncIOMotorClient( 19 | settings.DB_URL, uuidRepresentation="standard" 20 | ) 21 | app.db = app.mongodb_client.get_default_database() 22 | 23 | user_db = MongoDBUserDatabase(UserDB, app.db["users"]) 24 | 25 | app.fastapi_users = FastAPIUsers( 26 | user_db, 27 | [jwt_authentication], 28 | User, 29 | UserCreate, 30 | UserUpdate, 31 | UserDB, 32 | ) 33 | 34 | app.include_router(get_users_router(app)) 35 | app.include_router(get_todo_router(app)) 36 | 37 | 38 | @app.on_event("shutdown") 39 | async def shutdown_db_client(): 40 | app.mongodb_client.close() 41 | 42 | 43 | if __name__ == "__main__": 44 | uvicorn.run( 45 | "main:app", 46 | host=settings.HOST, 47 | reload=settings.DEBUG_MODE, 48 | port=settings.PORT, 49 | ) 50 | -------------------------------------------------------------------------------- /backend/requirements.in: -------------------------------------------------------------------------------- 1 | # API Server 2 | fastapi 3 | uvicorn[standard] 4 | pydantic[email] 5 | ujson 6 | email-validator 7 | python-multipart 8 | 9 | # JWT & Auth 10 | fastapi-users[mongodb] 11 | 12 | # Database 13 | motor 14 | dnspython 15 | 16 | # Development 17 | black 18 | flake8 19 | -------------------------------------------------------------------------------- /backend/requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # To update, run: 4 | # 5 | # pip-compile requirements.in 6 | # 7 | appdirs==1.4.4 8 | # via black 9 | bcrypt==3.2.0 10 | # via passlib 11 | black==20.8b1 12 | # via -r requirements.in 13 | cffi==1.14.4 14 | # via bcrypt 15 | click==7.1.2 16 | # via 17 | # black 18 | # uvicorn 19 | dnspython==2.0.0 20 | # via 21 | # -r requirements.in 22 | # email-validator 23 | email-validator==1.1.1 24 | # via 25 | # -r requirements.in 26 | # fastapi-users 27 | # pydantic 28 | fastapi-users[mongodb]==3.1.1 29 | # via -r requirements.in 30 | fastapi==0.61.1 31 | # via 32 | # -r requirements.in 33 | # fastapi-users 34 | flake8==3.8.4 35 | # via -r requirements.in 36 | h11==0.11.0 37 | # via uvicorn 38 | httptools==0.1.1 39 | # via uvicorn 40 | idna==2.10 41 | # via email-validator 42 | makefun==1.9.5 43 | # via fastapi-users 44 | mccabe==0.6.1 45 | # via flake8 46 | motor==2.3.0 47 | # via 48 | # -r requirements.in 49 | # fastapi-users 50 | mypy-extensions==0.4.3 51 | # via black 52 | passlib[bcrypt]==1.7.4 53 | # via fastapi-users 54 | pathspec==0.8.0 55 | # via black 56 | pycodestyle==2.6.0 57 | # via flake8 58 | pycparser==2.20 59 | # via cffi 60 | pydantic[email]==1.7 61 | # via 62 | # -r requirements.in 63 | # fastapi 64 | pyflakes==2.2.0 65 | # via flake8 66 | pyjwt==1.7.1 67 | # via fastapi-users 68 | pymongo==3.11.0 69 | # via motor 70 | python-dotenv==0.14.0 71 | # via uvicorn 72 | python-multipart==0.0.5 73 | # via 74 | # -r requirements.in 75 | # fastapi-users 76 | pyyaml==5.3.1 77 | # via uvicorn 78 | regex==2020.10.23 79 | # via black 80 | six==1.15.0 81 | # via 82 | # bcrypt 83 | # python-multipart 84 | starlette==0.13.6 85 | # via fastapi 86 | toml==0.10.1 87 | # via black 88 | typed-ast==1.4.1 89 | # via black 90 | typing-extensions==3.7.4.3 91 | # via black 92 | ujson==4.0.1 93 | # via -r requirements.in 94 | uvicorn[standard]==0.12.2 95 | # via -r requirements.in 96 | uvloop==0.14.0 97 | # via uvicorn 98 | watchgod==0.6 99 | # via uvicorn 100 | websockets==8.1 101 | # via uvicorn 102 | --------------------------------------------------------------------------------