diff --git a/.env b/.env index 819e2e1..7861a9d 100644 --- a/.env +++ b/.env @@ -2,7 +2,7 @@ SECRET_KEY=SUPER_SECRET_KEY ALGORITHM=HS256 ACCESS_TOKEN_EXPIRE_MINUTES=30 - +REFRESH_TOKEN_EXPIRE_MINUTES=6000 # CORS-middleware # ALLOW_ORIGINS=*, # "*" — разрешить всем; можно указать список конкретных доменов # ALLOW_CREDENTIALS=True, diff --git a/server/backend/JWT.py b/server/backend/JWT.py index 9d7f107..0517469 100644 --- a/server/backend/JWT.py +++ b/server/backend/JWT.py @@ -3,21 +3,47 @@ from jose import JWTError, jwt from fastapi import HTTPException, Depends, status from fastapi.security import OAuth2PasswordBearer +from server.database import db + from dotenv import load_dotenv #Работа с env для jwt import os load_dotenv() SECRET_KEY = os.getenv('SECRET_KEY') ALGORITHM = os.getenv('ALGORITHM') ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv('ACCESS_TOKEN_EXPIRE_MINUTES')) - +REFRESH_TOKEN_EXPIRE_MINUTES = int(os.getenv('REFRESH_TOKEN_EXPIRE_MINUTES')) oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login") #Создание jwt -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 +class Token(): + @staticmethod + async def create_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 +class AccessToken(Token): + @staticmethod + async def create(data:dict, expires_delta: timedelta | None = None): + return await Token.create_token(data, expires_delta) +class RefreshToken(Token): + @staticmethod + async def create(data:dict, expires_delta: timedelta | None = None): + token_str = await Token.create_token(data, expires_delta) + await db.refresh_token(encoded_jwt = token_str,email=data["sub"]) + return token_str +# 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 create_refresh_token(data:dict, expires_delta:timedelta | None = None): +# to_encode = data.copy() +# expire = datetime.utcnow() + (expires_delta or timedelta(minutes=6000)) +# 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)): #Проверка jwt try: diff --git a/server/backend/endpoints.py b/server/backend/endpoints.py index 8de5f74..86d1eee 100644 --- a/server/backend/endpoints.py +++ b/server/backend/endpoints.py @@ -87,8 +87,16 @@ async def login_user(row: pydentic.UserLogin): if not user: raise HTTPException(status_code=401, detail="The user isn't found") - token = await JWT.create_access_token( + access_token = await JWT.AccessToken.create( {"sub": user.email}, timedelta(minutes=JWT.ACCESS_TOKEN_EXPIRE_MINUTES) ) - return {"access_token": token, "token_type": "bearer"} \ No newline at end of file + refresh_token = await JWT.RefreshToken.create( + {"sub": user.email}, + timedelta(minutes=JWT.REFRESH_TOKEN_EXPIRE_MINUTES) + ) + return { + "access_token": access_token, + "refresh_token": refresh_token, + "token_type": "bearer" + } \ No newline at end of file diff --git a/server/database/db.py b/server/database/db.py index 4eb4801..95a59fe 100644 --- a/server/database/db.py +++ b/server/database/db.py @@ -33,6 +33,7 @@ class User(Base): description = Column(String, nullable=False) activated = Column(Boolean, default=False) password = Column(String, nullable=False) + refresh_token = Column(String, nullable=True) async def init_db(): async with async_engine.begin() as conn: @@ -77,6 +78,13 @@ async def LoginUser(user_info): if user and verify_password(user_info.password, user.password): return user return None +async def refresh_token(encoded_jwt, email): + async with AsyncSessionLocal() as session: + result = await session.execute(select(User).where(User.email==email)) + user = result.scalar_one_or_none() + if user: + user.refresh_token = encoded_jwt + await session.commit() async def main(): await init_db() #await CreateUser() diff --git a/server/front/login/js.js b/server/front/login/js.js index c5ef518..a21dc19 100644 --- a/server/front/login/js.js +++ b/server/front/login/js.js @@ -1,3 +1,6 @@ +//Разобраться с хранением и использованием refresh token +//Добавить endpoint logout + const token = localStorage.getItem("token"); if (token) { window.location.href = "./../main/index.html"; @@ -21,11 +24,16 @@ document.getElementById('loginForm').addEventListener('submit', async function ( }); const data = await response.json(); // читаем только один раз - - if (response.ok) { - localStorage.setItem("token", data.access_token); // сохраняем только при успехе + const remember_me = document.getElementById("remember").checked + if (response.ok) { // сохраняем только при успехе + if (remember_me) { // если нажат чекбокс, то сохраняем в localstorage со стандартным временем действия + localStorage.setItem("token", data.access_token); + } else { // если не нажат, то в sessionstorage до перезахода в браузер(?) + sessionStorage.setItem("token", data.access_token); + } + window.location.href = './../main/index.html'; - } else { + } else { //парсинг и вывод ошибок, если есть if (Array.isArray(data.detail)) { const messages = data.detail.map(e => { const field = e.loc.filter(locPart => locPart !== 'body').join(' -> '); @@ -42,7 +50,7 @@ document.getElementById('loginForm').addEventListener('submit', async function ( showError(['Connection error: ' + err.message]); } }); -function showError(messages){ +function showError(messages){ //Добавление их на form со стилями let errorElem = document.getElementById('formError'); let container = document.getElementById('glass-container'); if (!errorElem){ diff --git a/server/front/main/index.html b/server/front/main/index.html index 595914b..cea9322 100644 --- a/server/front/main/index.html +++ b/server/front/main/index.html @@ -9,6 +9,9 @@