diff --git a/.gitignore b/.gitignore index 7dd3667..a17b01d 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,5 @@ Thumbs.db hint.py #env -*.env \ No newline at end of file +*.env +db.py \ No newline at end of file diff --git a/requirements b/requirements index 8f0ad04..fa64fe4 100644 --- a/requirements +++ b/requirements @@ -4,4 +4,5 @@ pytest == 8.4.1 aiosqlite == 0.21.0 greenlet == 3.2.4 passlib == 1.7.4 -bcrypt == 4.3.0 \ No newline at end of file +bcrypt == 4.3.0 +python-jose[cryptography] == 3.5.0 \ No newline at end of file diff --git a/server/backend/JWT.py b/server/backend/JWT.py new file mode 100644 index 0000000..88f2fa5 --- /dev/null +++ b/server/backend/JWT.py @@ -0,0 +1,27 @@ +from datetime import datetime, timedelta +from jose import JWTError, jwt +from fastapi import HTTPException, Depends, status +from fastapi.security import OAuth2PasswordBearer + +SECRET_KEY = "super-secret-string" +ALGORITHM = "HS256" +ACCESS_TOKEN_EXPIRE_MINUTES = 30 + +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login") + +async def create_access_token(data: dict, expires_delta: timedelta | None = None): + to_encode = data.copy() + expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15)) + to_encode.update({"exp": expire}) + encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) + return encoded_jwt + +async def current_user(token: str = Depends(oauth2_scheme)): + try: + payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) + email: str = payload.get("sub") + if email is None: + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token") + return email + except JWTError: + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token") \ No newline at end of file diff --git a/server/backend/endpoints.py b/server/backend/endpoints.py index 68f7f15..3a98b4c 100644 --- a/server/backend/endpoints.py +++ b/server/backend/endpoints.py @@ -1,7 +1,10 @@ -from fastapi import FastAPI, HTTPException +from fastapi import FastAPI, HTTPException, status, Depends from fastapi.middleware.cors import CORSMiddleware -from . import pydentic +from . import pydentic, JWT +from datetime import datetime, timedelta +from pydantic import EmailStr from server.database import db + import asyncio api = FastAPI() @@ -14,6 +17,10 @@ api.add_middleware( allow_headers=["*"], # Разрешить любые заголовки ) +@api.get("/protected") +async def protected(current_user: str = Depends(JWT.current_user)): + return {"msg": f"Hello, {current_user}"} + @api.get("/", response_model=pydentic.IdofPersons) async def get_all_rows(): for row in await db.get_all_rows(): @@ -21,8 +28,8 @@ async def get_all_rows(): return row else: raise HTTPException(status_code=404, detail="The user isn't found") -@api.get("/get_user/{id}", response_model=pydentic.IdofPersons) -async def get_user(id:int): +@api.get("/get_user_by_id/{id}", response_model=pydentic.IdofPersons) +async def get_user(id: int, current_user: str = Depends(JWT.current_user)): user = await db.GetUser(id) if user: return user @@ -63,4 +70,15 @@ async def update_user(id: int, updated_row: pydentic.UserUpdate): await db.UpdateUser(user) else: pass - return user \ No newline at end of file + return user +@api.post("/login") +async def login_user(row: pydentic.UserLogin): + user = await db.LoginUser(row) + if not user: + raise HTTPException(status_code=401, detail="The user isn't found") + + token = await JWT.create_access_token( + {"sub": user.email}, + timedelta(minutes=JWT.ACCESS_TOKEN_EXPIRE_MINUTES) + ) + return {"access_token": token, "token_type": "bearer"} \ No newline at end of file diff --git a/server/backend/pydentic.py b/server/backend/pydentic.py index 466825a..ddacc84 100644 --- a/server/backend/pydentic.py +++ b/server/backend/pydentic.py @@ -34,4 +34,7 @@ class UserUpdate(BaseModel): password:Optional[constr(min_length=8)] = Field(None, description="Password with min 8 chars, letters and digits") @validator('password') def password_validator(cls, password): - return check_password_complexity(cls, password) \ No newline at end of file + return check_password_complexity(cls, password) +class UserLogin(BaseModel): + email:EmailStr = Field(..., min_length=6, max_length=254, description="user's email") + password:str = Field(..., description="Password") \ No newline at end of file diff --git a/server/database/DB/example.db b/server/database/DB/example.db index 61b2ec3..2a6781b 100644 Binary files a/server/database/DB/example.db and b/server/database/DB/example.db differ diff --git a/server/database/db.py b/server/database/db.py index b67c8ab..afe0e1d 100644 --- a/server/database/db.py +++ b/server/database/db.py @@ -71,6 +71,13 @@ async def DeleteUser(id): if user: await session.delete(user) await session.commit() +async def LoginUser(user_info): + async with AsyncSessionLocal() as session: + result = await session.execute(select(User).where(User.email == user_info.email)) + user = result.scalar_one_or_none() + if user and verify_password(user_info.password, user.password): + return user + return None async def main(): await init_db() await CreateUser() diff --git a/server/front/login/index.html b/server/front/login/index.html index 5f76402..f202c7c 100644 --- a/server/front/login/index.html +++ b/server/front/login/index.html @@ -10,8 +10,8 @@