-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* heel simpel inloggen en data ophalen er staan nog heel veel waarden hardcoded, en code is totaal niet clean * improve fetch * add frontend_url to config * env file initialized + README updated * linter prettier * logout fixed + temporary '/' * composition api only + logout page * improve fetch profile * update local SSL certificate for frontend * remove config.yml * format code * refactor backend to use JWT instead of cookies * update backend readme for explanation authentication * update requirements.txt * fix jwt -> pyjwt * update frontend to use JWT and some basic styling * format frontend * remove counter store * update readme about logging in * cleanup backend auth * cleanup frontend auth * run formatter * Update backend/src/auth/dependencies.py Co-authored-by: Xander Bil <[email protected]> * apply requested changes * run formatter * update backend README * Update frontend/.env.local.example Co-authored-by: Xander Bil <[email protected]> * reject config when values are missing * fix env value in config * color fixes * linter --------- Co-authored-by: Marieke <[email protected]> Co-authored-by: Xander Bil <[email protected]>
- Loading branch information
1 parent
d026975
commit d34b73d
Showing
44 changed files
with
856 additions
and
408 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
__pycache__ | ||
*venv* | ||
local-cert/ | ||
|
||
*.db | ||
config.yml | ||
.env | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,3 @@ | ||
#!/usr/bin/env bash | ||
|
||
uvicorn src.main:app --reload --port 8080 \ | ||
--ssl-keyfile "local-cert/localhost-key.pem" \ | ||
--ssl-certfile "local-cert/localhost.pem" | ||
uvicorn src.main:app --reload --port 5173 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import jwt | ||
from fastapi import Depends | ||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer | ||
from src.config import CONFIG | ||
|
||
from .exceptions import UnAuthenticated | ||
|
||
|
||
def verify_jwt_token( | ||
credentials: HTTPAuthorizationCredentials = Depends(HTTPBearer()), | ||
) -> str: | ||
""" | ||
Verify the JWT token and return the user_id | ||
Args: | ||
credentials (HTTPAuthorizationCredentials): The credentials from the request | ||
Returns: | ||
str: The user_id | ||
Raises: | ||
UnAuthenticated: If the token is invalid or expired | ||
""" | ||
try: | ||
payload = jwt.decode( | ||
credentials.credentials, | ||
CONFIG.secret_key, | ||
algorithms=[CONFIG.algorithm], | ||
verify_signature=True, | ||
options={"require": ["exp", "sub"]}, | ||
) | ||
user_id = payload["sub"] | ||
return user_id | ||
except (jwt.ExpiredSignatureError, jwt.MissingRequiredClaimError): | ||
# Token is expired or no expiration time is set | ||
raise UnAuthenticated | ||
except jwt.InvalidTokenError: | ||
# Token is invalid | ||
raise UnAuthenticated |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
from fastapi import HTTPException, status | ||
|
||
|
||
class NotAuthorized(HTTPException): | ||
def __init__(self, detail: str = "Not authorized"): | ||
"""Raised when user is not privileged enough to do this action""" | ||
super().__init__(status_code=status.HTTP_403_FORBIDDEN, detail=detail) | ||
|
||
|
||
class UnAuthenticated(HTTPException): | ||
def __init__(self, detail: str = "Login required"): | ||
"""Raised when user not logged in""" | ||
super().__init__(status_code=status.HTTP_401_UNAUTHORIZED, detail=detail) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import src.user.service as user_service | ||
from cas import CASClient | ||
from fastapi import APIRouter, Depends, Request | ||
from fastapi.responses import RedirectResponse | ||
from sqlalchemy.orm import Session | ||
from src import config | ||
from src.auth.schemas import Authority, Token, TokenRequest | ||
from src.dependencies import get_db | ||
from src.user.schemas import UserCreate | ||
|
||
from .exceptions import UnAuthenticated | ||
from .utils import create_jwt_token | ||
|
||
router = APIRouter( | ||
prefix="/api", tags=["auth"], responses={404: {"description": "Not Found"}} | ||
) | ||
|
||
cas_client = CASClient( | ||
version=2, | ||
server_url=f"{config.CONFIG.cas_server_url}", | ||
) | ||
|
||
|
||
@router.get("/authority") | ||
def authority() -> Authority: | ||
""" | ||
Get CAS authority | ||
""" | ||
return Authority(method="cas", authority=config.CONFIG.cas_server_url) | ||
|
||
|
||
@router.post("/token") | ||
async def token( | ||
request: Request, | ||
token_request: TokenRequest, | ||
db: Session = Depends(get_db), | ||
) -> Token: | ||
""" | ||
Get JWT token from CAS ticket | ||
""" | ||
# No ticket provided | ||
if not token_request.ticket: | ||
raise UnAuthenticated(detail="No ticket provided") | ||
|
||
cas_client.service_url = f"{request.headers.get('origin')}{token_request.returnUrl}" | ||
user, attributes, _ = cas_client.verify_ticket(token_request.ticket) | ||
|
||
# Invalid ticket | ||
if not user or not attributes: | ||
raise UnAuthenticated(detail="Invalid CAS ticket") | ||
# Create user if not exists | ||
if not await user_service.get_by_id(db, attributes["uid"]): | ||
await user_service.create_user( | ||
db, | ||
UserCreate( | ||
given_name=attributes["givenname"], | ||
uid=attributes["uid"], | ||
mail=attributes["mail"], | ||
), | ||
) | ||
|
||
# Create JWT token | ||
jwt_token = create_jwt_token(attributes["uid"]) | ||
return jwt_token | ||
|
||
|
||
@router.get("/logout") | ||
async def logout() -> RedirectResponse: | ||
""" | ||
Logout from CAS | ||
""" | ||
cas_logout_url = cas_client.get_logout_url() | ||
return RedirectResponse(cas_logout_url) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
from pydantic import BaseModel | ||
|
||
|
||
class Token(BaseModel): | ||
token: str | ||
token_type: str | ||
|
||
|
||
class TokenRequest(BaseModel): | ||
ticket: str | ||
returnUrl: str | ||
|
||
|
||
class Authority(BaseModel): | ||
method: str | ||
authority: str |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import jwt | ||
from src import config | ||
from src.auth.schemas import Token | ||
from datetime import datetime, timedelta, timezone | ||
|
||
|
||
def create_jwt_token(user_id: str) -> Token: | ||
now = datetime.now(timezone.utc) | ||
payload = { | ||
"sub": user_id, | ||
"iat": now, | ||
# TODO: don't hardcode this | ||
"exp": now + timedelta(weeks=1), | ||
} | ||
token = jwt.encode( | ||
payload, config.CONFIG.secret_key, algorithm=config.CONFIG.algorithm | ||
) | ||
return Token(token=token, token_type="bearer") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,36 @@ | ||
from __future__ import annotations | ||
from dotenv import load_dotenv | ||
import os | ||
from dataclasses import dataclass | ||
|
||
from dotenv import load_dotenv | ||
|
||
load_dotenv() | ||
|
||
|
||
@dataclass | ||
class Config: | ||
api_url: str | ||
frontend_url: str | ||
cas_server_url: str | ||
database_uri: str | ||
secret_key: str | ||
algorithm: str | ||
|
||
|
||
env = { | ||
"frontend_url": os.getenv("FRONTEND_URL", ""), | ||
"cas_server_url": os.getenv("CAS_SERVER_URL", ""), | ||
"database_uri": os.getenv("DATABASE_URI", ""), | ||
"secret_key": os.getenv("SECRET_KEY", ""), | ||
"algorithm": os.getenv("ALGORITHM", ""), | ||
} | ||
|
||
for key, value in env.items(): | ||
if value == "": | ||
raise ValueError(f"Environment variable {key} is not set") | ||
|
||
CONFIG = Config( | ||
os.getenv("API_URL", "https://localhost:8000"), | ||
os.getenv("CAS_SERVER", "https://login.ugent.be"), | ||
os.getenv("DATABASE_URI", "CONNECtOON_STRING") | ||
frontend_url=env["frontend_url"], | ||
cas_server_url=env["cas_server_url"], | ||
database_uri=env["database_uri"], | ||
secret_key=env["secret_key"], | ||
algorithm=env["algorithm"], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.