mirror of
https://github.com/joaovitoriasilva/endurain.git
synced 2026-01-09 15:57:59 -05:00
Started change of login secrets handling
[backend] Created new alembic migration to remove unused photo_path_aux column from users table. This new migration also changes default admin password to new hashing method [backend] Moved password hashing on login to backend [backend] Added env variable for expire token in minutes [backend] Rename file schema_session/schema_access_token to dependencies_security [backend] Started changing session dependencies functions to handle token in the request cookie [frontend] Started updating api calls logic [frontend] Moved login store logic to pinea store instead of local storage [README] Updated README with new env variable for backend
This commit is contained in:
@@ -32,6 +32,7 @@ ENV DB_DATABASE="endurain"
|
|||||||
ENV SECRET_KEY="changeme"
|
ENV SECRET_KEY="changeme"
|
||||||
ENV ALGORITHM="HS256"
|
ENV ALGORITHM="HS256"
|
||||||
ENV ACCESS_TOKEN_EXPIRE_MINUTES=30
|
ENV ACCESS_TOKEN_EXPIRE_MINUTES=30
|
||||||
|
ENV REFRESH_TOKEN_EXPIRE_DAYS=7
|
||||||
ENV STRAVA_CLIENT_ID="changeme"
|
ENV STRAVA_CLIENT_ID="changeme"
|
||||||
ENV STRAVA_CLIENT_SECRET="changeme"
|
ENV STRAVA_CLIENT_SECRET="changeme"
|
||||||
ENV STRAVA_AUTH_CODE="changeme"
|
ENV STRAVA_AUTH_CODE="changeme"
|
||||||
|
|||||||
@@ -88,8 +88,9 @@ DB_USER | endurain | Yes | N/A
|
|||||||
DB_PASSWORD | changeme | `No` | N/A
|
DB_PASSWORD | changeme | `No` | N/A
|
||||||
DB_DATABASE | endurain | Yes | N/A
|
DB_DATABASE | endurain | Yes | N/A
|
||||||
SECRET_KEY | changeme | `No` | N/A
|
SECRET_KEY | changeme | `No` | N/A
|
||||||
ALGORITHM | HS256 | Yes | N/A
|
ALGORITHM | HS256 | Yes | Currently only HS256 is supported
|
||||||
ACCESS_TOKEN_EXPIRE_MINUTES | 30 | Yes | N/A
|
ACCESS_TOKEN_EXPIRE_MINUTES | 15 | Yes | N/A
|
||||||
|
REFRESH_TOKEN_EXPIRE_DAYS | 7 | Yes | N/A
|
||||||
STRAVA_CLIENT_ID | changeme | `No` | N/A
|
STRAVA_CLIENT_ID | changeme | `No` | N/A
|
||||||
STRAVA_CLIENT_SECRET | changeme | `No` | N/A
|
STRAVA_CLIENT_SECRET | changeme | `No` | N/A
|
||||||
STRAVA_AUTH_CODE | changeme | `No` | N/A
|
STRAVA_AUTH_CODE | changeme | `No` | N/A
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
"""Remove photo_path_aux column from users table
|
||||||
|
|
||||||
|
Revision ID: ab815ee3beae
|
||||||
|
Revises: 0ab200a7f196
|
||||||
|
Create Date: 2024-06-07 21:40:28.789274
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import mysql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = 'ab815ee3beae'
|
||||||
|
down_revision: Union[str, None] = '0ab200a7f196'
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('users', 'photo_path_aux')
|
||||||
|
|
||||||
|
# Update admin password
|
||||||
|
new_password = '$2b$12$.b.fWl/Bu/sIx/6ZtIgJPuXaqfqkIi8NwnxeP6SQiQPZB3kKxD5tm' # replace this with the actual new password
|
||||||
|
op.execute(
|
||||||
|
sa.text(
|
||||||
|
"""
|
||||||
|
UPDATE users
|
||||||
|
SET password = :new_password
|
||||||
|
WHERE username = 'admin'
|
||||||
|
"""
|
||||||
|
).bindparams(new_password=new_password)
|
||||||
|
)
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('users', sa.Column('photo_path_aux', mysql.VARCHAR(length=250), nullable=True, comment='Auxiliary photo path'))
|
||||||
|
|
||||||
|
old_password = '8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918' # replace this with the actual old password
|
||||||
|
op.execute(
|
||||||
|
sa.text(
|
||||||
|
"""
|
||||||
|
UPDATE users
|
||||||
|
SET password = :old_password
|
||||||
|
WHERE username = 'admin'
|
||||||
|
"""
|
||||||
|
).bindparams(old_password=old_password)
|
||||||
|
)
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -5,7 +5,8 @@ API_VERSION = "v0.2.1"
|
|||||||
|
|
||||||
# JWT Token constants
|
# JWT Token constants
|
||||||
JWT_ALGORITHM = os.environ.get("ALGORITHM")
|
JWT_ALGORITHM = os.environ.get("ALGORITHM")
|
||||||
JWT_EXPIRATION_IN_MINUTES = int(os.environ.get("ACCESS_TOKEN_EXPIRE_MINUTES"))
|
JWT_ACCESS_TOKEN_EXPIRE_MINUTES = int(os.environ.get("ACCESS_TOKEN_EXPIRE_MINUTES"))
|
||||||
|
JWT_REFRESH_TOKEN_EXPIRE_DAYS = int(os.environ.get("REFRESH_TOKEN_EXPIRE_DAYS"))
|
||||||
JWT_SECRET_KEY = os.environ.get("SECRET_KEY")
|
JWT_SECRET_KEY = os.environ.get("SECRET_KEY")
|
||||||
|
|
||||||
# Scopes definition
|
# Scopes definition
|
||||||
|
|||||||
@@ -34,13 +34,13 @@ def format_user_birthdate(user):
|
|||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
def authenticate_user(username: str, password: str, db: Session):
|
def authenticate_user(username: str, db: Session):
|
||||||
try:
|
try:
|
||||||
# Get the user from the database
|
# Get the user from the database
|
||||||
user = (
|
user = (
|
||||||
db.query(models.User)
|
db.query(models.User)
|
||||||
.filter(
|
.filter(
|
||||||
models.User.username == username, models.User.password == password
|
models.User.username == username
|
||||||
)
|
)
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
|
|||||||
136
backend/dependencies/dependencies_security.py
Normal file
136
backend/dependencies/dependencies_security.py
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from fastapi import Depends, HTTPException, status
|
||||||
|
from fastapi.security import OAuth2PasswordBearer
|
||||||
|
|
||||||
|
# import the jwt module from the joserfc package
|
||||||
|
from joserfc import jwt
|
||||||
|
from joserfc.jwk import OctKey
|
||||||
|
|
||||||
|
from constants import (
|
||||||
|
JWT_ALGORITHM,
|
||||||
|
JWT_SECRET_KEY,
|
||||||
|
ADMIN_ACCESS,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Define the OAuth2 scheme for handling bearer tokens
|
||||||
|
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
||||||
|
|
||||||
|
# Define a loggger created on main.py
|
||||||
|
logger = logging.getLogger("myLogger")
|
||||||
|
|
||||||
|
|
||||||
|
def decode_token(token: Annotated[str, Depends(oauth2_scheme)]):
|
||||||
|
try:
|
||||||
|
# Decode the token and return the payload
|
||||||
|
return jwt.decode(token, OctKey.import_key(JWT_SECRET_KEY))
|
||||||
|
except Exception:
|
||||||
|
# Log the error and raise the exception
|
||||||
|
logger.info("Unable to decode token | Returning 401 response")
|
||||||
|
|
||||||
|
# Raise an HTTPException with a 401 Unauthorized status code
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Unable to decode token",
|
||||||
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_token_expiration(token: Annotated[str, Depends(oauth2_scheme)]):
|
||||||
|
# Try to decode the token and check if it is expired
|
||||||
|
try:
|
||||||
|
# Decode the token
|
||||||
|
# Mark exp claim as required
|
||||||
|
claims_requests = jwt.JWTClaimsRegistry(exp={"essential": True})
|
||||||
|
|
||||||
|
# decodes the token
|
||||||
|
payload = decode_token(token)
|
||||||
|
|
||||||
|
# Validate token exp
|
||||||
|
claims_requests.validate(payload.claims)
|
||||||
|
except Exception:
|
||||||
|
# Log the error and raise the exception
|
||||||
|
logger.info("Token expired during validation | Returning 401 response")
|
||||||
|
|
||||||
|
# Raise an HTTPException with a 401 Unauthorized status code
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Token no longer valid",
|
||||||
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_token_scopes(token: Annotated[str, Depends(oauth2_scheme)]):
|
||||||
|
# Decode the token
|
||||||
|
payload = decode_token(token)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Get the scopes from the payload and return it
|
||||||
|
return payload.claims["scopes"]
|
||||||
|
except Exception:
|
||||||
|
# Log the error and raise the exception
|
||||||
|
logger.info("Scopes not present in token | Returning 401 response")
|
||||||
|
|
||||||
|
# Raise an HTTPException with a 401 Unauthorized status code
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Scopes not present in token",
|
||||||
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_token_user_id(token: Annotated[str, Depends(oauth2_scheme)]):
|
||||||
|
# Decode the token
|
||||||
|
payload = decode_token(token)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Get the user id from the payload and return it
|
||||||
|
return payload.claims["id"]
|
||||||
|
except Exception:
|
||||||
|
# Log the error and raise the exception
|
||||||
|
logger.info("Claim with user ID not present in token | Returning 401 response")
|
||||||
|
|
||||||
|
# Raise an HTTPException with a 401 Unauthorized status code
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Claim with user ID not present in token",
|
||||||
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_token_access_type(token: Annotated[str, Depends(oauth2_scheme)]):
|
||||||
|
# Decode the token
|
||||||
|
payload = decode_token(token)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Get the user access_type from the payload and return it
|
||||||
|
return payload.claims["access_type"]
|
||||||
|
except Exception:
|
||||||
|
# Log the error and raise the exception
|
||||||
|
logger.info(
|
||||||
|
"Claim with user access Type not present in token | Returning 401 response"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Raise an HTTPException with a 401 Unauthorized status code
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Claim with user access Type not present in token",
|
||||||
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_token_admin_access(token: Annotated[str, Depends(oauth2_scheme)]):
|
||||||
|
if get_token_access_type(token) != ADMIN_ACCESS:
|
||||||
|
# Raise an HTTPException with a 403 Forbidden status code
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="Unauthorized Access - Admin Access Required",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_token(data: dict):
|
||||||
|
# Encode the data and return the token
|
||||||
|
return jwt.encode({"alg": JWT_ALGORITHM}, data.copy(), JWT_SECRET_KEY)
|
||||||
@@ -1,36 +1,99 @@
|
|||||||
from fastapi import Depends, HTTPException, status
|
from fastapi import Depends, HTTPException, status, Request, Security
|
||||||
from fastapi.security import OAuth2PasswordBearer
|
from fastapi.security import OAuth2PasswordBearer, SecurityScopes
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from dependencies import dependencies_database
|
from typing import Annotated
|
||||||
from schemas import schema_access_tokens, schema_users
|
|
||||||
|
from dependencies import dependencies_database, dependencies_security
|
||||||
|
from schemas import schema_users
|
||||||
|
|
||||||
# Define the OAuth2 scheme for handling bearer tokens
|
# Define the OAuth2 scheme for handling bearer tokens
|
||||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
||||||
|
|
||||||
def validate_token(token: str = Depends(oauth2_scheme), db: Session = Depends(dependencies_database.get_db)):
|
|
||||||
|
### Dependencies for access token validation
|
||||||
|
|
||||||
|
def validate_access_token(request: Request):
|
||||||
|
# Extract the access token from the cookies
|
||||||
|
access_token = request.cookies.get("endurain_access_token")
|
||||||
|
if not access_token:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="Access token missing",
|
||||||
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
)
|
||||||
|
|
||||||
# Validate the token expiration
|
# Validate the token expiration
|
||||||
schema_access_tokens.validate_token_expiration(db, token)
|
dependencies_security.validate_token_expiration(access_token)
|
||||||
|
|
||||||
|
|
||||||
def validate_token_and_get_authenticated_user_id(
|
def validate_token_and_return_access_token(request: Request):
|
||||||
token: str = Depends(oauth2_scheme), db: Session = Depends(dependencies_database.get_db)
|
# Extract the access token from the cookies
|
||||||
|
access_token = request.cookies.get("endurain_access_token")
|
||||||
|
if not access_token:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="Access token missing",
|
||||||
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Validate the token expiration
|
||||||
|
dependencies_security.validate_token_expiration(access_token)
|
||||||
|
|
||||||
|
# Return token
|
||||||
|
return access_token
|
||||||
|
|
||||||
|
|
||||||
|
def validate_access_token_and_get_authenticated_user_id(
|
||||||
|
access_token: Annotated[str, Depends(validate_token_and_return_access_token)],
|
||||||
):
|
):
|
||||||
# Validate the token expiration
|
|
||||||
schema_access_tokens.validate_token_expiration(db, token)
|
|
||||||
|
|
||||||
# Return the user ID associated with the token
|
# Return the user ID associated with the token
|
||||||
return schema_access_tokens.get_token_user_id(token)
|
return dependencies_security.get_token_user_id(access_token)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_access_token_and_validate_admin_access(
|
||||||
|
access_token: Annotated[str, Depends(validate_token_and_return_access_token)],
|
||||||
|
):
|
||||||
|
# Check if the token has admin access
|
||||||
|
dependencies_security.validate_token_admin_access(access_token)
|
||||||
|
|
||||||
|
|
||||||
|
### Dependencies for refresh token validation
|
||||||
|
|
||||||
|
|
||||||
|
def validate_token_and_return_refresh_token(request: Request):
|
||||||
|
# Extract the refresh token from the cookies
|
||||||
|
refresh_token = request.cookies.get("endurain_refresh_token")
|
||||||
|
if not refresh_token:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="Refresh token missing",
|
||||||
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Validate the token expiration
|
||||||
|
dependencies_security.validate_token_expiration(refresh_token)
|
||||||
|
|
||||||
|
# Return token
|
||||||
|
return refresh_token
|
||||||
|
|
||||||
|
|
||||||
|
def validate_refresh_token_and_get_authenticated_user_id(
|
||||||
|
refresh_token: Annotated[str, Depends(validate_token_and_return_refresh_token)]
|
||||||
|
):
|
||||||
|
# Return the user ID associated with the token
|
||||||
|
return dependencies_security.get_token_user_id(refresh_token)
|
||||||
|
|
||||||
|
|
||||||
def validate_token_and_validate_admin_access(
|
def validate_token_and_validate_admin_access(
|
||||||
token: str = Depends(oauth2_scheme), db: Session = Depends(dependencies_database.get_db)
|
token: str = Depends(oauth2_scheme),
|
||||||
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
# Validate the token expiration
|
# Validate the token expiration
|
||||||
schema_access_tokens.validate_token_expiration(db, token)
|
dependencies_security.validate_token_expiration(token)
|
||||||
|
|
||||||
# Check if the token has admin access
|
# Check if the token has admin access
|
||||||
schema_access_tokens.validate_token_admin_access(token)
|
dependencies_security.validate_token_admin_access(token)
|
||||||
|
|
||||||
|
|
||||||
def validate_token_and_if_user_id_equals_token_user_id_if_not_validate_admin_access(
|
def validate_token_and_if_user_id_equals_token_user_id_if_not_validate_admin_access(
|
||||||
@@ -39,7 +102,7 @@ def validate_token_and_if_user_id_equals_token_user_id_if_not_validate_admin_acc
|
|||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
# Validate the token expiration
|
# Validate the token expiration
|
||||||
schema_access_tokens.validate_token_expiration(db, token)
|
dependencies_security.validate_token_expiration(token)
|
||||||
|
|
||||||
# Check if user_id higher than 0
|
# Check if user_id higher than 0
|
||||||
if not (int(user_id) > 0):
|
if not (int(user_id) > 0):
|
||||||
@@ -49,9 +112,9 @@ def validate_token_and_if_user_id_equals_token_user_id_if_not_validate_admin_acc
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Check if token id is different from user id. If yes, check if the token has admin access
|
# Check if token id is different from user id. If yes, check if the token has admin access
|
||||||
if user_id != schema_access_tokens.get_token_user_id(token):
|
if user_id != dependencies_security.get_token_user_id(token):
|
||||||
# Check if the token has admin access
|
# Check if the token has admin access
|
||||||
schema_access_tokens.validate_token_admin_access(token)
|
dependencies_security.validate_token_admin_access(token)
|
||||||
|
|
||||||
|
|
||||||
def validate_token_and_if_user_id_equals_token_user_attributtes_id_if_not_validate_admin_access(
|
def validate_token_and_if_user_id_equals_token_user_attributtes_id_if_not_validate_admin_access(
|
||||||
@@ -72,7 +135,7 @@ def validate_token_and_if_user_id_equals_token_user_attributtes_password_id_if_n
|
|||||||
|
|
||||||
def validate_token_user_id_admin_access(db, token, user_id):
|
def validate_token_user_id_admin_access(db, token, user_id):
|
||||||
# Validate the token expiration
|
# Validate the token expiration
|
||||||
schema_access_tokens.validate_token_expiration(db, token)
|
dependencies_security.validate_token_expiration(token)
|
||||||
|
|
||||||
# Check if user_id higher than 0
|
# Check if user_id higher than 0
|
||||||
if not (int(user_id) > 0):
|
if not (int(user_id) > 0):
|
||||||
@@ -82,6 +145,26 @@ def validate_token_user_id_admin_access(db, token, user_id):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Check if token id is different from user id. If yes, check if the token has admin access
|
# Check if token id is different from user id. If yes, check if the token has admin access
|
||||||
if user_id != schema_access_tokens.get_token_user_id(token):
|
if user_id != dependencies_security.get_token_user_id(token):
|
||||||
# Check if the token has admin access
|
# Check if the token has admin access
|
||||||
schema_access_tokens.validate_token_admin_access(token)
|
dependencies_security.validate_token_admin_access(token)
|
||||||
|
|
||||||
|
|
||||||
|
def check_scopes(
|
||||||
|
security_scopes: SecurityScopes,
|
||||||
|
# scopes: Annotated[list[str], Depends(dependencies_security.get_token_scopes)],
|
||||||
|
access_token: Annotated[str, Depends(validate_token_and_return_access_token)],
|
||||||
|
):
|
||||||
|
# Get the scopes from the token
|
||||||
|
scopes = dependencies_security.get_token_scopes(access_token)
|
||||||
|
|
||||||
|
# Check if the token has the required scopes
|
||||||
|
for scope in security_scopes.scopes:
|
||||||
|
if scope not in scopes:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail=f"Unauthorized Access - Not enough permissions - scope={security_scopes.scopes}",
|
||||||
|
headers={
|
||||||
|
"WWW-Authenticate": f'Bearer scope="{security_scopes.scopes}"'
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ from routers import (
|
|||||||
router_strava,
|
router_strava,
|
||||||
)
|
)
|
||||||
from constants import API_VERSION
|
from constants import API_VERSION
|
||||||
from schemas import schema_access_tokens
|
|
||||||
from database import SessionLocal
|
from database import SessionLocal
|
||||||
from processors import strava_processor, strava_activity_processor
|
from processors import strava_processor, strava_activity_processor
|
||||||
|
|
||||||
@@ -102,6 +101,7 @@ required_env_vars = [
|
|||||||
"SECRET_KEY",
|
"SECRET_KEY",
|
||||||
"ALGORITHM",
|
"ALGORITHM",
|
||||||
"ACCESS_TOKEN_EXPIRE_MINUTES",
|
"ACCESS_TOKEN_EXPIRE_MINUTES",
|
||||||
|
"REFRESH_TOKEN_EXPIRE_DAYS",
|
||||||
"STRAVA_CLIENT_ID",
|
"STRAVA_CLIENT_ID",
|
||||||
"STRAVA_CLIENT_SECRET",
|
"STRAVA_CLIENT_SECRET",
|
||||||
"STRAVA_AUTH_CODE",
|
"STRAVA_AUTH_CODE",
|
||||||
|
|||||||
@@ -90,9 +90,6 @@ class User(Base):
|
|||||||
Integer, nullable=False, comment="User type (one digit)(1 - user, 2 - admin)"
|
Integer, nullable=False, comment="User type (one digit)(1 - user, 2 - admin)"
|
||||||
)
|
)
|
||||||
photo_path = Column(String(length=250), nullable=True, comment="User photo path")
|
photo_path = Column(String(length=250), nullable=True, comment="User photo path")
|
||||||
photo_path_aux = Column(
|
|
||||||
String(length=250), nullable=True, comment="Auxiliary photo path"
|
|
||||||
)
|
|
||||||
is_active = Column(
|
is_active = Column(
|
||||||
Integer, nullable=False, comment="Is user active (1 - active, 2 - not active)"
|
Integer, nullable=False, comment="Is user active (1 - active, 2 - not active)"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from dependencies import (
|
|||||||
dependencies_activities,
|
dependencies_activities,
|
||||||
dependencies_gear,
|
dependencies_gear,
|
||||||
dependencies_global,
|
dependencies_global,
|
||||||
|
dependencies_security,
|
||||||
)
|
)
|
||||||
from processors import gpx_processor, fit_processor
|
from processors import gpx_processor, fit_processor
|
||||||
|
|
||||||
@@ -45,7 +46,9 @@ async def read_activities_useractivities_week(
|
|||||||
],
|
],
|
||||||
token_user_id: Annotated[
|
token_user_id: Annotated[
|
||||||
Callable,
|
Callable,
|
||||||
Depends(dependencies_session.validate_token_and_get_authenticated_user_id),
|
Depends(
|
||||||
|
dependencies_session.validate_access_token_and_get_authenticated_user_id
|
||||||
|
),
|
||||||
],
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
@@ -84,7 +87,9 @@ async def read_activities_useractivities_thisweek_distances(
|
|||||||
validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)],
|
validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)],
|
||||||
token_user_id: Annotated[
|
token_user_id: Annotated[
|
||||||
Callable,
|
Callable,
|
||||||
Depends(dependencies_session.validate_token_and_get_authenticated_user_id),
|
Depends(
|
||||||
|
dependencies_session.validate_access_token_and_get_authenticated_user_id
|
||||||
|
),
|
||||||
],
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
@@ -125,7 +130,7 @@ async def read_activities_useractivities_thismonth_distances(
|
|||||||
validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)],
|
validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)],
|
||||||
token_user_id: Annotated[
|
token_user_id: Annotated[
|
||||||
Callable,
|
Callable,
|
||||||
Depends(dependencies_session.validate_token_and_get_authenticated_user_id),
|
Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id),
|
||||||
],
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
@@ -165,7 +170,7 @@ async def read_activities_useractivities_thismonth_number(
|
|||||||
validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)],
|
validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)],
|
||||||
token_user_id: Annotated[
|
token_user_id: Annotated[
|
||||||
Callable,
|
Callable,
|
||||||
Depends(dependencies_session.validate_token_and_get_authenticated_user_id),
|
Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id),
|
||||||
],
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
@@ -214,7 +219,9 @@ async def read_activities_gearactivities(
|
|||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
# Get the activities for the gear
|
# Get the activities for the gear
|
||||||
return crud_activities.get_user_activities_by_gear_id_and_user_id(user_id, gear_id, db)
|
return crud_activities.get_user_activities_by_gear_id_and_user_id(
|
||||||
|
user_id, gear_id, db
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
@@ -225,7 +232,7 @@ async def read_activities_gearactivities(
|
|||||||
async def read_activities_useractivities_number(
|
async def read_activities_useractivities_number(
|
||||||
user_id: int,
|
user_id: int,
|
||||||
validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)],
|
validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)],
|
||||||
validate_token: Annotated[Callable, Depends(dependencies_session.validate_token)],
|
validate_token: Annotated[Callable, Depends(dependencies_security.validate_token_expiration)],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
# Get the number of activities for the user
|
# Get the number of activities for the user
|
||||||
@@ -252,7 +259,7 @@ async def read_activities_useractivities_pagination(
|
|||||||
validate_pagination_values: Annotated[
|
validate_pagination_values: Annotated[
|
||||||
Callable, Depends(dependencies_global.validate_pagination_values)
|
Callable, Depends(dependencies_global.validate_pagination_values)
|
||||||
],
|
],
|
||||||
validate_token: Annotated[Callable, Depends(dependencies_session.validate_token)],
|
validate_token: Annotated[Callable, Depends(dependencies_security.validate_token_expiration)],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
# Get the activities for the user with pagination
|
# Get the activities for the user with pagination
|
||||||
@@ -281,7 +288,7 @@ async def read_activities_followed_user_activities_pagination(
|
|||||||
validate_pagination_values: Annotated[
|
validate_pagination_values: Annotated[
|
||||||
Callable, Depends(dependencies_global.validate_pagination_values)
|
Callable, Depends(dependencies_global.validate_pagination_values)
|
||||||
],
|
],
|
||||||
validate_token: Annotated[Callable, Depends(dependencies_session.validate_token)],
|
validate_token: Annotated[Callable, Depends(dependencies_security.validate_token_expiration)],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
# Get the activities for the following users with pagination
|
# Get the activities for the following users with pagination
|
||||||
@@ -298,7 +305,7 @@ async def read_activities_followed_user_activities_pagination(
|
|||||||
async def read_activities_followed_useractivities_number(
|
async def read_activities_followed_useractivities_number(
|
||||||
user_id: int,
|
user_id: int,
|
||||||
validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)],
|
validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)],
|
||||||
validate_token: Annotated[Callable, Depends(dependencies_session.validate_token)],
|
validate_token: Annotated[Callable, Depends(dependencies_security.validate_token_expiration)],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
# Get the number of activities for the following users
|
# Get the number of activities for the following users
|
||||||
@@ -324,12 +331,14 @@ async def read_activities_activity_from_id(
|
|||||||
],
|
],
|
||||||
token_user_id: Annotated[
|
token_user_id: Annotated[
|
||||||
Callable,
|
Callable,
|
||||||
Depends(dependencies_session.validate_token_and_get_authenticated_user_id),
|
Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id),
|
||||||
],
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
# Get the activity from the database and return it
|
# Get the activity from the database and return it
|
||||||
return crud_activities.get_activity_by_id_from_user_id_or_has_visibility(activity_id, token_user_id, db)
|
return crud_activities.get_activity_by_id_from_user_id_or_has_visibility(
|
||||||
|
activity_id, token_user_id, db
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
@@ -341,7 +350,7 @@ async def read_activities_contain_name(
|
|||||||
name: str,
|
name: str,
|
||||||
token_user_id: Annotated[
|
token_user_id: Annotated[
|
||||||
Callable,
|
Callable,
|
||||||
Depends(dependencies_session.validate_token_and_get_authenticated_user_id),
|
Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id),
|
||||||
],
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
@@ -358,7 +367,7 @@ async def read_activities_contain_name(
|
|||||||
async def create_activity_with_uploaded_file(
|
async def create_activity_with_uploaded_file(
|
||||||
token_user_id: Annotated[
|
token_user_id: Annotated[
|
||||||
Callable,
|
Callable,
|
||||||
Depends(dependencies_session.validate_token_and_get_authenticated_user_id),
|
Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id),
|
||||||
],
|
],
|
||||||
file: UploadFile,
|
file: UploadFile,
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
@@ -425,7 +434,8 @@ async def create_activity_with_uploaded_file(
|
|||||||
) from err
|
) from err
|
||||||
|
|
||||||
|
|
||||||
@router.put("/activities/{activity_id}/addgear/{gear_id}",
|
@router.put(
|
||||||
|
"/activities/{activity_id}/addgear/{gear_id}",
|
||||||
tags=["activities"],
|
tags=["activities"],
|
||||||
)
|
)
|
||||||
async def activity_add_gear(
|
async def activity_add_gear(
|
||||||
@@ -435,7 +445,9 @@ async def activity_add_gear(
|
|||||||
],
|
],
|
||||||
gear_id: int,
|
gear_id: int,
|
||||||
validate_gear_id: Annotated[Callable, Depends(dependencies_gear.validate_gear_id)],
|
validate_gear_id: Annotated[Callable, Depends(dependencies_gear.validate_gear_id)],
|
||||||
token_user_id: Annotated[int, Depends(dependencies_session.validate_token_and_get_authenticated_user_id)],
|
token_user_id: Annotated[
|
||||||
|
int, Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id)
|
||||||
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
# Get the gear by user id and gear id
|
# Get the gear by user id and gear id
|
||||||
@@ -447,9 +459,11 @@ async def activity_add_gear(
|
|||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
detail=f"Gear ID {gear_id} for user {token_user_id} not found",
|
detail=f"Gear ID {gear_id} for user {token_user_id} not found",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get the activity by id from user id
|
# Get the activity by id from user id
|
||||||
activity = crud_activities.get_activity_by_id_from_user_id(activity_id, token_user_id, db)
|
activity = crud_activities.get_activity_by_id_from_user_id(
|
||||||
|
activity_id, token_user_id, db
|
||||||
|
)
|
||||||
|
|
||||||
# Check if activity is None and raise an HTTPException with a 404 Not Found status code if it is
|
# Check if activity is None and raise an HTTPException with a 404 Not Found status code if it is
|
||||||
if activity is None:
|
if activity is None:
|
||||||
@@ -460,11 +474,13 @@ async def activity_add_gear(
|
|||||||
|
|
||||||
# Add the gear to the activity
|
# Add the gear to the activity
|
||||||
crud_activities.add_gear_to_activity(activity_id, gear_id, db)
|
crud_activities.add_gear_to_activity(activity_id, gear_id, db)
|
||||||
|
|
||||||
# Return success message
|
# Return success message
|
||||||
return {"detail": f"Gear ID {gear_id} added to activity successfully"}
|
return {"detail": f"Gear ID {gear_id} added to activity successfully"}
|
||||||
|
|
||||||
@router.put("/activities/{activity_id}/deletegear",
|
|
||||||
|
@router.put(
|
||||||
|
"/activities/{activity_id}/deletegear",
|
||||||
tags=["activities"],
|
tags=["activities"],
|
||||||
)
|
)
|
||||||
async def delete_activity_gear(
|
async def delete_activity_gear(
|
||||||
@@ -472,11 +488,15 @@ async def delete_activity_gear(
|
|||||||
validate_activity_id: Annotated[
|
validate_activity_id: Annotated[
|
||||||
Callable, Depends(dependencies_activities.validate_activity_id)
|
Callable, Depends(dependencies_activities.validate_activity_id)
|
||||||
],
|
],
|
||||||
token_user_id: Annotated[int, Depends(dependencies_session.validate_token_and_get_authenticated_user_id)],
|
token_user_id: Annotated[
|
||||||
|
int, Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id)
|
||||||
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
# Get the activity by id from user id
|
# Get the activity by id from user id
|
||||||
activity = crud_activities.get_activity_by_id_from_user_id(activity_id, token_user_id, db)
|
activity = crud_activities.get_activity_by_id_from_user_id(
|
||||||
|
activity_id, token_user_id, db
|
||||||
|
)
|
||||||
|
|
||||||
# Check if activity is None and raise an HTTPException with a 404 Not Found status code if it is
|
# Check if activity is None and raise an HTTPException with a 404 Not Found status code if it is
|
||||||
if activity is None:
|
if activity is None:
|
||||||
@@ -487,11 +507,13 @@ async def delete_activity_gear(
|
|||||||
|
|
||||||
# Delete gear from the activity
|
# Delete gear from the activity
|
||||||
crud_activities.add_gear_to_activity(activity_id, None, db)
|
crud_activities.add_gear_to_activity(activity_id, None, db)
|
||||||
|
|
||||||
# Return success message
|
# Return success message
|
||||||
return {"detail": f"Gear ID {activity.gear_id} deleted from activity successfully"}
|
return {"detail": f"Gear ID {activity.gear_id} deleted from activity successfully"}
|
||||||
|
|
||||||
@router.delete("/activities/{activity_id}/delete",
|
|
||||||
|
@router.delete(
|
||||||
|
"/activities/{activity_id}/delete",
|
||||||
tags=["activities"],
|
tags=["activities"],
|
||||||
)
|
)
|
||||||
async def delete_activity(
|
async def delete_activity(
|
||||||
@@ -499,11 +521,15 @@ async def delete_activity(
|
|||||||
validate_activity_id: Annotated[
|
validate_activity_id: Annotated[
|
||||||
Callable, Depends(dependencies_activities.validate_activity_id)
|
Callable, Depends(dependencies_activities.validate_activity_id)
|
||||||
],
|
],
|
||||||
token_user_id: Annotated[int, Depends(dependencies_session.validate_token_and_get_authenticated_user_id)],
|
token_user_id: Annotated[
|
||||||
|
int, Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id)
|
||||||
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
# Get the activity by id from user id
|
# Get the activity by id from user id
|
||||||
activity = crud_activities.get_activity_by_id_from_user_id(activity_id, token_user_id, db)
|
activity = crud_activities.get_activity_by_id_from_user_id(
|
||||||
|
activity_id, token_user_id, db
|
||||||
|
)
|
||||||
|
|
||||||
# Check if activity is None and raise an HTTPException with a 404 Not Found status code if it is
|
# Check if activity is None and raise an HTTPException with a 404 Not Found status code if it is
|
||||||
if activity is None:
|
if activity is None:
|
||||||
@@ -511,10 +537,9 @@ async def delete_activity(
|
|||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
detail=f"Activity ID {activity_id} for user {token_user_id} not found",
|
detail=f"Activity ID {activity_id} for user {token_user_id} not found",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Delete the activity
|
# Delete the activity
|
||||||
crud_activities.delete_activity(activity_id, db)
|
crud_activities.delete_activity(activity_id, db)
|
||||||
|
|
||||||
# Return success message
|
# Return success message
|
||||||
return {"detail": f"Activity {activity_id} deleted successfully"}
|
return {"detail": f"Activity {activity_id} deleted successfully"}
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ from schemas import schema_activity_streams
|
|||||||
from crud import crud_activity_streams
|
from crud import crud_activity_streams
|
||||||
from dependencies import (
|
from dependencies import (
|
||||||
dependencies_database,
|
dependencies_database,
|
||||||
dependencies_session,
|
dependencies_security,
|
||||||
dependencies_activities,
|
dependencies_activities,
|
||||||
dependencies_activity_streams,
|
dependencies_activity_streams,
|
||||||
)
|
)
|
||||||
@@ -36,7 +36,7 @@ async def read_activities_streams_for_activity_all(
|
|||||||
Callable, Depends(dependencies_activities.validate_activity_id)
|
Callable, Depends(dependencies_activities.validate_activity_id)
|
||||||
],
|
],
|
||||||
validate_token_validate_admin_access: Annotated[
|
validate_token_validate_admin_access: Annotated[
|
||||||
Callable, Depends(dependencies_session.validate_token)
|
Callable, Depends(dependencies_security.validate_token_expiration)
|
||||||
],
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
@@ -59,7 +59,7 @@ async def read_activities_streams_for_activity_stream_type(
|
|||||||
Callable, Depends(dependencies_activity_streams.validate_activity_stream_type)
|
Callable, Depends(dependencies_activity_streams.validate_activity_stream_type)
|
||||||
],
|
],
|
||||||
validate_token: Annotated[
|
validate_token: Annotated[
|
||||||
Callable, Depends(dependencies_session.validate_token)
|
Callable, Depends(dependencies_security.validate_token_expiration)
|
||||||
],
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from schemas import schema_followers
|
|||||||
from crud import crud_followers
|
from crud import crud_followers
|
||||||
from dependencies import (
|
from dependencies import (
|
||||||
dependencies_database,
|
dependencies_database,
|
||||||
dependencies_session,
|
dependencies_security,
|
||||||
dependencies_users,
|
dependencies_users,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ logger = logging.getLogger("myLogger")
|
|||||||
async def get_user_follower_all(
|
async def get_user_follower_all(
|
||||||
user_id: int,
|
user_id: int,
|
||||||
validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)],
|
validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)],
|
||||||
validate_token: Callable = Depends(dependencies_session.validate_token),
|
validate_token: Callable = Depends(dependencies_security.validate_token_expiration),
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
# Return followers
|
# Return followers
|
||||||
@@ -47,7 +47,7 @@ async def get_user_follower_all(
|
|||||||
async def get_user_follower_count_all(
|
async def get_user_follower_count_all(
|
||||||
user_id: int,
|
user_id: int,
|
||||||
validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)],
|
validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)],
|
||||||
validate_token: Callable = Depends(dependencies_session.validate_token),
|
validate_token: Callable = Depends(dependencies_security.validate_token_expiration),
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
# Return followers
|
# Return followers
|
||||||
@@ -69,7 +69,7 @@ async def get_user_follower_count_all(
|
|||||||
async def get_user_follower_count(
|
async def get_user_follower_count(
|
||||||
user_id: int,
|
user_id: int,
|
||||||
validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)],
|
validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)],
|
||||||
validate_token: Callable = Depends(dependencies_session.validate_token),
|
validate_token: Callable = Depends(dependencies_security.validate_token_expiration),
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
# Return followers
|
# Return followers
|
||||||
@@ -91,7 +91,7 @@ async def get_user_follower_count(
|
|||||||
async def get_user_following_all(
|
async def get_user_following_all(
|
||||||
user_id: int,
|
user_id: int,
|
||||||
validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)],
|
validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)],
|
||||||
validate_token: Callable = Depends(dependencies_session.validate_token),
|
validate_token: Callable = Depends(dependencies_security.validate_token_expiration),
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
# Return followings
|
# Return followings
|
||||||
@@ -106,7 +106,7 @@ async def get_user_following_all(
|
|||||||
async def get_user_following_count_all(
|
async def get_user_following_count_all(
|
||||||
user_id: int,
|
user_id: int,
|
||||||
validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)],
|
validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)],
|
||||||
validate_token: Callable = Depends(dependencies_session.validate_token),
|
validate_token: Callable = Depends(dependencies_security.validate_token_expiration),
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
# Return followings
|
# Return followings
|
||||||
@@ -128,7 +128,7 @@ async def get_user_following_count_all(
|
|||||||
async def get_user_following_count(
|
async def get_user_following_count(
|
||||||
user_id: int,
|
user_id: int,
|
||||||
validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)],
|
validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)],
|
||||||
validate_token: Callable = Depends(dependencies_session.validate_token),
|
validate_token: Callable = Depends(dependencies_security.validate_token_expiration),
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
# Return followings
|
# Return followings
|
||||||
@@ -154,7 +154,7 @@ async def read_followers_user_specific_user(
|
|||||||
validate_target_user_id: Annotated[
|
validate_target_user_id: Annotated[
|
||||||
Callable, Depends(dependencies_users.validate_target_user_id)
|
Callable, Depends(dependencies_users.validate_target_user_id)
|
||||||
],
|
],
|
||||||
validate_token: Callable = Depends(dependencies_session.validate_token),
|
validate_token: Callable = Depends(dependencies_security.validate_token_expiration),
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
# Return the follower
|
# Return the follower
|
||||||
@@ -175,7 +175,7 @@ async def create_follow(
|
|||||||
validate_target_user_id: Annotated[
|
validate_target_user_id: Annotated[
|
||||||
Callable, Depends(dependencies_users.validate_target_user_id)
|
Callable, Depends(dependencies_users.validate_target_user_id)
|
||||||
],
|
],
|
||||||
validate_token: Callable = Depends(dependencies_session.validate_token),
|
validate_token: Callable = Depends(dependencies_security.validate_token_expiration),
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
# Create the follower
|
# Create the follower
|
||||||
@@ -195,7 +195,7 @@ async def accept_follow(
|
|||||||
validate_target_user_id: Annotated[
|
validate_target_user_id: Annotated[
|
||||||
Callable, Depends(dependencies_users.validate_target_user_id)
|
Callable, Depends(dependencies_users.validate_target_user_id)
|
||||||
],
|
],
|
||||||
validate_token: Callable = Depends(dependencies_session.validate_token),
|
validate_token: Callable = Depends(dependencies_security.validate_token_expiration),
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
# Accept the follower
|
# Accept the follower
|
||||||
@@ -216,7 +216,7 @@ async def delete_follow(
|
|||||||
validate_target_user_id: Annotated[
|
validate_target_user_id: Annotated[
|
||||||
Callable, Depends(dependencies_users.validate_target_user_id)
|
Callable, Depends(dependencies_users.validate_target_user_id)
|
||||||
],
|
],
|
||||||
validate_token: Callable = Depends(dependencies_session.validate_token),
|
validate_token: Callable = Depends(dependencies_security.validate_token_expiration),
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
# Delete the follower
|
# Delete the follower
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ async def read_gear_id(
|
|||||||
gear_id: int,
|
gear_id: int,
|
||||||
validate_gear_id: Annotated[Callable, Depends(dependencies_gear.validate_gear_id)],
|
validate_gear_id: Annotated[Callable, Depends(dependencies_gear.validate_gear_id)],
|
||||||
user_id: Annotated[
|
user_id: Annotated[
|
||||||
int, Depends(dependencies_session.validate_token_and_get_authenticated_user_id)
|
int, Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id)
|
||||||
],
|
],
|
||||||
db: Annotated[Session, Depends(dependencies_database.get_db)],
|
db: Annotated[Session, Depends(dependencies_database.get_db)],
|
||||||
):
|
):
|
||||||
@@ -54,7 +54,7 @@ async def read_gear_user_pagination(
|
|||||||
Callable, Depends(dependencies_global.validate_pagination_values)
|
Callable, Depends(dependencies_global.validate_pagination_values)
|
||||||
],
|
],
|
||||||
user_id: Annotated[
|
user_id: Annotated[
|
||||||
int, Depends(dependencies_session.validate_token_and_get_authenticated_user_id)
|
int, Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id)
|
||||||
],
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
@@ -71,7 +71,7 @@ async def read_gear_user_pagination(
|
|||||||
)
|
)
|
||||||
async def read_gear_user_number(
|
async def read_gear_user_number(
|
||||||
user_id: Annotated[
|
user_id: Annotated[
|
||||||
int, Depends(dependencies_session.validate_token_and_get_authenticated_user_id)
|
int, Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id)
|
||||||
],
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
@@ -94,7 +94,7 @@ async def read_gear_user_number(
|
|||||||
async def read_gear_user_by_nickname(
|
async def read_gear_user_by_nickname(
|
||||||
nickname: str,
|
nickname: str,
|
||||||
user_id: Annotated[
|
user_id: Annotated[
|
||||||
int, Depends(dependencies_session.validate_token_and_get_authenticated_user_id)
|
int, Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id)
|
||||||
],
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
@@ -111,7 +111,7 @@ async def read_gear_user_by_type(
|
|||||||
gear_type: int,
|
gear_type: int,
|
||||||
validate_type: Annotated[Callable, Depends(dependencies_gear.validate_gear_type)],
|
validate_type: Annotated[Callable, Depends(dependencies_gear.validate_gear_type)],
|
||||||
user_id: Annotated[
|
user_id: Annotated[
|
||||||
int, Depends(dependencies_session.validate_token_and_get_authenticated_user_id)
|
int, Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id)
|
||||||
],
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
@@ -127,7 +127,7 @@ async def read_gear_user_by_type(
|
|||||||
async def create_gear(
|
async def create_gear(
|
||||||
gear: schema_gear.Gear,
|
gear: schema_gear.Gear,
|
||||||
user_id: Annotated[
|
user_id: Annotated[
|
||||||
int, Depends(dependencies_session.validate_token_and_get_authenticated_user_id)
|
int, Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id)
|
||||||
],
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
@@ -144,7 +144,7 @@ async def edit_gear(
|
|||||||
validate_id: Annotated[Callable, Depends(dependencies_gear.validate_gear_id)],
|
validate_id: Annotated[Callable, Depends(dependencies_gear.validate_gear_id)],
|
||||||
gear: schema_gear.Gear,
|
gear: schema_gear.Gear,
|
||||||
token_user_id: Annotated[
|
token_user_id: Annotated[
|
||||||
int, Depends(dependencies_session.validate_token_and_get_authenticated_user_id)
|
int, Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id)
|
||||||
],
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
@@ -170,7 +170,7 @@ async def delete_user(
|
|||||||
gear_id: int,
|
gear_id: int,
|
||||||
validate_id: Annotated[Callable, Depends(dependencies_gear.validate_gear_id)],
|
validate_id: Annotated[Callable, Depends(dependencies_gear.validate_gear_id)],
|
||||||
token_user_id: Annotated[
|
token_user_id: Annotated[
|
||||||
int, Depends(dependencies_session.validate_token_and_get_authenticated_user_id)
|
int, Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id)
|
||||||
],
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -1,26 +1,36 @@
|
|||||||
import logging
|
import logging
|
||||||
|
import bcrypt
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, timezone
|
||||||
from typing import Annotated, Callable
|
from typing import Annotated
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, status, Security
|
from fastapi import (
|
||||||
from fastapi.security import (
|
APIRouter,
|
||||||
OAuth2PasswordBearer,
|
Depends,
|
||||||
OAuth2PasswordRequestForm,
|
HTTPException,
|
||||||
SecurityScopes,
|
status,
|
||||||
|
Response,
|
||||||
|
Request,
|
||||||
)
|
)
|
||||||
|
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from crud import crud_user_integrations, crud_users
|
from crud import crud_users
|
||||||
from schemas import schema_access_tokens, schema_users
|
from schemas import schema_users
|
||||||
from constants import (
|
from constants import (
|
||||||
USER_NOT_ACTIVE,
|
USER_NOT_ACTIVE,
|
||||||
REGULAR_ACCESS,
|
REGULAR_ACCESS,
|
||||||
REGULAR_ACCESS_SCOPES,
|
REGULAR_ACCESS_SCOPES,
|
||||||
ADMIN_ACCESS_SCOPES,
|
ADMIN_ACCESS_SCOPES,
|
||||||
SCOPES_DICT,
|
SCOPES_DICT,
|
||||||
|
JWT_ACCESS_TOKEN_EXPIRE_MINUTES,
|
||||||
|
JWT_REFRESH_TOKEN_EXPIRE_DAYS,
|
||||||
|
)
|
||||||
|
from dependencies import (
|
||||||
|
dependencies_database,
|
||||||
|
dependencies_session,
|
||||||
|
dependencies_security,
|
||||||
)
|
)
|
||||||
from dependencies import dependencies_database, dependencies_session
|
|
||||||
|
|
||||||
# Define the OAuth2 scheme for handling bearer tokens
|
# Define the OAuth2 scheme for handling bearer tokens
|
||||||
oauth2_scheme = OAuth2PasswordBearer(
|
oauth2_scheme = OAuth2PasswordBearer(
|
||||||
@@ -35,60 +45,108 @@ router = APIRouter()
|
|||||||
logger = logging.getLogger("myLogger")
|
logger = logging.getLogger("myLogger")
|
||||||
|
|
||||||
|
|
||||||
|
def hash_password(password: str):
|
||||||
|
# Hash the password and return it
|
||||||
|
return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt())
|
||||||
|
|
||||||
|
|
||||||
|
def verify_password(plain_password: str, hashed_password: str):
|
||||||
|
# Check if the password is equal to the hashed password
|
||||||
|
return bcrypt.checkpw(
|
||||||
|
plain_password.encode("utf-8"), hashed_password.encode("utf-8")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def authenticate_user(username: str, password: str, db: Session):
|
def authenticate_user(username: str, password: str, db: Session):
|
||||||
# Get the user from the database
|
# Get the user from the database
|
||||||
user = crud_users.authenticate_user(username, password, db)
|
user = crud_users.authenticate_user(username, db)
|
||||||
|
|
||||||
# Check if the user exists and if the password is correct and if not return False
|
# Check if the user exists and if the hashed_password is correct and if not return False
|
||||||
if not user:
|
if not user:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
detail="Incorrect username or password",
|
detail="Incorrect username",
|
||||||
headers={"WWW-Authenticate": "Bearer"},
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
)
|
)
|
||||||
|
|
||||||
# Return the user if the password is correct
|
if not verify_password(password, user.password):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Incorrect password",
|
||||||
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Return the user if the hashed_password is correct
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
def get_current_user(db: Session, user_id: int):
|
def create_response_with_tokens(response: Response, user: schema_users.User):
|
||||||
# Get the user from the database
|
# Check user access level and set scopes accordingly
|
||||||
user = crud_users.get_user_by_id(user_id, db)
|
if user.access_type == REGULAR_ACCESS:
|
||||||
|
scopes = REGULAR_ACCESS_SCOPES
|
||||||
|
else:
|
||||||
|
scopes = ADMIN_ACCESS_SCOPES
|
||||||
|
|
||||||
# If the user does not exist raise the exception
|
# Create the access and refresh tokens
|
||||||
if user is None:
|
access_token = dependencies_security.create_token(
|
||||||
raise HTTPException(
|
data={
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
"sub": user.username,
|
||||||
detail="Could not validate credentials (user not found)",
|
"scopes": scopes,
|
||||||
headers={"WWW-Authenticate": "Bearer"},
|
"id": user.id,
|
||||||
)
|
"access_type": user.access_type,
|
||||||
|
"exp": datetime.now(timezone.utc)
|
||||||
user_integrations = crud_user_integrations.get_user_integrations_by_user_id(
|
+ timedelta(minutes=JWT_ACCESS_TOKEN_EXPIRE_MINUTES),
|
||||||
user.id, db
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
if user_integrations is None:
|
refresh_token = dependencies_security.create_token(
|
||||||
raise HTTPException(
|
data={
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
"sub": user.username,
|
||||||
detail="Could not validate credentials (user integrations not found)",
|
"scopes": "scopes",
|
||||||
headers={"WWW-Authenticate": "Bearer"},
|
"id": user.id,
|
||||||
)
|
"access_type": user.access_type,
|
||||||
|
"exp": datetime.now(timezone.utc)
|
||||||
|
+ timedelta(days=JWT_REFRESH_TOKEN_EXPIRE_DAYS),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
if user_integrations.strava_token is None:
|
# Set the cookies with the tokens
|
||||||
user.is_strava_linked = 0
|
response.set_cookie(
|
||||||
else:
|
key="endurain_access_token",
|
||||||
user.is_strava_linked = 1
|
value=access_token,
|
||||||
|
expires=datetime.now(timezone.utc)
|
||||||
|
+ timedelta(minutes=JWT_ACCESS_TOKEN_EXPIRE_MINUTES),
|
||||||
|
httponly=True,
|
||||||
|
path="/",
|
||||||
|
secure=False,
|
||||||
|
samesite="None",
|
||||||
|
)
|
||||||
|
response.set_cookie(
|
||||||
|
key="endurain_refresh_token",
|
||||||
|
value=refresh_token,
|
||||||
|
expires=datetime.now(timezone.utc)
|
||||||
|
+ timedelta(days=JWT_REFRESH_TOKEN_EXPIRE_DAYS),
|
||||||
|
httponly=True,
|
||||||
|
path="/",
|
||||||
|
secure=False,
|
||||||
|
samesite="None",
|
||||||
|
)
|
||||||
|
|
||||||
# Return the user
|
# Set the user id in a cookie
|
||||||
return user
|
response.set_cookie(
|
||||||
|
key="endurain_logged_user_id",
|
||||||
|
value=user.id,
|
||||||
|
httponly=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Return the response
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post("/token", tags=["session"])
|
||||||
"/token", response_model=schema_access_tokens.AccessToken, tags=["session"]
|
|
||||||
)
|
|
||||||
async def login_for_access_token(
|
async def login_for_access_token(
|
||||||
|
response: Response,
|
||||||
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
|
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
|
||||||
do_not_expire: bool = False,
|
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
user = authenticate_user(form_data.username, form_data.password, db)
|
user = authenticate_user(form_data.username, form_data.password, db)
|
||||||
@@ -100,49 +158,33 @@ async def login_for_access_token(
|
|||||||
headers={"WWW-Authenticate": "Bearer"},
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
)
|
)
|
||||||
|
|
||||||
expire = None
|
response = create_response_with_tokens(response, user)
|
||||||
if do_not_expire:
|
|
||||||
expire = datetime.utcnow() + timedelta(days=90)
|
|
||||||
|
|
||||||
if user.access_type == REGULAR_ACCESS:
|
return {"message": "Login successful"}
|
||||||
scopes = REGULAR_ACCESS_SCOPES
|
|
||||||
else:
|
|
||||||
scopes = ADMIN_ACCESS_SCOPES
|
|
||||||
|
|
||||||
access_token = schema_access_tokens.create_access_token(
|
|
||||||
db,
|
|
||||||
data={
|
|
||||||
"sub": user.username,
|
|
||||||
"scopes": scopes,
|
|
||||||
"id": user.id,
|
|
||||||
"access_type": user.access_type,
|
|
||||||
},
|
|
||||||
expires_delta=expire,
|
|
||||||
)
|
|
||||||
|
|
||||||
return schema_access_tokens.AccessToken(
|
|
||||||
access_token=access_token, token_type="bearer"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/users/me", response_model=schema_users.UserMe, tags=["session"])
|
@router.post("/refresh", tags=["session"])
|
||||||
async def read_users_me(
|
async def refresh_token(
|
||||||
security_scopes: SecurityScopes,
|
response: Response,
|
||||||
|
request: Request,
|
||||||
user_id: Annotated[
|
user_id: Annotated[
|
||||||
int, Depends(dependencies_session.validate_token_and_get_authenticated_user_id)
|
int,
|
||||||
],
|
Depends(
|
||||||
check_scopes: Annotated[
|
dependencies_session.validate_refresh_token_and_get_authenticated_user_id
|
||||||
Callable, Security(schema_access_tokens.check_scopes, scopes=["users:read"])
|
),
|
||||||
],
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
return get_current_user(db, user_id)
|
# get user
|
||||||
|
user = crud_users.get_user_by_id(user_id, db)
|
||||||
|
|
||||||
|
if user.is_active == USER_NOT_ACTIVE:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="Inactive user",
|
||||||
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
)
|
||||||
|
|
||||||
@router.get("/validate_token", tags=["session"])
|
response = create_response_with_tokens(response, user)
|
||||||
async def validate_token(
|
|
||||||
validate_token: Callable = Depends(dependencies_session.validate_token),
|
return {"message": "Token refreshed successfully"}
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
|
||||||
):
|
|
||||||
# Return None if the token is valid
|
|
||||||
return None
|
|
||||||
|
|||||||
@@ -78,9 +78,7 @@ async def strava_link(
|
|||||||
|
|
||||||
# Redirect to the main page or any other desired page after processing
|
# Redirect to the main page or any other desired page after processing
|
||||||
redirect_url = (
|
redirect_url = (
|
||||||
"https://"
|
"https://" + os.environ.get("FRONTEND_HOST") + "/settings?stravaLinked=1"
|
||||||
+ os.environ.get("FRONTEND_HOST")
|
|
||||||
+ "/settings?stravaLinked=1"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Return a RedirectResponse to the redirect URL
|
# Return a RedirectResponse to the redirect URL
|
||||||
@@ -104,7 +102,10 @@ async def strava_link(
|
|||||||
async def strava_retrieve_activities_days(
|
async def strava_retrieve_activities_days(
|
||||||
days: int,
|
days: int,
|
||||||
token_user_id: Annotated[
|
token_user_id: Annotated[
|
||||||
int, Depends(dependencies_session.validate_token_and_get_authenticated_user_id)
|
int,
|
||||||
|
Depends(
|
||||||
|
dependencies_session.validate_access_token_and_get_authenticated_user_id
|
||||||
|
),
|
||||||
],
|
],
|
||||||
# db: Annotated[Session, Depends(dependencies_database.get_db)],
|
# db: Annotated[Session, Depends(dependencies_database.get_db)],
|
||||||
background_tasks: BackgroundTasks,
|
background_tasks: BackgroundTasks,
|
||||||
@@ -132,7 +133,10 @@ async def strava_retrieve_activities_days(
|
|||||||
async def strava_set_user_unique_state(
|
async def strava_set_user_unique_state(
|
||||||
state: str,
|
state: str,
|
||||||
user_id: Annotated[
|
user_id: Annotated[
|
||||||
int, Depends(dependencies_session.validate_token_and_get_authenticated_user_id)
|
int,
|
||||||
|
Depends(
|
||||||
|
dependencies_session.validate_access_token_and_get_authenticated_user_id
|
||||||
|
),
|
||||||
],
|
],
|
||||||
db: Annotated[Session, Depends(dependencies_database.get_db)],
|
db: Annotated[Session, Depends(dependencies_database.get_db)],
|
||||||
):
|
):
|
||||||
@@ -149,7 +153,7 @@ async def strava_set_user_unique_state(
|
|||||||
)
|
)
|
||||||
async def strava_unset_user_unique_state(
|
async def strava_unset_user_unique_state(
|
||||||
user_id: Annotated[
|
user_id: Annotated[
|
||||||
int, Depends(dependencies_session.validate_token_and_get_authenticated_user_id)
|
int, Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id)
|
||||||
],
|
],
|
||||||
db: Annotated[Session, Depends(dependencies_database.get_db)],
|
db: Annotated[Session, Depends(dependencies_database.get_db)],
|
||||||
):
|
):
|
||||||
@@ -163,7 +167,7 @@ async def strava_unset_user_unique_state(
|
|||||||
@router.delete("/strava/unlink", tags=["strava"])
|
@router.delete("/strava/unlink", tags=["strava"])
|
||||||
async def strava_unlink(
|
async def strava_unlink(
|
||||||
token_user_id: Annotated[
|
token_user_id: Annotated[
|
||||||
int, Depends(dependencies_session.validate_token_and_get_authenticated_user_id)
|
int, Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id)
|
||||||
],
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
db: Session = Depends(dependencies_database.get_db),
|
||||||
):
|
):
|
||||||
@@ -183,7 +187,7 @@ async def strava_unlink(
|
|||||||
@router.get("/strava/gear", status_code=202, tags=["strava"])
|
@router.get("/strava/gear", status_code=202, tags=["strava"])
|
||||||
async def strava_retrieve_gear(
|
async def strava_retrieve_gear(
|
||||||
token_user_id: Annotated[
|
token_user_id: Annotated[
|
||||||
int, Depends(dependencies_session.validate_token_and_get_authenticated_user_id)
|
int, Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id)
|
||||||
],
|
],
|
||||||
background_tasks: BackgroundTasks,
|
background_tasks: BackgroundTasks,
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import logging
|
|||||||
|
|
||||||
from typing import Annotated, Callable
|
from typing import Annotated, Callable
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile
|
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, Security
|
||||||
from fastapi.security import OAuth2PasswordBearer
|
from fastapi.security import OAuth2PasswordBearer
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
@@ -16,6 +16,7 @@ from dependencies import (
|
|||||||
dependencies_session,
|
dependencies_session,
|
||||||
dependencies_global,
|
dependencies_global,
|
||||||
dependencies_users,
|
dependencies_users,
|
||||||
|
dependencies_security,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Define the OAuth2 scheme for handling bearer tokens
|
# Define the OAuth2 scheme for handling bearer tokens
|
||||||
@@ -28,12 +29,65 @@ router = APIRouter()
|
|||||||
logger = logging.getLogger("myLogger")
|
logger = logging.getLogger("myLogger")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/users/me", response_model=schema_users.UserMe, tags=["users"])
|
||||||
|
async def read_users_me(
|
||||||
|
token_user_id: Annotated[
|
||||||
|
int,
|
||||||
|
Depends(
|
||||||
|
dependencies_session.validate_access_token_and_get_authenticated_user_id
|
||||||
|
),
|
||||||
|
],
|
||||||
|
check_scopes: Annotated[
|
||||||
|
Callable, Security(dependencies_session.check_scopes, scopes=["users:read"])
|
||||||
|
],
|
||||||
|
db: Annotated[
|
||||||
|
Session,
|
||||||
|
Depends(dependencies_database.get_db),
|
||||||
|
],
|
||||||
|
):
|
||||||
|
# Get the user from the database
|
||||||
|
user = crud_users.get_user_by_id(token_user_id, db)
|
||||||
|
|
||||||
|
# If the user does not exist raise the exception
|
||||||
|
if user is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Could not validate credentials (user not found)",
|
||||||
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
)
|
||||||
|
|
||||||
|
user_integrations = crud_user_integrations.get_user_integrations_by_user_id(
|
||||||
|
user.id, db
|
||||||
|
)
|
||||||
|
|
||||||
|
if user_integrations is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Could not validate credentials (user integrations not found)",
|
||||||
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
)
|
||||||
|
|
||||||
|
if user_integrations.strava_token is None:
|
||||||
|
user.is_strava_linked = 0
|
||||||
|
else:
|
||||||
|
user.is_strava_linked = 1
|
||||||
|
|
||||||
|
# Return the user
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
@router.get("/users/number", response_model=int, tags=["users"])
|
@router.get("/users/number", response_model=int, tags=["users"])
|
||||||
async def read_users_number(
|
async def read_users_number(
|
||||||
validate_token_validate_admin_access: Annotated[
|
validate_access_token_and_validate_admin_access: Annotated[
|
||||||
Callable, Depends(dependencies_session.validate_token_and_validate_admin_access)
|
Callable, Depends(dependencies_session.validate_access_token_and_validate_admin_access)
|
||||||
|
],
|
||||||
|
check_scopes: Annotated[
|
||||||
|
Callable, Security(dependencies_session.check_scopes, scopes=["users:read"])
|
||||||
|
],
|
||||||
|
db: Annotated[
|
||||||
|
Session,
|
||||||
|
Depends(dependencies_database.get_db),
|
||||||
],
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
|
||||||
):
|
):
|
||||||
return crud_users.get_users_number(db)
|
return crud_users.get_users_number(db)
|
||||||
|
|
||||||
@@ -49,10 +103,16 @@ async def read_users_all_pagination(
|
|||||||
validate_pagination_values: Annotated[
|
validate_pagination_values: Annotated[
|
||||||
Callable, Depends(dependencies_global.validate_pagination_values)
|
Callable, Depends(dependencies_global.validate_pagination_values)
|
||||||
],
|
],
|
||||||
validate_token_validate_admin_access: Annotated[
|
validate_access_token_and_validate_admin_access: Annotated[
|
||||||
Callable, Depends(dependencies_session.validate_token_and_validate_admin_access)
|
Callable, Depends(dependencies_session.validate_access_token_and_validate_admin_access)
|
||||||
|
],
|
||||||
|
check_scopes: Annotated[
|
||||||
|
Callable, Security(dependencies_session.check_scopes, scopes=["users:read"])
|
||||||
|
],
|
||||||
|
db: Annotated[
|
||||||
|
Session,
|
||||||
|
Depends(dependencies_database.get_db),
|
||||||
],
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
|
||||||
):
|
):
|
||||||
# Get the users from the database with pagination
|
# Get the users from the database with pagination
|
||||||
return crud_users.get_users_with_pagination(
|
return crud_users.get_users_with_pagination(
|
||||||
@@ -67,10 +127,16 @@ async def read_users_all_pagination(
|
|||||||
)
|
)
|
||||||
async def read_users_contain_username(
|
async def read_users_contain_username(
|
||||||
username: str,
|
username: str,
|
||||||
validate_token_validate_admin_access: Annotated[
|
validate_access_token_and_validate_admin_access: Annotated[
|
||||||
Callable, Depends(dependencies_session.validate_token_and_validate_admin_access)
|
Callable, Depends(dependencies_session.validate_access_token_and_validate_admin_access)
|
||||||
|
],
|
||||||
|
check_scopes: Annotated[
|
||||||
|
Callable, Security(dependencies_session.check_scopes, scopes=["users:read"])
|
||||||
|
],
|
||||||
|
db: Annotated[
|
||||||
|
Session,
|
||||||
|
Depends(dependencies_database.get_db),
|
||||||
],
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
|
||||||
):
|
):
|
||||||
# Get the users from the database by username
|
# Get the users from the database by username
|
||||||
return crud_users.get_user_if_contains_username(username=username, db=db)
|
return crud_users.get_user_if_contains_username(username=username, db=db)
|
||||||
@@ -83,10 +149,16 @@ async def read_users_contain_username(
|
|||||||
)
|
)
|
||||||
async def read_users_username(
|
async def read_users_username(
|
||||||
username: str,
|
username: str,
|
||||||
validate_token_validate_admin_access: Annotated[
|
validate_access_token_and_validate_admin_access: Annotated[
|
||||||
Callable, Depends(dependencies_session.validate_token_and_validate_admin_access)
|
Callable, Depends(dependencies_session.validate_access_token_and_validate_admin_access)
|
||||||
|
],
|
||||||
|
check_scopes: Annotated[
|
||||||
|
Callable, Security(dependencies_session.check_scopes, scopes=["users:read"])
|
||||||
|
],
|
||||||
|
db: Annotated[
|
||||||
|
Session,
|
||||||
|
Depends(dependencies_database.get_db),
|
||||||
],
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
|
||||||
):
|
):
|
||||||
# Get the user from the database by username
|
# Get the user from the database by username
|
||||||
return crud_users.get_user_by_username(username=username, db=db)
|
return crud_users.get_user_by_username(username=username, db=db)
|
||||||
@@ -96,8 +168,16 @@ async def read_users_username(
|
|||||||
async def read_users_id(
|
async def read_users_id(
|
||||||
user_id: int,
|
user_id: int,
|
||||||
validate_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)],
|
validate_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)],
|
||||||
validate_token: Callable = Depends(dependencies_session.validate_token),
|
validate_access_token: Annotated[
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
Callable, Depends(dependencies_session.validate_access_token)
|
||||||
|
],
|
||||||
|
check_scopes: Annotated[
|
||||||
|
Callable, Security(dependencies_session.check_scopes, scopes=["users:read"])
|
||||||
|
],
|
||||||
|
db: Annotated[
|
||||||
|
Session,
|
||||||
|
Depends(dependencies_database.get_db),
|
||||||
|
],
|
||||||
):
|
):
|
||||||
# Get the users from the database by id
|
# Get the users from the database by id
|
||||||
return crud_users.get_user_by_id(user_id=user_id, db=db)
|
return crud_users.get_user_by_id(user_id=user_id, db=db)
|
||||||
@@ -106,10 +186,16 @@ async def read_users_id(
|
|||||||
@router.get("/users/{username}/id", response_model=int, tags=["users"])
|
@router.get("/users/{username}/id", response_model=int, tags=["users"])
|
||||||
async def read_users_username_id(
|
async def read_users_username_id(
|
||||||
username: str,
|
username: str,
|
||||||
validate_token_validate_admin_access: Annotated[
|
validate_access_token_and_validate_admin_access: Annotated[
|
||||||
Callable, Depends(dependencies_session.validate_token_and_validate_admin_access)
|
Callable, Depends(dependencies_session.validate_access_token_and_validate_admin_access)
|
||||||
|
],
|
||||||
|
check_scopes: Annotated[
|
||||||
|
Callable, Security(dependencies_session.check_scopes, scopes=["users:read"])
|
||||||
|
],
|
||||||
|
db: Annotated[
|
||||||
|
Session,
|
||||||
|
Depends(dependencies_database.get_db),
|
||||||
],
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
|
||||||
):
|
):
|
||||||
# Get the users from the database by username
|
# Get the users from the database by username
|
||||||
return crud_users.get_user_id_by_username(username, db)
|
return crud_users.get_user_id_by_username(username, db)
|
||||||
@@ -119,37 +205,34 @@ async def read_users_username_id(
|
|||||||
async def read_users_id_photo_path(
|
async def read_users_id_photo_path(
|
||||||
user_id: int,
|
user_id: int,
|
||||||
validate_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)],
|
validate_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)],
|
||||||
validate_token_validate_admin_access: Annotated[
|
validate_access_token_and_validate_admin_access: Annotated[
|
||||||
Callable, Depends(dependencies_session.validate_token_and_validate_admin_access)
|
Callable, Depends(dependencies_session.validate_access_token_and_validate_admin_access)
|
||||||
|
],
|
||||||
|
check_scopes: Annotated[
|
||||||
|
Callable, Security(dependencies_session.check_scopes, scopes=["users:read"])
|
||||||
|
],
|
||||||
|
db: Annotated[
|
||||||
|
Session,
|
||||||
|
Depends(dependencies_database.get_db),
|
||||||
],
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
|
||||||
):
|
):
|
||||||
# Get the photo_path from the database by id
|
# Get the photo_path from the database by id
|
||||||
return crud_users.get_user_photo_path_by_id(user_id, db)
|
return crud_users.get_user_photo_path_by_id(user_id, db)
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
|
||||||
"/users/{user_id}/photo_path_aux", response_model=str | None, tags=["users"]
|
|
||||||
)
|
|
||||||
async def read_users_id_photo_path_aux(
|
|
||||||
user_id: int,
|
|
||||||
validate_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)],
|
|
||||||
validate_token_validate_admin_access: Annotated[
|
|
||||||
Callable, Depends(dependencies_session.validate_token_and_validate_admin_access)
|
|
||||||
],
|
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
|
||||||
):
|
|
||||||
# Get the photo_path_aux from the database by id
|
|
||||||
return crud_users.get_user_photo_path_aux_by_id(user_id, db)
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/users/create", response_model=int, status_code=201, tags=["users"])
|
@router.post("/users/create", response_model=int, status_code=201, tags=["users"])
|
||||||
async def create_user(
|
async def create_user(
|
||||||
user: schema_users.UserCreate,
|
user: schema_users.UserCreate,
|
||||||
validate_token_validate_admin_access: Annotated[
|
validate_access_token_and_validate_admin_access: Annotated[
|
||||||
Callable, Depends(dependencies_session.validate_token_and_validate_admin_access)
|
Callable, Depends(dependencies_session.validate_access_token_and_validate_admin_access)
|
||||||
|
],
|
||||||
|
check_scopes: Annotated[
|
||||||
|
Callable, Security(dependencies_session.check_scopes, scopes=["users:write"])
|
||||||
|
],
|
||||||
|
db: Annotated[
|
||||||
|
Session,
|
||||||
|
Depends(dependencies_database.get_db),
|
||||||
],
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
|
||||||
):
|
):
|
||||||
# Create the user in the database
|
# Create the user in the database
|
||||||
created_user = crud_users.create_user(user, db)
|
created_user = crud_users.create_user(user, db)
|
||||||
@@ -171,15 +254,23 @@ async def upload_user_image(
|
|||||||
user_id: int,
|
user_id: int,
|
||||||
token_user_id: Annotated[
|
token_user_id: Annotated[
|
||||||
Callable,
|
Callable,
|
||||||
Depends(dependencies_session.validate_token_and_get_authenticated_user_id),
|
Depends(
|
||||||
|
dependencies_session.validate_access_token_and_get_authenticated_user_id
|
||||||
|
),
|
||||||
],
|
],
|
||||||
file: UploadFile,
|
file: UploadFile,
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
check_scopes: Annotated[
|
||||||
|
Callable, Security(dependencies_session.check_scopes, scopes=["users:edit"])
|
||||||
|
],
|
||||||
|
db: Annotated[
|
||||||
|
Session,
|
||||||
|
Depends(dependencies_database.get_db),
|
||||||
|
],
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
upload_dir = "user_images"
|
upload_dir = "user_images"
|
||||||
os.makedirs(upload_dir, exist_ok=True)
|
os.makedirs(upload_dir, exist_ok=True)
|
||||||
|
|
||||||
# Get file extension
|
# Get file extension
|
||||||
_, file_extension = os.path.splitext(file.filename)
|
_, file_extension = os.path.splitext(file.filename)
|
||||||
filename = f"{user_id}{file_extension}"
|
filename = f"{user_id}{file_extension}"
|
||||||
@@ -188,13 +279,11 @@ async def upload_user_image(
|
|||||||
|
|
||||||
with open(file_path_to_save, "wb") as buffer:
|
with open(file_path_to_save, "wb") as buffer:
|
||||||
shutil.copyfileobj(file.file, buffer)
|
shutil.copyfileobj(file.file, buffer)
|
||||||
|
|
||||||
return crud_users.edit_user_photo_path(user_id, file_path_to_save, db)
|
return crud_users.edit_user_photo_path(user_id, file_path_to_save, db)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
# Log the exception
|
# Log the exception
|
||||||
logger.error(
|
logger.error(f"Error in upload_user_image: {err}", exc_info=True)
|
||||||
f"Error in upload_user_image: {err}", exc_info=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Remove the file after processing
|
# Remove the file after processing
|
||||||
if os.path.exists(file_path_to_save):
|
if os.path.exists(file_path_to_save):
|
||||||
@@ -216,7 +305,13 @@ async def edit_user(
|
|||||||
dependencies_session.validate_token_and_if_user_id_equals_token_user_attributtes_id_if_not_validate_admin_access
|
dependencies_session.validate_token_and_if_user_id_equals_token_user_attributtes_id_if_not_validate_admin_access
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
check_scopes: Annotated[
|
||||||
|
Callable, Security(dependencies_session.check_scopes, scopes=["users:edit"])
|
||||||
|
],
|
||||||
|
db: Annotated[
|
||||||
|
Session,
|
||||||
|
Depends(dependencies_database.get_db),
|
||||||
|
],
|
||||||
):
|
):
|
||||||
# Update the user in the database
|
# Update the user in the database
|
||||||
crud_users.edit_user(user_attributtes, db)
|
crud_users.edit_user(user_attributtes, db)
|
||||||
@@ -234,7 +329,13 @@ async def edit_user_password(
|
|||||||
dependencies_session.validate_token_and_if_user_id_equals_token_user_attributtes_password_id_if_not_validate_admin_access
|
dependencies_session.validate_token_and_if_user_id_equals_token_user_attributtes_password_id_if_not_validate_admin_access
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
check_scopes: Annotated[
|
||||||
|
Callable, Security(dependencies_session.check_scopes, scopes=["users:edit"])
|
||||||
|
],
|
||||||
|
db: Annotated[
|
||||||
|
Session,
|
||||||
|
Depends(dependencies_database.get_db),
|
||||||
|
],
|
||||||
):
|
):
|
||||||
# Update the user password in the database
|
# Update the user password in the database
|
||||||
crud_users.edit_user_password(user_attributtes.id, user_attributtes.password, db)
|
crud_users.edit_user_password(user_attributtes.id, user_attributtes.password, db)
|
||||||
@@ -252,7 +353,13 @@ async def delete_user_photo(
|
|||||||
dependencies_session.validate_token_and_if_user_id_equals_token_user_id_if_not_validate_admin_access
|
dependencies_session.validate_token_and_if_user_id_equals_token_user_id_if_not_validate_admin_access
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
check_scopes: Annotated[
|
||||||
|
Callable, Security(dependencies_session.check_scopes, scopes=["users:edit"])
|
||||||
|
],
|
||||||
|
db: Annotated[
|
||||||
|
Session,
|
||||||
|
Depends(dependencies_database.get_db),
|
||||||
|
],
|
||||||
):
|
):
|
||||||
# Update the user photo_path in the database
|
# Update the user photo_path in the database
|
||||||
crud_users.delete_user_photo(user_id, db)
|
crud_users.delete_user_photo(user_id, db)
|
||||||
@@ -268,7 +375,13 @@ async def delete_user(
|
|||||||
validate_token_validate_admin_access: Annotated[
|
validate_token_validate_admin_access: Annotated[
|
||||||
Callable, Depends(dependencies_session.validate_token_and_validate_admin_access)
|
Callable, Depends(dependencies_session.validate_token_and_validate_admin_access)
|
||||||
],
|
],
|
||||||
db: Session = Depends(dependencies_database.get_db),
|
check_scopes: Annotated[
|
||||||
|
Callable, Security(dependencies_session.check_scopes, scopes=["users:write"])
|
||||||
|
],
|
||||||
|
db: Annotated[
|
||||||
|
Session,
|
||||||
|
Depends(dependencies_database.get_db),
|
||||||
|
],
|
||||||
):
|
):
|
||||||
# Delete the user in the database
|
# Delete the user in the database
|
||||||
crud_users.delete_user(user_id, db)
|
crud_users.delete_user(user_id, db)
|
||||||
|
|||||||
@@ -1,167 +0,0 @@
|
|||||||
import logging
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
|
||||||
from datetime import datetime, timedelta, timezone
|
|
||||||
from fastapi import Depends, HTTPException, status, Security
|
|
||||||
from fastapi.security import OAuth2PasswordBearer, SecurityScopes
|
|
||||||
|
|
||||||
# from jose import JWTError, jwt
|
|
||||||
from joserfc import jwt
|
|
||||||
from joserfc.jwk import OctKey
|
|
||||||
from sqlalchemy.orm import Session
|
|
||||||
|
|
||||||
from constants import (
|
|
||||||
JWT_EXPIRATION_IN_MINUTES,
|
|
||||||
JWT_ALGORITHM,
|
|
||||||
JWT_SECRET_KEY,
|
|
||||||
ADMIN_ACCESS,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AccessToken(BaseModel):
|
|
||||||
"""Access token schema"""
|
|
||||||
|
|
||||||
access_token: str
|
|
||||||
token_type: str
|
|
||||||
|
|
||||||
|
|
||||||
class Token(BaseModel):
|
|
||||||
"""Token schema"""
|
|
||||||
|
|
||||||
user_id: int | None = None
|
|
||||||
expires_at: str | None = None
|
|
||||||
|
|
||||||
|
|
||||||
class TokenData(Token):
|
|
||||||
"""Token data schema"""
|
|
||||||
|
|
||||||
access_type: int | None = None
|
|
||||||
|
|
||||||
|
|
||||||
class CreateToken(Token):
|
|
||||||
"""Create token schema"""
|
|
||||||
|
|
||||||
token: str
|
|
||||||
|
|
||||||
|
|
||||||
# Define the OAuth2 scheme for handling bearer tokens
|
|
||||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
|
||||||
|
|
||||||
# Define a loggger created on main.py
|
|
||||||
logger = logging.getLogger("myLogger")
|
|
||||||
|
|
||||||
|
|
||||||
def decode_token(token: str = Depends(oauth2_scheme)):
|
|
||||||
# Decode the token and return the payload
|
|
||||||
return jwt.decode(token, OctKey.import_key(JWT_SECRET_KEY))
|
|
||||||
|
|
||||||
|
|
||||||
def validate_token_expiration(db: Session, token: str = Depends(oauth2_scheme)):
|
|
||||||
# Try to decode the token and check if it is expired
|
|
||||||
try:
|
|
||||||
# Decode the token
|
|
||||||
# Mark exp claim as required
|
|
||||||
claims_requests = jwt.JWTClaimsRegistry(exp={"essential": True})
|
|
||||||
|
|
||||||
# decodes the token
|
|
||||||
payload = decode_token(token)
|
|
||||||
|
|
||||||
# Validate token exp
|
|
||||||
claims_requests.validate(payload.claims)
|
|
||||||
except Exception:
|
|
||||||
# Log the error and raise the exception
|
|
||||||
logger.info("Token expired during validation | Returning 401 response")
|
|
||||||
|
|
||||||
# Raise an HTTPException with a 401 Unauthorized status code
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
||||||
detail="Token no longer valid",
|
|
||||||
headers={"WWW-Authenticate": "Bearer"},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_token_user_id(token: str = Depends(oauth2_scheme)):
|
|
||||||
# Decode the token
|
|
||||||
payload = decode_token(token)
|
|
||||||
|
|
||||||
# Get the user id from the payload
|
|
||||||
user_id = payload.claims["id"]
|
|
||||||
|
|
||||||
if user_id is None:
|
|
||||||
# If the user id is None raise an HTTPException with a 401 Unauthorized status code
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
||||||
detail="Could not validate credentials",
|
|
||||||
headers={"WWW-Authenticate": "Bearer"},
|
|
||||||
)
|
|
||||||
|
|
||||||
# Return the user id
|
|
||||||
return user_id
|
|
||||||
|
|
||||||
|
|
||||||
def get_token_access_type(token: str = Depends(oauth2_scheme)):
|
|
||||||
# Decode the token
|
|
||||||
payload = decode_token(token)
|
|
||||||
|
|
||||||
# Get the admin access from the payload
|
|
||||||
access_type = payload.claims["access_type"]
|
|
||||||
|
|
||||||
if access_type is None:
|
|
||||||
# If the access type is None raise an HTTPException with a 401 Unauthorized status code
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
||||||
detail="Could not validate credentials",
|
|
||||||
headers={"WWW-Authenticate": "Bearer"},
|
|
||||||
)
|
|
||||||
|
|
||||||
# Return the access type
|
|
||||||
return access_type
|
|
||||||
|
|
||||||
|
|
||||||
def validate_token_admin_access(token: str = Depends(oauth2_scheme)):
|
|
||||||
if get_token_access_type(token) != ADMIN_ACCESS:
|
|
||||||
# Raise an HTTPException with a 403 Forbidden status code
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
|
||||||
detail="Unauthorized Access - Admin Access Required",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def check_scopes(security_scopes: SecurityScopes, token: str = Security(oauth2_scheme)):
|
|
||||||
# Decode the token
|
|
||||||
payload = decode_token(token)
|
|
||||||
|
|
||||||
# Get the scopes from the payload
|
|
||||||
scopes = payload.claims["scopes"]
|
|
||||||
|
|
||||||
if not any(scope in scopes for scope in security_scopes.scopes):
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
|
||||||
detail="Unauthorized Access - Not enough permissions",
|
|
||||||
headers={"WWW-Authenticate": f'Bearer scope="{security_scopes.scope_str}"'},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def create_access_token(
|
|
||||||
db: Session, data: dict, expires_delta: timedelta | None = None
|
|
||||||
):
|
|
||||||
# Create a copy of the data to encode
|
|
||||||
to_encode = data.copy()
|
|
||||||
|
|
||||||
# If an expiration time is provided, calculate the expiration time
|
|
||||||
if expires_delta:
|
|
||||||
expire = datetime.now(timezone.utc) + expires_delta
|
|
||||||
else:
|
|
||||||
expire = datetime.now(timezone.utc) + timedelta(
|
|
||||||
minutes=JWT_EXPIRATION_IN_MINUTES
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add the expiration time to the data to encode
|
|
||||||
to_encode.update({"exp": expire})
|
|
||||||
|
|
||||||
# Encode the data and return the token
|
|
||||||
# encoded_jwt = jwt.encode(to_encode, JWT_SECRET_KEY, algorithm=JWT_ALGORITHM)
|
|
||||||
encoded_jwt = jwt.encode({"alg": JWT_ALGORITHM}, to_encode, JWT_SECRET_KEY)
|
|
||||||
|
|
||||||
# Return the token
|
|
||||||
return encoded_jwt
|
|
||||||
@@ -12,7 +12,6 @@ class User(BaseModel):
|
|||||||
gender: int
|
gender: int
|
||||||
access_type: int
|
access_type: int
|
||||||
photo_path: str | None = None
|
photo_path: str | None = None
|
||||||
photo_path_aux: str | None = None
|
|
||||||
is_active: int
|
is_active: int
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import UserView from '../views/UserView.vue'
|
|||||||
import SettingsView from '../views/SettingsView.vue';
|
import SettingsView from '../views/SettingsView.vue';
|
||||||
import NotFoundView from '../views/NotFoundView.vue';
|
import NotFoundView from '../views/NotFoundView.vue';
|
||||||
|
|
||||||
import { auth } from '@/services/auth';
|
//import { auth } from '@/services/auth';
|
||||||
|
|
||||||
//import { useAuthStore } from '@/stores/auth';
|
//import { useAuthStore } from '@/stores/auth';
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ const router = createRouter({
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
/* router.beforeEach((to, from, next) => {
|
||||||
const accessToken = localStorage.getItem('accessToken');
|
const accessToken = localStorage.getItem('accessToken');
|
||||||
const tokenType = localStorage.getItem('tokenType');
|
const tokenType = localStorage.getItem('tokenType');
|
||||||
|
|
||||||
@@ -79,6 +79,6 @@ router.beforeEach((to, from, next) => {
|
|||||||
} else {
|
} else {
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
});
|
}); */
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { fetchGetRequestTokenAsParameter, fetchPostFormUrlEncoded } from '@/utils/serviceUtils';
|
import { fetchGetRequest, fetchPostFormUrlEncoded } from '@/utils/serviceUtils';
|
||||||
import { useUserStore } from '@/stores/user';
|
import { useUserStore } from '@/stores/user';
|
||||||
|
|
||||||
export const auth = {
|
export const auth = {
|
||||||
@@ -13,9 +13,7 @@ export const auth = {
|
|||||||
|
|
||||||
return exp > currentTime;
|
return exp > currentTime;
|
||||||
},
|
},
|
||||||
storeLoggedUser(token, userMe) {
|
storeLoggedUser(userMe) {
|
||||||
localStorage.setItem('accessToken', token.access_token);
|
|
||||||
localStorage.setItem('tokenType', token.token_type);
|
|
||||||
localStorage.setItem('userMe', JSON.stringify(userMe));
|
localStorage.setItem('userMe', JSON.stringify(userMe));
|
||||||
},
|
},
|
||||||
removeLoggedUser() {
|
removeLoggedUser() {
|
||||||
@@ -28,6 +26,6 @@ export const auth = {
|
|||||||
return fetchPostFormUrlEncoded('token', formData);
|
return fetchPostFormUrlEncoded('token', formData);
|
||||||
},
|
},
|
||||||
getUserMe(token) {
|
getUserMe(token) {
|
||||||
return fetchGetRequestTokenAsParameter('users/me', token);
|
return fetchGetRequest('users/me', token);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
7
frontend/src/services/session.js
Normal file
7
frontend/src/services/session.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { fetchGetRequest, fetchPostFormUrlEncoded } from '@/utils/serviceUtils';
|
||||||
|
|
||||||
|
export const session = {
|
||||||
|
getToken(formData) {
|
||||||
|
return fetchPostFormUrlEncoded('token', formData);
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
import { fetchGetRequest, fetchPostRequest, fetchPutRequest, fetchDeleteRequest, fetchPostFileRequest } from '@/utils/serviceUtils';
|
import { fetchGetRequest, fetchPostRequest, fetchPutRequest, fetchDeleteRequest, fetchPostFileRequest } from '@/utils/serviceUtils';
|
||||||
|
|
||||||
export const users = {
|
export const users = {
|
||||||
|
getUserMe() {
|
||||||
|
return fetchGetRequest('users/me');
|
||||||
|
},
|
||||||
getUsersWithPagination(pageNumber, numRecords) {
|
getUsersWithPagination(pageNumber, numRecords) {
|
||||||
return fetchGetRequest(`users/all/page_number/${pageNumber}/num_records/${numRecords}`);
|
return fetchGetRequest(`users/all/page_number/${pageNumber}/num_records/${numRecords}`);
|
||||||
},
|
},
|
||||||
|
|||||||
39
frontend/src/stores/session.js
Normal file
39
frontend/src/stores/session.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
|
export const useSessionStore = defineStore('session', {
|
||||||
|
state: () => ({
|
||||||
|
id: null,
|
||||||
|
name: '',
|
||||||
|
username: '',
|
||||||
|
email: '',
|
||||||
|
city: null,
|
||||||
|
birthdate: null,
|
||||||
|
preferred_language: '',
|
||||||
|
gender: null,
|
||||||
|
access_type: null,
|
||||||
|
photo_path: '',
|
||||||
|
photo_path_aux: null,
|
||||||
|
is_active: null,
|
||||||
|
is_strava_linked: null,
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
setUser(user) {
|
||||||
|
this.id = user.id;
|
||||||
|
this.name = user.name;
|
||||||
|
this.username = user.username;
|
||||||
|
this.email = user.email;
|
||||||
|
this.city = user.city;
|
||||||
|
this.birthdate = user.birthdate;
|
||||||
|
this.preferred_language = user.preferred_language;
|
||||||
|
this.gender = user.gender;
|
||||||
|
this.access_type = user.access_type;
|
||||||
|
this.photo_path = user.photo_path;
|
||||||
|
this.photo_path_aux = user.photo_path_aux;
|
||||||
|
this.is_active = user.is_active;
|
||||||
|
this.is_strava_linked = user.is_strava_linked;
|
||||||
|
},
|
||||||
|
resetUser() {
|
||||||
|
this.$reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -13,9 +13,9 @@ export async function fetchGetRequest(url) {
|
|||||||
// Send the GET request
|
// Send the GET request
|
||||||
const response = await fetch(fullUrl, {
|
const response = await fetch(fullUrl, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
credentials: 'include',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Authorization': `Bearer ${localStorage.getItem('accessToken')}`,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
// If the response status is not ok, throw an error
|
// If the response status is not ok, throw an error
|
||||||
@@ -96,10 +96,13 @@ export async function fetchPostFileRequest(url, formData) {
|
|||||||
export async function fetchPostFormUrlEncoded(url, formData) {
|
export async function fetchPostFormUrlEncoded(url, formData) {
|
||||||
// Create the full URL by combining the API URL with the provided URL
|
// Create the full URL by combining the API URL with the provided URL
|
||||||
const fullUrl = `${API_URL}${url}`;
|
const fullUrl = `${API_URL}${url}`;
|
||||||
|
// Ensure formData is a URLSearchParams object for URL-encoded data
|
||||||
|
const urlEncodedData = new URLSearchParams(formData);
|
||||||
// Send the POST request
|
// Send the POST request
|
||||||
const response = await fetch(fullUrl, {
|
const response = await fetch(fullUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData,
|
body: urlEncodedData,
|
||||||
|
credentials: 'include',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -21,11 +21,11 @@
|
|||||||
<label for="loginPassword">{{ $t("login.password") }}</label>
|
<label for="loginPassword">{{ $t("login.password") }}</label>
|
||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
<div class="form-check">
|
<!--<div class="form-check">
|
||||||
<input type="checkbox" class="form-check-input" name="loginNeverExpires" v-model="neverExpires">
|
<input type="checkbox" class="form-check-input" name="loginNeverExpires" v-model="neverExpires">
|
||||||
<label class="form-check-label" for="loginNeverExpires">{{ $t("login.neverExpires") }}</label>
|
<label class="form-check-label" for="loginNeverExpires">{{ $t("login.neverExpires") }}</label>
|
||||||
</div>
|
</div>
|
||||||
<br>
|
<br>-->
|
||||||
<button class="w-100 btn btn-lg btn-primary" type="submit">{{ $t("login.signInButton") }}</button>
|
<button class="w-100 btn btn-lg btn-primary" type="submit">{{ $t("login.signInButton") }}</button>
|
||||||
<!--<div>
|
<!--<div>
|
||||||
<br>
|
<br>
|
||||||
@@ -46,13 +46,16 @@ import { useI18n } from 'vue-i18n';
|
|||||||
// Importing the stores
|
// Importing the stores
|
||||||
import { useErrorAlertStore } from '@/stores/Alerts/errorAlert';
|
import { useErrorAlertStore } from '@/stores/Alerts/errorAlert';
|
||||||
import { useInfoAlertStore } from '@/stores/Alerts/infoAlert';
|
import { useInfoAlertStore } from '@/stores/Alerts/infoAlert';
|
||||||
|
import { useSessionStore } from '@/stores/session';
|
||||||
// Importing the services for the login
|
// Importing the services for the login
|
||||||
import { auth } from '@/services/auth';
|
//import { auth } from '@/services/auth';
|
||||||
|
import { session } from '@/services/session';
|
||||||
|
import { users } from '@/services/user';
|
||||||
// Importing the components
|
// Importing the components
|
||||||
import ErrorAlertComponent from '@/components/Alerts/ErrorAlertComponent.vue';
|
import ErrorAlertComponent from '@/components/Alerts/ErrorAlertComponent.vue';
|
||||||
import InfoAlertComponent from '@/components/Alerts/InfoAlertComponent.vue';
|
import InfoAlertComponent from '@/components/Alerts/InfoAlertComponent.vue';
|
||||||
// Importing the crypto-js
|
// Importing the crypto-js
|
||||||
import CryptoJS from 'crypto-js';
|
//import CryptoJS from 'crypto-js';
|
||||||
|
|
||||||
|
|
||||||
// Exporting the default object
|
// Exporting the default object
|
||||||
@@ -70,30 +73,33 @@ export default {
|
|||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const username = ref('');
|
const username = ref('');
|
||||||
const password = ref('');
|
const password = ref('');
|
||||||
const neverExpires = ref(false);
|
//const neverExpires = ref(false);
|
||||||
const errorMessage = ref('');
|
const errorMessage = ref('');
|
||||||
const showSessionExpiredMessage = ref(false);
|
const showSessionExpiredMessage = ref(false);
|
||||||
const errorAlertStore = useErrorAlertStore();
|
const errorAlertStore = useErrorAlertStore();
|
||||||
const infoAlertStore = useInfoAlertStore();
|
const infoAlertStore = useInfoAlertStore();
|
||||||
|
const sessionStore = useSessionStore();
|
||||||
|
|
||||||
// Handle the form submission
|
// Handle the form submission
|
||||||
const submitForm = async () => {
|
const submitForm = async () => {
|
||||||
// Hash the password
|
// Hash the password
|
||||||
const hashedPassword = CryptoJS.SHA256(password.value).toString(CryptoJS.enc.Hex);
|
//const hashedPassword = CryptoJS.SHA256(password.value).toString(CryptoJS.enc.Hex);
|
||||||
// Create the form data
|
// Create the form data
|
||||||
const formData = new URLSearchParams();
|
const formData = new URLSearchParams();
|
||||||
formData.append('username', username.value);
|
formData.append('username', username.value);
|
||||||
formData.append('password', hashedPassword);
|
formData.append('password', password.value);
|
||||||
formData.append('neverExpires', neverExpires.value);
|
//formData.append('neverExpires', neverExpires.value);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get the token
|
// Get the token
|
||||||
const token = await auth.getToken(formData);
|
await session.getToken(formData);
|
||||||
// Get the userMe
|
|
||||||
const userMe = await auth.getUserMe(token.access_token);
|
|
||||||
|
|
||||||
// Store the logged user
|
// Get logged user information
|
||||||
auth.storeLoggedUser(token, userMe);
|
const userMe = await users.getUserMe();
|
||||||
|
|
||||||
|
// Store the user in the session store
|
||||||
|
sessionStore.setUser(userMe);
|
||||||
|
|
||||||
// Redirect to the home page
|
// Redirect to the home page
|
||||||
router.push('/');
|
router.push('/');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -127,7 +133,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
neverExpires,
|
//neverExpires,
|
||||||
showSessionExpiredMessage,
|
showSessionExpiredMessage,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
submitForm,
|
submitForm,
|
||||||
|
|||||||
@@ -13,4 +13,5 @@ opentelemetry.exporter.otlp==1.22.0
|
|||||||
python-multipart==0.0.9
|
python-multipart==0.0.9
|
||||||
gpxpy==1.6.2
|
gpxpy==1.6.2
|
||||||
alembic==1.13.1
|
alembic==1.13.1
|
||||||
joserfc==0.11.1
|
joserfc==0.11.1
|
||||||
|
bcrypt==4.1.3
|
||||||
Reference in New Issue
Block a user