From 2bfc751d01d7aff9f4dd3b8b146d802b34fb5cc2 Mon Sep 17 00:00:00 2001 From: Jayden Pyles Date: Sat, 6 Jul 2024 22:40:47 -0500 Subject: [PATCH] feat: convert to self-hosted version --- .gitignore | 2 +- api/backend/amazon.py | 49 ---- api/backend/app.py | 18 +- api/backend/auth/__init__.py | 0 api/backend/auth/auth_router.py | 57 +++++ api/backend/auth/auth_utils.py | 101 ++++++++ api/backend/database.py | 23 ++ api/backend/job.py | 26 +++ api/backend/schemas.py | 30 +++ docker-compose.yml | 13 +- next.config.mjs | 8 + package-lock.json | 93 +++++++- package.json | 2 + pdm.lock | 393 +++++++++++++++++++++++++++++++- pyproject.toml | 9 +- src/components/NavDrawer.tsx | 29 +-- src/hooks/useAuth.ts | 52 +++++ src/lib/auth0.ts | 19 -- src/pages/_app.tsx | 21 +- src/pages/index.tsx | 172 ++++++++++---- src/pages/jobs.tsx | 28 ++- src/pages/login.tsx | 122 ++++++++++ src/useAuth.ts | 13 -- tsconfig.json | 15 +- 24 files changed, 1073 insertions(+), 222 deletions(-) delete mode 100644 api/backend/amazon.py create mode 100644 api/backend/auth/__init__.py create mode 100644 api/backend/auth/auth_router.py create mode 100644 api/backend/auth/auth_utils.py create mode 100644 api/backend/database.py create mode 100644 api/backend/job.py create mode 100644 api/backend/schemas.py create mode 100644 src/hooks/useAuth.ts delete mode 100644 src/lib/auth0.ts create mode 100644 src/pages/login.tsx delete mode 100644 src/useAuth.ts diff --git a/.gitignore b/.gitignore index 5722c2b..946ef03 100644 --- a/.gitignore +++ b/.gitignore @@ -183,5 +183,5 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ .pdm-python - .next +postgres_data diff --git a/api/backend/amazon.py b/api/backend/amazon.py deleted file mode 100644 index 6780db7..0000000 --- a/api/backend/amazon.py +++ /dev/null @@ -1,49 +0,0 @@ -# STL -import os -import logging -from typing import Any - -# PDM -import boto3 -from mypy_boto3_dynamodb.service_resource import Table, DynamoDBServiceResource - -LOG = logging.getLogger(__name__) - - -def connect_to_dynamo() -> Table: - region_name = os.getenv("AWS_REGION") - dynamodb: DynamoDBServiceResource = boto3.resource( - "dynamodb", region_name=region_name - ) - return dynamodb.Table("scrape") - - -def insert(table: Table, item: dict[str, Any]) -> None: - i = table.put_item(Item=item) - LOG.info(f"Inserted item: {i}") - - -def query(table: Table, index_name: str, key_condition: Any) -> list[Any]: - try: - response = table.query( - IndexName=index_name, KeyConditionExpression=key_condition - ) - items = response.get("Items", []) - for item in items: - LOG.info(f"Queried item: {item}") - return items - except Exception as e: - LOG.error(f"Failed to query table: {e}") - raise - - -def query_by_id(table: Table, key_condition: Any) -> list[Any]: - try: - response = table.query(KeyConditionExpression=key_condition) - items = response.get("Items", []) - for item in items: - LOG.info(f"Queried item: {item}") - return items - except Exception as e: - LOG.error(f"Failed to query table: {e}") - raise diff --git a/api/backend/app.py b/api/backend/app.py index 748f51b..f18c5da 100644 --- a/api/backend/app.py +++ b/api/backend/app.py @@ -5,27 +5,28 @@ from io import StringIO # PDM import pandas as pd -from fastapi import FastAPI, HTTPException +from fastapi import FastAPI from fastapi.encoders import jsonable_encoder from fastapi.responses import FileResponse, JSONResponse, StreamingResponse from fastapi.staticfiles import StaticFiles from fastapi.middleware.cors import CORSMiddleware -from boto3.dynamodb.conditions import Key # LOCAL -from api.backend.amazon import query, insert, query_by_id, connect_to_dynamo +from api.backend.job import query, insert from api.backend.models import DownloadJob, SubmitScrapeJob, RetrieveScrapeJobs from api.backend.scraping import scrape +from api.backend.auth.auth_router import auth_router logging.basicConfig( level=logging.INFO, - format="[%(levelname)s] %(asctime)s - %(name)s - %(message)s", + format="%(levelname)s: %(asctime)s - %(name)s - %(message)s", handlers=[logging.StreamHandler()], ) LOG = logging.getLogger(__name__) app = FastAPI(title="api") +app.include_router(auth_router) app.add_middleware( CORSMiddleware, @@ -54,10 +55,9 @@ async def submit_scrape_job(job: SubmitScrapeJob): ) json_scraped = jsonable_encoder(scraped) - table = connect_to_dynamo() job.result = json_scraped job.id = uuid.uuid4().hex - insert(table, jsonable_encoder(job)) + await insert(jsonable_encoder(job)) return JSONResponse(content=json_scraped) except Exception as e: return JSONResponse(content={"error": str(e)}, status_code=500) @@ -67,8 +67,7 @@ async def submit_scrape_job(job: SubmitScrapeJob): async def retrieve_scrape_jobs(retrieve: RetrieveScrapeJobs): LOG.info(f"Retrieving jobs for account: {retrieve.user}") try: - table = connect_to_dynamo() - results = query(table, "user", Key("user").eq(retrieve.user)) + results = await query({"user": retrieve.user}) return JSONResponse(content=results) except Exception as e: LOG.error(f"Exception occurred: {e}") @@ -79,8 +78,7 @@ async def retrieve_scrape_jobs(retrieve: RetrieveScrapeJobs): async def download(download_job: DownloadJob): LOG.info(f"Downloading job with id: {download_job.id}") try: - table = connect_to_dynamo() - results = query_by_id(table, Key("id").eq(download_job.id)) + results = await query({"id": download_job.id}) df = pd.DataFrame(results) diff --git a/api/backend/auth/__init__.py b/api/backend/auth/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/backend/auth/auth_router.py b/api/backend/auth/auth_router.py new file mode 100644 index 0000000..41a1b9e --- /dev/null +++ b/api/backend/auth/auth_router.py @@ -0,0 +1,57 @@ +# STL +from datetime import timedelta + +# PDM +from fastapi import Depends, APIRouter, HTTPException, status +from fastapi.security import OAuth2PasswordRequestForm + +# LOCAL +from api.backend.schemas import User, Token, UserCreate +from api.backend.database import get_user_collection +from api.backend.auth.auth_utils import ( + ACCESS_TOKEN_EXPIRE_MINUTES, + get_current_user, + authenticate_user, + get_password_hash, + create_access_token, +) + +auth_router = APIRouter() + + +@auth_router.post("/api/auth/token", response_model=Token) +async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()): + user = await authenticate_user(form_data.username, form_data.password) + if not user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Incorrect username or password", + headers={"WWW-Authenticate": "Bearer"}, + ) + + expire_minutes = ( + int(ACCESS_TOKEN_EXPIRE_MINUTES) if ACCESS_TOKEN_EXPIRE_MINUTES else 60 + ) + + access_token_expires = timedelta(minutes=expire_minutes) + access_token = create_access_token( + data={"sub": user.email}, expires_delta=access_token_expires + ) + + return {"access_token": access_token, "token_type": "bearer"} + + +@auth_router.post("/api/auth/signup", response_model=User) +async def create_user(user: UserCreate): + users_collection = get_user_collection() + hashed_password = get_password_hash(user.password) + user_dict = user.model_dump() + user_dict["hashed_password"] = hashed_password + del user_dict["password"] + _ = await users_collection.insert_one(user_dict) + return user_dict + + +@auth_router.get("/api/auth/users/me", response_model=User) +async def read_users_me(current_user: User = Depends(get_current_user)): + return current_user diff --git a/api/backend/auth/auth_utils.py b/api/backend/auth/auth_utils.py new file mode 100644 index 0000000..7f7e11c --- /dev/null +++ b/api/backend/auth/auth_utils.py @@ -0,0 +1,101 @@ +# STL +import os +from typing import Any, Optional +from datetime import datetime, timedelta + +# PDM +from jose import JWTError, jwt +from dotenv import load_dotenv +from fastapi import Depends, HTTPException, status +from passlib.context import CryptContext +from fastapi.security import OAuth2PasswordBearer + +# LOCAL +from api.backend.schemas import User, UserInDB, TokenData +from api.backend.database import get_user_collection + +_ = load_dotenv() + +SECRET_KEY = os.getenv("SECRET_KEY") or "" +ALGORITHM = os.getenv("ALGORITHM") or "" +ACCESS_TOKEN_EXPIRE_MINUTES = os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES") + +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/token") + + +def verify_password(plain_password: str, hashed_password: str): + return pwd_context.verify(plain_password, hashed_password) + + +def get_password_hash(password: str): + return pwd_context.hash(password) + + +async def get_user(email: str): + user_collection = get_user_collection() + user = await user_collection.find_one({"email": email}) + + if not user: + return + + return UserInDB(**user) + + +async def authenticate_user(email: str, password: str): + user = await get_user(email) + + if not user: + return False + + if not verify_password(password, user.hashed_password): + return False + + return user + + +def create_access_token( + data: dict[str, Any], expires_delta: Optional[timedelta] = None +): + to_encode = data.copy() + + if expires_delta: + expire = datetime.now() + expires_delta + else: + expire = datetime.now() + timedelta(minutes=15) + to_encode.update({"exp": expire}) + + encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) + return encoded_jwt + + +async def get_current_user(token: str = Depends(oauth2_scheme)): + credentials_exception = HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Could not validate credentials", + headers={"WWW-Authenticate": "Bearer"}, + ) + + try: + payload: Optional[dict[str, Any]] = jwt.decode( + token, SECRET_KEY, algorithms=[ALGORITHM] + ) + if not payload: + raise credentials_exception + + email = payload.get("sub") + + if email is None: + raise credentials_exception + + token_data = TokenData(email=email) + + except JWTError: + raise credentials_exception + + user = await get_user(email=token_data.email) + + if user is None: + raise credentials_exception + + return user diff --git a/api/backend/database.py b/api/backend/database.py new file mode 100644 index 0000000..4af96ea --- /dev/null +++ b/api/backend/database.py @@ -0,0 +1,23 @@ +# STL +import os +from typing import Any + +# PDM +from dotenv import load_dotenv +from motor.motor_asyncio import AsyncIOMotorClient + +_ = load_dotenv() + +MONGODB_URI = os.getenv("MONGODB_URI") + + +def get_user_collection(): + client: AsyncIOMotorClient[dict[str, Any]] = AsyncIOMotorClient(MONGODB_URI) + db = client["scrape"] + return db["users"] + + +def get_job_collection(): + client: AsyncIOMotorClient[dict[str, Any]] = AsyncIOMotorClient(MONGODB_URI) + db = client["scrape"] + return db["jobs"] diff --git a/api/backend/job.py b/api/backend/job.py new file mode 100644 index 0000000..27db04f --- /dev/null +++ b/api/backend/job.py @@ -0,0 +1,26 @@ +# STL +import logging +from typing import Any + +# LOCAL +from api.backend.database import get_job_collection + +LOG = logging.getLogger(__name__) + + +async def insert(item: dict[str, Any]) -> None: + collection = get_job_collection() + i = await collection.insert_one(item) + LOG.info(f"Inserted item: {i}") + + +async def query(filter: dict[str, Any]) -> list[dict[str, Any]]: + collection = get_job_collection() + cursor = collection.find(filter) + results: list[dict[str, Any]] = [] + + async for document in cursor: + del document["_id"] + results.append(document) + + return results diff --git a/api/backend/schemas.py b/api/backend/schemas.py new file mode 100644 index 0000000..27c0870 --- /dev/null +++ b/api/backend/schemas.py @@ -0,0 +1,30 @@ +# STL +from typing import Optional + +# PDM +from pydantic import EmailStr, BaseModel + + +class Token(BaseModel): + access_token: str + token_type: str + + +class TokenData(BaseModel): + email: Optional[str] = None + + +class User(BaseModel): + email: EmailStr + full_name: Optional[str] = None + disabled: Optional[bool] = None + + +class UserInDB(User): + hashed_password: str + + +class UserCreate(BaseModel): + email: EmailStr + password: str + full_name: Optional[str] = None diff --git a/docker-compose.yml b/docker-compose.yml index defe451..30e4024 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,15 +20,20 @@ services: - "--providers.docker=true" - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" - # - "--providers.file.filename=/etc/traefik/dynamic_conf.yaml" ports: - 80:80 - 443:443 volumes: - "/var/run/docker.sock:/var/run/docker.sock:ro" - # - "./dynamic_conf.yaml:/etc/traefik/dynamic_conf.yaml" - # - "/etc/letsencrypt/live/domain/fullchain.pem:/etc/certs/ssl-cert.pem" - # - "/etc/letsencrypt/live/domain/privkey.pem:/etc/certs/ssl-cert.key" + networks: + - web + mongo: + container_name: webscrape-mongo + image: mongo + restart: always + environment: + MONGO_INITDB_ROOT_USERNAME: root + MONGO_INITDB_ROOT_PASSWORD: example networks: - web networks: diff --git a/next.config.mjs b/next.config.mjs index 3419958..2781a46 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -2,6 +2,14 @@ const nextConfig = { output: "export", distDir: "./dist", + async rewrites() { + return [ + { + source: "/auth/:path*", + destination: "/api/auth/:path*", + }, + ]; + }, }; export default nextConfig; diff --git a/package-lock.json b/package-lock.json index fab3b45..37ab2ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,9 +19,11 @@ "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "axios": "^1.7.2", "bootstrap": "^5.3.0", "framer-motion": "^4.1.17", "next": "^14.2.4", + "next-auth": "^4.24.7", "react": "^18.3.1", "react-bootstrap": "^2.8.0", "react-dom": "^18.3.1", @@ -7349,6 +7351,29 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/axobject-query": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", @@ -10489,9 +10514,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", @@ -15291,6 +15316,33 @@ } } }, + "node_modules/next-auth": { + "version": "4.24.7", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.7.tgz", + "integrity": "sha512-iChjE8ov/1K/z98gdKbn2Jw+2vLgJtVV39X+rCP5SGnVQuco7QOr19FRNGMIrD8d3LYhHWV9j9sKLzq1aDWWQQ==", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@panva/hkdf": "^1.0.2", + "cookie": "^0.5.0", + "jose": "^4.15.5", + "oauth": "^0.9.15", + "openid-client": "^5.4.0", + "preact": "^10.6.3", + "preact-render-to-string": "^5.1.19", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "next": "^12.2.5 || ^13 || ^14", + "nodemailer": "^6.6.5", + "react": "^17.0.2 || ^18", + "react-dom": "^17.0.2 || ^18" + }, + "peerDependenciesMeta": { + "nodemailer": { + "optional": true + } + } + }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -15372,6 +15424,11 @@ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==" }, + "node_modules/oauth": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==" + }, "node_modules/oauth4webapi": { "version": "2.11.1", "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.11.1.tgz", @@ -17183,6 +17240,31 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, + "node_modules/preact": { + "version": "10.22.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.22.1.tgz", + "integrity": "sha512-jRYbDDgMpIb5LHq3hkI0bbl+l/TQ9UnkdQ0ww+lp+4MMOdqaUYdFc5qeyP+IV8FAd/2Em7drVPeKdQxsiWCf/A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz", + "integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==", + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, + "node_modules/preact-render-to-string/node_modules/pretty-format": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -17321,6 +17403,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", diff --git a/package.json b/package.json index 1ce8112..9c8bb6d 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,11 @@ "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "axios": "^1.7.2", "bootstrap": "^5.3.0", "framer-motion": "^4.1.17", "next": "^14.2.4", + "next-auth": "^4.24.7", "react": "^18.3.1", "react-bootstrap": "^2.8.0", "react-dom": "^18.3.1", diff --git a/pdm.lock b/pdm.lock index 1427f44..6b3bd55 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "dev"] strategy = ["cross_platform", "inherit_metadata"] lock_version = "4.4.2" -content_hash = "sha256:a12cdcf1cdd6f91260a7d7126be4581a6820caf91ffc26386abfe9a6b3fbc9d9" +content_hash = "sha256:acc35177b2d4e7bdadd428e489a200c53caa5ea1455c442b4d9d738a60dcab77" [[package]] name = "aiohttp" @@ -122,6 +122,20 @@ files = [ {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] +[[package]] +name = "asgiref" +version = "3.8.1" +requires_python = ">=3.8" +summary = "ASGI specs, helper code, and adapters" +groups = ["default"] +dependencies = [ + "typing-extensions>=4; python_version < \"3.11\"", +] +files = [ + {file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"}, + {file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"}, +] + [[package]] name = "asttokens" version = "2.4.1" @@ -135,6 +149,16 @@ files = [ {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, ] +[[package]] +name = "async-property" +version = "0.2.2" +summary = "Python decorator for async properties." +groups = ["default"] +files = [ + {file = "async_property-0.2.2-py2.py3-none-any.whl", hash = "sha256:8924d792b5843994537f8ed411165700b27b2bd966cefc4daeefc1253442a9d7"}, + {file = "async_property-0.2.2.tar.gz", hash = "sha256:17d9bd6ca67e27915a75d92549df64b5c7174e9dc806b30a3934dc4ff0506380"}, +] + [[package]] name = "async-timeout" version = "4.0.3" @@ -168,6 +192,42 @@ files = [ {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, ] +[[package]] +name = "bcrypt" +version = "4.1.3" +requires_python = ">=3.7" +summary = "Modern password hashing for your software and your servers" +groups = ["default"] +files = [ + {file = "bcrypt-4.1.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:48429c83292b57bf4af6ab75809f8f4daf52aa5d480632e53707805cc1ce9b74"}, + {file = "bcrypt-4.1.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a8bea4c152b91fd8319fef4c6a790da5c07840421c2b785084989bf8bbb7455"}, + {file = "bcrypt-4.1.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d3b317050a9a711a5c7214bf04e28333cf528e0ed0ec9a4e55ba628d0f07c1a"}, + {file = "bcrypt-4.1.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:094fd31e08c2b102a14880ee5b3d09913ecf334cd604af27e1013c76831f7b05"}, + {file = "bcrypt-4.1.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4fb253d65da30d9269e0a6f4b0de32bd657a0208a6f4e43d3e645774fb5457f3"}, + {file = "bcrypt-4.1.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:193bb49eeeb9c1e2db9ba65d09dc6384edd5608d9d672b4125e9320af9153a15"}, + {file = "bcrypt-4.1.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:8cbb119267068c2581ae38790e0d1fbae65d0725247a930fc9900c285d95725d"}, + {file = "bcrypt-4.1.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6cac78a8d42f9d120b3987f82252bdbeb7e6e900a5e1ba37f6be6fe4e3848286"}, + {file = "bcrypt-4.1.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:01746eb2c4299dd0ae1670234bf77704f581dd72cc180f444bfe74eb80495b64"}, + {file = "bcrypt-4.1.3-cp37-abi3-win32.whl", hash = "sha256:037c5bf7c196a63dcce75545c8874610c600809d5d82c305dd327cd4969995bf"}, + {file = "bcrypt-4.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:8a893d192dfb7c8e883c4576813bf18bb9d59e2cfd88b68b725990f033f1b978"}, + {file = "bcrypt-4.1.3-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d4cf6ef1525f79255ef048b3489602868c47aea61f375377f0d00514fe4a78c"}, + {file = "bcrypt-4.1.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5698ce5292a4e4b9e5861f7e53b1d89242ad39d54c3da451a93cac17b61921a"}, + {file = "bcrypt-4.1.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec3c2e1ca3e5c4b9edb94290b356d082b721f3f50758bce7cce11d8a7c89ce84"}, + {file = "bcrypt-4.1.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3a5be252fef513363fe281bafc596c31b552cf81d04c5085bc5dac29670faa08"}, + {file = "bcrypt-4.1.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5f7cd3399fbc4ec290378b541b0cf3d4398e4737a65d0f938c7c0f9d5e686611"}, + {file = "bcrypt-4.1.3-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:c4c8d9b3e97209dd7111bf726e79f638ad9224b4691d1c7cfefa571a09b1b2d6"}, + {file = "bcrypt-4.1.3-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:31adb9cbb8737a581a843e13df22ffb7c84638342de3708a98d5c986770f2834"}, + {file = "bcrypt-4.1.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:551b320396e1d05e49cc18dd77d970accd52b322441628aca04801bbd1d52a73"}, + {file = "bcrypt-4.1.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6717543d2c110a155e6821ce5670c1f512f602eabb77dba95717ca76af79867d"}, + {file = "bcrypt-4.1.3-cp39-abi3-win32.whl", hash = "sha256:6004f5229b50f8493c49232b8e75726b568535fd300e5039e255d919fc3a07f2"}, + {file = "bcrypt-4.1.3-cp39-abi3-win_amd64.whl", hash = "sha256:2505b54afb074627111b5a8dc9b6ae69d0f01fea65c2fcaea403448c503d3991"}, + {file = "bcrypt-4.1.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:cb9c707c10bddaf9e5ba7cdb769f3e889e60b7d4fea22834b261f51ca2b89fed"}, + {file = "bcrypt-4.1.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9f8ea645eb94fb6e7bea0cf4ba121c07a3a182ac52876493870033141aa687bc"}, + {file = "bcrypt-4.1.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f44a97780677e7ac0ca393bd7982b19dbbd8d7228c1afe10b128fd9550eef5f1"}, + {file = "bcrypt-4.1.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d84702adb8f2798d813b17d8187d27076cca3cd52fe3686bb07a9083930ce650"}, + {file = "bcrypt-4.1.3.tar.gz", hash = "sha256:2ee15dd749f5952fe3f0430d0ff6b74082e159c50332a1413d51b5689cf06623"}, +] + [[package]] name = "beautifulsoup4" version = "4.12.3" @@ -296,7 +356,7 @@ version = "1.16.0" requires_python = ">=3.8" summary = "Foreign Function Interface for Python calling C code." groups = ["default"] -marker = "os_name == \"nt\" and implementation_name != \"pypy\"" +marker = "os_name == \"nt\" and implementation_name != \"pypy\" or platform_python_implementation != \"PyPy\"" dependencies = [ "pycparser", ] @@ -418,6 +478,50 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "cryptography" +version = "42.0.8" +requires_python = ">=3.7" +summary = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +groups = ["default"] +dependencies = [ + "cffi>=1.12; platform_python_implementation != \"PyPy\"", +] +files = [ + {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e"}, + {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7"}, + {file = "cryptography-42.0.8-cp37-abi3-win32.whl", hash = "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2"}, + {file = "cryptography-42.0.8-cp37-abi3-win_amd64.whl", hash = "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba"}, + {file = "cryptography-42.0.8-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14"}, + {file = "cryptography-42.0.8-cp39-abi3-win32.whl", hash = "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c"}, + {file = "cryptography-42.0.8-cp39-abi3-win_amd64.whl", hash = "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad"}, + {file = "cryptography-42.0.8.tar.gz", hash = "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2"}, +] + [[package]] name = "cssselect" version = "1.2.0" @@ -440,6 +544,19 @@ files = [ {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, ] +[[package]] +name = "deprecation" +version = "2.1.0" +summary = "A library to handle automated deprecations" +groups = ["default"] +dependencies = [ + "packaging", +] +files = [ + {file = "deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a"}, + {file = "deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff"}, +] + [[package]] name = "dnspython" version = "2.6.1" @@ -451,6 +568,20 @@ files = [ {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, ] +[[package]] +name = "ecdsa" +version = "0.19.0" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.6" +summary = "ECDSA cryptographic signature library (pure python)" +groups = ["default"] +dependencies = [ + "six>=1.9.0", +] +files = [ + {file = "ecdsa-0.19.0-py2.py3-none-any.whl", hash = "sha256:2cea9b88407fdac7bbeca0833b189e4c9c53f2ef1e1eaa29f6224dbc809b707a"}, + {file = "ecdsa-0.19.0.tar.gz", hash = "sha256:60eaad1199659900dd0af521ed462b793bbdf867432b3948e87416ae4caf6bf8"}, +] + [[package]] name = "email-validator" version = "2.1.1" @@ -550,6 +681,40 @@ files = [ {file = "fastapi_cli-0.0.3.tar.gz", hash = "sha256:3b6e4d2c4daee940fb8db59ebbfd60a72c4b962bcf593e263e4cc69da4ea3d7f"}, ] +[[package]] +name = "fastapi-keycloak" +version = "1.0.11" +requires_python = ">=3.8" +summary = "Keycloak API Client for integrating authentication and authorization with FastAPI" +groups = ["default"] +dependencies = [ + "anyio>=3.4.0", + "asgiref>=3.4.1", + "certifi>=2021.10.8", + "charset-normalizer>=2.0.9", + "click>=8.0.3", + "ecdsa>=0.17.0", + "fastapi>=0.70.1", + "h11>=0.12.0", + "idna>=3.3", + "itsdangerous>=2.0.1", + "pyasn1>=0.4.8", + "pydantic>=1.5a1", + "python-jose>=3.3.0", + "requests>=2.26.0", + "rsa>=4.8", + "six>=1.16.0", + "sniffio>=1.2.0", + "starlette>=0.16.0", + "typing-extensions>=4.0.1", + "urllib3>=1.26.7", + "uvicorn>=0.16.0", +] +files = [ + {file = "fastapi_keycloak-1.0.11-py3-none-any.whl", hash = "sha256:2d2da95cc9bfd2c02f80bbff3bd4ed985d398a07354da917ee4f7e6416373b29"}, + {file = "fastapi_keycloak-1.0.11.tar.gz", hash = "sha256:e3b4e290bfbc47077cb9bca33f6bac758216a40bfd9fe13c843c71d56087e20c"}, +] + [[package]] name = "frozenlist" version = "1.4.1" @@ -716,6 +881,17 @@ files = [ {file = "ipython-8.26.0.tar.gz", hash = "sha256:1cec0fbba8404af13facebe83d04436a7434c7400e59f47acf467c64abd0956c"}, ] +[[package]] +name = "itsdangerous" +version = "2.2.0" +requires_python = ">=3.8" +summary = "Safely pass data to untrusted environments and back." +groups = ["default"] +files = [ + {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, + {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, +] + [[package]] name = "jedi" version = "0.19.1" @@ -755,6 +931,21 @@ files = [ {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, ] +[[package]] +name = "jwcrypto" +version = "1.5.6" +requires_python = ">= 3.8" +summary = "Implementation of JOSE Web standards" +groups = ["default"] +dependencies = [ + "cryptography>=3.4", + "typing-extensions>=4.5.0", +] +files = [ + {file = "jwcrypto-1.5.6-py3-none-any.whl", hash = "sha256:150d2b0ebbdb8f40b77f543fb44ffd2baeff48788be71f67f03566692fd55789"}, + {file = "jwcrypto-1.5.6.tar.gz", hash = "sha256:771a87762a0c081ae6166958a954f80848820b2ab066937dc8b8379d65b1b039"}, +] + [[package]] name = "lxml" version = "5.2.2" @@ -1056,6 +1247,35 @@ files = [ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] +[[package]] +name = "motor" +version = "3.5.0" +requires_python = ">=3.8" +summary = "Non-blocking MongoDB driver for Tornado or asyncio" +groups = ["default"] +dependencies = [ + "pymongo<5,>=4.5", +] +files = [ + {file = "motor-3.5.0-py3-none-any.whl", hash = "sha256:e8f1d7a3370e8dd30eb4c68aaaee46dc608fbac70a757e58f3e828124f5e7693"}, + {file = "motor-3.5.0.tar.gz", hash = "sha256:2b38e405e5a0c52d499edb8d23fa029debdf0158da092c21b44d92cac7f59942"}, +] + +[[package]] +name = "motor" +version = "3.5.0" +extras = ["asyncio"] +requires_python = ">=3.8" +summary = "Non-blocking MongoDB driver for Tornado or asyncio" +groups = ["default"] +dependencies = [ + "motor==3.5.0", +] +files = [ + {file = "motor-3.5.0-py3-none-any.whl", hash = "sha256:e8f1d7a3370e8dd30eb4c68aaaee46dc608fbac70a757e58f3e828124f5e7693"}, + {file = "motor-3.5.0.tar.gz", hash = "sha256:2b38e405e5a0c52d499edb8d23fa029debdf0158da092c21b44d92cac7f59942"}, +] + [[package]] name = "multidict" version = "6.0.5" @@ -1391,6 +1611,31 @@ files = [ {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, ] +[[package]] +name = "passlib" +version = "1.7.4" +summary = "comprehensive password hashing framework supporting over 30 schemes" +groups = ["default"] +files = [ + {file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"}, + {file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"}, +] + +[[package]] +name = "passlib" +version = "1.7.4" +extras = ["bcrypt"] +summary = "comprehensive password hashing framework supporting over 30 schemes" +groups = ["default"] +dependencies = [ + "bcrypt>=3.1.0", + "passlib==1.7.4", +] +files = [ + {file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"}, + {file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"}, +] + [[package]] name = "pexpect" version = "4.9.0" @@ -1440,13 +1685,24 @@ files = [ {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, ] +[[package]] +name = "pyasn1" +version = "0.6.0" +requires_python = ">=3.8" +summary = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" +groups = ["default"] +files = [ + {file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"}, + {file = "pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c"}, +] + [[package]] name = "pycparser" version = "2.22" requires_python = ">=3.8" summary = "C parser in Python" groups = ["default"] -marker = "os_name == \"nt\" and implementation_name != \"pypy\"" +marker = "os_name == \"nt\" and implementation_name != \"pypy\" or platform_python_implementation != \"PyPy\"" files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, @@ -1571,6 +1827,46 @@ files = [ {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, ] +[[package]] +name = "pymongo" +version = "4.8.0" +requires_python = ">=3.8" +summary = "Python driver for MongoDB " +groups = ["default"] +dependencies = [ + "dnspython<3.0.0,>=1.16.0", +] +files = [ + {file = "pymongo-4.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f2b7bec27e047e84947fbd41c782f07c54c30c76d14f3b8bf0c89f7413fac67a"}, + {file = "pymongo-4.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3c68fe128a171493018ca5c8020fc08675be130d012b7ab3efe9e22698c612a1"}, + {file = "pymongo-4.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:920d4f8f157a71b3cb3f39bc09ce070693d6e9648fb0e30d00e2657d1dca4e49"}, + {file = "pymongo-4.8.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52b4108ac9469febba18cea50db972605cc43978bedaa9fea413378877560ef8"}, + {file = "pymongo-4.8.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:180d5eb1dc28b62853e2f88017775c4500b07548ed28c0bd9c005c3d7bc52526"}, + {file = "pymongo-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aec2b9088cdbceb87e6ca9c639d0ff9b9d083594dda5ca5d3c4f6774f4c81b33"}, + {file = "pymongo-4.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0cf61450feadca81deb1a1489cb1a3ae1e4266efd51adafecec0e503a8dcd84"}, + {file = "pymongo-4.8.0-cp310-cp310-win32.whl", hash = "sha256:8b18c8324809539c79bd6544d00e0607e98ff833ca21953df001510ca25915d1"}, + {file = "pymongo-4.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e5df28f74002e37bcbdfdc5109799f670e4dfef0fb527c391ff84f078050e7b5"}, + {file = "pymongo-4.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6b50040d9767197b77ed420ada29b3bf18a638f9552d80f2da817b7c4a4c9c68"}, + {file = "pymongo-4.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:417369ce39af2b7c2a9c7152c1ed2393edfd1cbaf2a356ba31eb8bcbd5c98dd7"}, + {file = "pymongo-4.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf821bd3befb993a6db17229a2c60c1550e957de02a6ff4dd0af9476637b2e4d"}, + {file = "pymongo-4.8.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9365166aa801c63dff1a3cb96e650be270da06e3464ab106727223123405510f"}, + {file = "pymongo-4.8.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc8b8582f4209c2459b04b049ac03c72c618e011d3caa5391ff86d1bda0cc486"}, + {file = "pymongo-4.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e5019f75f6827bb5354b6fef8dfc9d6c7446894a27346e03134d290eb9e758"}, + {file = "pymongo-4.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b5802151fc2b51cd45492c80ed22b441d20090fb76d1fd53cd7760b340ff554"}, + {file = "pymongo-4.8.0-cp311-cp311-win32.whl", hash = "sha256:4bf58e6825b93da63e499d1a58de7de563c31e575908d4e24876234ccb910eba"}, + {file = "pymongo-4.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:b747c0e257b9d3e6495a018309b9e0c93b7f0d65271d1d62e572747f4ffafc88"}, + {file = "pymongo-4.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e6a720a3d22b54183352dc65f08cd1547204d263e0651b213a0a2e577e838526"}, + {file = "pymongo-4.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:31e4d21201bdf15064cf47ce7b74722d3e1aea2597c6785882244a3bb58c7eab"}, + {file = "pymongo-4.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6b804bb4f2d9dc389cc9e827d579fa327272cdb0629a99bfe5b83cb3e269ebf"}, + {file = "pymongo-4.8.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f2fbdb87fe5075c8beb17a5c16348a1ea3c8b282a5cb72d173330be2fecf22f5"}, + {file = "pymongo-4.8.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd39455b7ee70aabee46f7399b32ab38b86b236c069ae559e22be6b46b2bbfc4"}, + {file = "pymongo-4.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:940d456774b17814bac5ea7fc28188c7a1338d4a233efbb6ba01de957bded2e8"}, + {file = "pymongo-4.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:236bbd7d0aef62e64caf4b24ca200f8c8670d1a6f5ea828c39eccdae423bc2b2"}, + {file = "pymongo-4.8.0-cp312-cp312-win32.whl", hash = "sha256:47ec8c3f0a7b2212dbc9be08d3bf17bc89abd211901093e3ef3f2adea7de7a69"}, + {file = "pymongo-4.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e84bc7707492f06fbc37a9f215374d2977d21b72e10a67f1b31893ec5a140ad8"}, + {file = "pymongo-4.8.0.tar.gz", hash = "sha256:454f2295875744dc70f1881e4b2eb99cdad008a33574bc8aaf120530f66c0cde"}, +] + [[package]] name = "pyppeteer" version = "0.0.25" @@ -1638,6 +1934,55 @@ files = [ {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, ] +[[package]] +name = "python-jose" +version = "3.3.0" +summary = "JOSE implementation in Python" +groups = ["default"] +dependencies = [ + "ecdsa!=0.15", + "pyasn1", + "rsa", +] +files = [ + {file = "python-jose-3.3.0.tar.gz", hash = "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a"}, + {file = "python_jose-3.3.0-py2.py3-none-any.whl", hash = "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a"}, +] + +[[package]] +name = "python-jose" +version = "3.3.0" +extras = ["cryptography"] +summary = "JOSE implementation in Python" +groups = ["default"] +dependencies = [ + "cryptography>=3.4.0", + "python-jose==3.3.0", +] +files = [ + {file = "python-jose-3.3.0.tar.gz", hash = "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a"}, + {file = "python_jose-3.3.0-py2.py3-none-any.whl", hash = "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a"}, +] + +[[package]] +name = "python-keycloak" +version = "4.2.0" +requires_python = "<4.0,>=3.8" +summary = "python-keycloak is a Python package providing access to the Keycloak API." +groups = ["default"] +dependencies = [ + "async-property>=0.2.2", + "deprecation>=2.1.0", + "httpx>=0.23.2", + "jwcrypto>=1.5.4", + "requests-toolbelt>=0.6.0", + "requests>=2.20.0", +] +files = [ + {file = "python_keycloak-4.2.0-py3-none-any.whl", hash = "sha256:95761a589687f9308db63eb3e390c2822714372fa05b36cb59bfb02686c81315"}, + {file = "python_keycloak-4.2.0.tar.gz", hash = "sha256:6eb4a9e0cf978b1fe8bacd47a8183e925dc72ccc26cac5b03a0d1a85972cc788"}, +] + [[package]] name = "python-multipart" version = "0.0.9" @@ -1729,6 +2074,20 @@ files = [ {file = "requests_html-0.10.0-py3-none-any.whl", hash = "sha256:cb8a78cf829c4eca9d6233f28524f65dd2bfaafb4bdbbc407f0a0b8f487df6e2"}, ] +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +summary = "A utility belt for advanced users of python-requests" +groups = ["default"] +dependencies = [ + "requests<3.0.0,>=2.0.1", +] +files = [ + {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, + {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, +] + [[package]] name = "rich" version = "13.7.1" @@ -1744,6 +2103,20 @@ files = [ {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, ] +[[package]] +name = "rsa" +version = "4.9" +requires_python = ">=3.6,<4" +summary = "Pure-Python RSA implementation" +groups = ["default"] +dependencies = [ + "pyasn1>=0.1.3", +] +files = [ + {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, + {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, +] + [[package]] name = "s3transfer" version = "0.10.2" @@ -2078,7 +2451,7 @@ files = [ [[package]] name = "uvicorn" -version = "0.29.0" +version = "0.30.1" requires_python = ">=3.8" summary = "The lightning-fast ASGI server." groups = ["default"] @@ -2088,13 +2461,13 @@ dependencies = [ "typing-extensions>=4.0; python_version < \"3.11\"", ] files = [ - {file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"}, - {file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"}, + {file = "uvicorn-0.30.1-py3-none-any.whl", hash = "sha256:cd17daa7f3b9d7a24de3617820e634d0933b69eed8e33a516071174427238c81"}, + {file = "uvicorn-0.30.1.tar.gz", hash = "sha256:d46cd8e0fd80240baffbcd9ec1012a712938754afcf81bce56c024c1656aece8"}, ] [[package]] name = "uvicorn" -version = "0.29.0" +version = "0.30.1" extras = ["standard"] requires_python = ">=3.8" summary = "The lightning-fast ASGI server." @@ -2104,14 +2477,14 @@ dependencies = [ "httptools>=0.5.0", "python-dotenv>=0.13", "pyyaml>=5.1", - "uvicorn==0.29.0", + "uvicorn==0.30.1", "uvloop!=0.15.0,!=0.15.1,>=0.14.0; (sys_platform != \"cygwin\" and sys_platform != \"win32\") and platform_python_implementation != \"PyPy\"", "watchfiles>=0.13", "websockets>=10.4", ] files = [ - {file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"}, - {file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"}, + {file = "uvicorn-0.30.1-py3-none-any.whl", hash = "sha256:cd17daa7f3b9d7a24de3617820e634d0933b69eed8e33a516071174427238c81"}, + {file = "uvicorn-0.30.1.tar.gz", hash = "sha256:d46cd8e0fd80240baffbcd9ec1012a712938754afcf81bce56c024c1656aece8"}, ] [[package]] diff --git a/pyproject.toml b/pyproject.toml index 1270742..f390e7b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ authors = [ {name = "Jayden Pyles", email = "jpylesbuisness@gmail.com"}, ] dependencies = [ - "uvicorn>=0.29.0", + "uvicorn>=0.30.1", "fastapi>=0.111.0", "boto3>=1.34.140", "python-dotenv>=1.0.1", @@ -24,6 +24,12 @@ dependencies = [ "pandas>=2.2.2", "openpyxl>=3.1.5", "xlsxwriter>=3.2.0", + "python-keycloak>=4.2.0", + "fastapi-keycloak>=1.0.11", + "pymongo>=4.8.0", + "motor[asyncio]>=3.5.0", + "python-jose[cryptography]>=3.3.0", + "passlib[bcrypt]>=1.7.4", ] requires-python = ">=3.10" readme = "README.md" @@ -47,6 +53,7 @@ reportUnknownMemberType= false reportMissingImports = true reportMissingTypeStubs = false reportAny = false +reportCallInDefaultInitializer = false pythonVersion = "3.9" pythonPlatform = "Linux" diff --git a/src/components/NavDrawer.tsx b/src/components/NavDrawer.tsx index 66c1911..5b73795 100644 --- a/src/components/NavDrawer.tsx +++ b/src/components/NavDrawer.tsx @@ -1,6 +1,5 @@ import React, { useState } from "react"; -import { useAuth } from "../useAuth"; -import { LogoutOptions, RedirectLoginOptions } from "@auth0/auth0-react"; +import { useAuth } from "../hooks/useAuth"; import { Box, Drawer, @@ -22,20 +21,10 @@ import { useRouter } from "next/router"; const NavDrawer: React.FC = () => { const router = useRouter(); - const { loginWithRedirect, logout, user, isAuthenticated } = useAuth(); + const { login, logout, user, isAuthenticated } = useAuth(); const [open, setOpen] = useState(false); - const handleLogout = () => { - const logoutOptions: LogoutOptions = {}; - logout(logoutOptions); - }; - - const handleLogin = () => { - const loginOptions: RedirectLoginOptions = { - authorizationParams: { redirect_uri: "http://localhost" }, - }; - loginWithRedirect(loginOptions); - }; + console.log(user); const toggleDrawer = (open: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => { @@ -80,7 +69,7 @@ const NavDrawer: React.FC = () => { return ( <> - + { {isAuthenticated ? ( - <> +
- Welcome, {user?.name} + Welcome, {user?.full_name} - - +
) : ( - )} diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts new file mode 100644 index 0000000..ae74cbd --- /dev/null +++ b/src/hooks/useAuth.ts @@ -0,0 +1,52 @@ +import { useState, useEffect } from "react"; +import axios from "axios"; + +type User = { + full_name: string; + email: string; + disabled: null | boolean; +}; + +export const useAuth = () => { + const [isAuthenticated, setIsAuthenticated] = useState(false); + const [user, setUser] = useState(null); + + useEffect(() => { + const fetchUserInfo = async () => { + try { + const response = await axios.get("/api/auth/users/me", { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); + setUser(response.data); + setIsAuthenticated(true); + } catch (error) { + setIsAuthenticated(false); + setUser(null); + } + }; + + const token = localStorage.getItem("token"); + if (token) { + fetchUserInfo(); + } + }, []); + + const login = () => { + window.location.href = "http://localhost:8000/api/auth/login"; + }; + + const logout = () => { + localStorage.removeItem("token"); + setIsAuthenticated(false); + setUser(null); + }; + + return { + isAuthenticated, + user, + login, + logout, + }; +}; diff --git a/src/lib/auth0.ts b/src/lib/auth0.ts deleted file mode 100644 index 1788672..0000000 --- a/src/lib/auth0.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { initAuth0 } from "@auth0/nextjs-auth0"; - -const secret = process.env.AUTH0_SECRET; - -if (!secret) { - throw new Error("Secret not found"); -} - -export default initAuth0({ - secret: process.env.AUTH0_SECRET as string, - baseURL: process.env.AUTH0_BASE_URL as string, - issuerBaseURL: process.env.AUTH0_ISSUER_BASE_URL as string, - clientID: process.env.AUTH0_CLIENT_ID as string, - clientSecret: process.env.AUTH0_CLIENT_SECRET as string, - routes: { - callback: "/auth/callback", - postLogoutRedirect: "/", - }, -}); diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 77b6ad9..e4c518f 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -4,32 +4,19 @@ import "../styles/globals.css"; import React from "react"; import type { AppProps } from "next/app"; import Head from "next/head"; -import { Auth0Provider } from "@auth0/auth0-react"; +import { SessionProvider } from "next-auth/react"; import NavDrawer from "../components/NavDrawer"; -const domain = process.env.NEXT_PUBLIC_AUTH0_ISSUER_BASE_URL || ""; -const clientId = process.env.NEXT_PUBLIC_AUTH0_CLIENT_ID || ""; - -console.log(domain); - const App: React.FC = ({ Component, pageProps }) => { return ( <> Webapp Template - - + + - + ); }; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index a999c08..a63c18f 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useRef } from "react"; import { Typography, TextField, @@ -10,9 +10,11 @@ import { TableRow, Container, Box, + IconButton, + Tooltip, } from "@mui/material"; import AddIcon from "@mui/icons-material/Add"; -import { useAuth0 } from "@auth0/auth0-react"; +import { useAuth } from "../hooks/useAuth"; import { useRouter } from "next/router"; interface Element { @@ -31,13 +33,24 @@ type Result = { [key: string]: ScrapeResult[]; }; +function validateURL(url: string): boolean { + try { + new URL(url); + return true; + } catch (_) { + return false; + } +} + const Home = () => { - const { user } = useAuth0(); + const { user } = useAuth(); const router = useRouter(); const { elements, url } = router.query; - const [submittedURL, setUrl] = useState(""); + const [submittedURL, setSubmittedURL] = useState(""); + const [isValidURL, setIsValidUrl] = useState(true); + const [urlError, setUrlError] = useState(null); const [rows, setRows] = useState([]); const [results, setResults] = useState(null); const [newRow, setNewRow] = useState({ @@ -46,29 +59,54 @@ const Home = () => { url: "", }); + const resultsRef = useRef(null); + useEffect(() => { if (elements) { setRows(JSON.parse(elements as string)); } if (url) { - setUrl(url as string); + setSubmittedURL(url as string); } }, [elements, url]); + useEffect(() => { + if (results && resultsRef.current) { + resultsRef.current.scrollIntoView({ behavior: "smooth" }); + } + }, [results]); + const handleAddRow = () => { - newRow.url = submittedURL; - setRows([...rows, newRow]); + const updatedRow = { ...newRow, url: submittedURL }; + setRows([...rows, updatedRow]); setNewRow({ name: "", xpath: "", url: "" }); }; + const handleDeleteRow = (elementName: string) => { + setRows( + rows.filter((r) => { + return elementName !== r.name; + }), + ); + }; + const handleSubmit = () => { + if (!validateURL(submittedURL)) { + setIsValidUrl(false); + setUrlError("Please enter a valid URL."); + return; + } + + setIsValidUrl(true); + setUrlError(null); + fetch("/api/submit-scrape-job", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ - url: url, + url: submittedURL, elements: rows, - user: user?.name, + user: user?.email, time_created: new Date().toISOString(), }), }) @@ -77,25 +115,43 @@ const Home = () => { }; return ( - <> + - + Web Scraper -
+
setUrl(e.target.value)} - style={{ marginBottom: "10px" }} + value={submittedURL} + onChange={(e) => setSubmittedURL(e.target.value)} + error={!isValidURL} + helperText={!isValidURL ? urlError : ""} /> -
- + { value={newRow.xpath} onChange={(e) => setNewRow({ ...newRow, xpath: e.target.value })} /> - + + 0 && newRow.name.length > 0)} + > + + + + Elements - +
Name @@ -136,36 +204,44 @@ const Home = () => { + + + ))}
{results && ( - - - - Name - XPath - Text - - - - {Object.keys(results).map((key, index) => ( - - {results[key].map((result, resultIndex) => ( - - {result.name} - {result.xpath} - {result.text} - - ))} - - ))} - -
+ <> + Results + + + + Name + XPath + Text + + + + {Object.keys(results).map((key, index) => ( + + {results[key].map((result, resultIndex) => ( + + {result.name} + {result.xpath} + {result.text} + + ))} + + ))} + +
+ )} - +
); }; diff --git a/src/pages/jobs.tsx b/src/pages/jobs.tsx index 315c5d4..6eab227 100644 --- a/src/pages/jobs.tsx +++ b/src/pages/jobs.tsx @@ -1,26 +1,24 @@ -import { useAuth0 } from "@auth0/auth0-react"; import React, { useEffect, useState } from "react"; import JobTable from "../components/JobTable"; +import { useAuth } from "../hooks/useAuth"; const Jobs = () => { - const { user } = useAuth0(); + const { user } = useAuth(); const [jobs, setJobs] = useState([]); useEffect(() => { - fetch("/api/retrieve-scrape-jobs", { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({ user: user?.name }), - }) - .then((response) => response.json()) - .then((data) => setJobs(data)); - }, []); + if (user) { + fetch("/api/retrieve-scrape-jobs", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ user: user?.email }), + }) + .then((response) => response.json()) + .then((data) => setJobs(data)); + } + }, [user]); - return ( - <> - - - ); + return ; }; export default Jobs; diff --git a/src/pages/login.tsx b/src/pages/login.tsx new file mode 100644 index 0000000..aaf6312 --- /dev/null +++ b/src/pages/login.tsx @@ -0,0 +1,122 @@ +import React, { useState } from "react"; +import axios from "axios"; +import { Button, TextField, Typography, Container, Box } from "@mui/material"; + +type Mode = "login" | "signup"; + +const AuthForm: React.FC = () => { + const [mode, setMode] = useState("login"); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [fullName, setFullName] = useState(""); + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + try { + if (mode === "login") { + const params = new URLSearchParams(); + params.append("username", email); + params.append("password", password); + const response = await axios.post( + "http://localhost:8000/api/auth/token", + params, + ); + localStorage.setItem("token", response.data.access_token); + alert("Login successful"); + } else { + await axios.post("http://localhost:8000/api/auth/signup", { + email: email, + password: password, + full_name: fullName, + }); + alert("Signup successful"); + } + } catch (error) { + console.error(error); + alert(`${mode.charAt(0).toUpperCase() + mode.slice(1)} failed`); + } + }; + + const toggleMode = () => { + setMode((prevMode) => (prevMode === "login" ? "signup" : "login")); + }; + + return ( + + + + {mode.charAt(0).toUpperCase() + mode.slice(1)} + + + setEmail(e.target.value)} + /> + setPassword(e.target.value)} + /> + {mode === "signup" && ( + setFullName(e.target.value)} + /> + )} + + + + + + ); +}; + +export default AuthForm; diff --git a/src/useAuth.ts b/src/useAuth.ts deleted file mode 100644 index 7053bda..0000000 --- a/src/useAuth.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { useAuth0 } from "@auth0/auth0-react"; - -export const useAuth = () => { - const { loginWithRedirect, logout, user, isAuthenticated, isLoading } = - useAuth0(); - return { - loginWithRedirect, - logout, - user, - isAuthenticated, - isLoading, - }; -}; diff --git a/tsconfig.json b/tsconfig.json index b388191..a2e2ef5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -98,11 +98,7 @@ /* Completeness */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */, - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], + "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "noEmit": true, "incremental": true, @@ -110,11 +106,6 @@ "resolveJsonModule": true, "isolatedModules": true }, - "include": [ - "src", - "src/declaration.d.ts" - ], - "exclude": [ - "node_modules" - ] + "include": ["src", "src/declaration.d.ts", "src/next-auth.d.ts"], + "exclude": ["node_modules"] }