Skip to content

Commit 4d2a6ad

Browse files
committed
something resembling auth
1 parent 19ba3aa commit 4d2a6ad

37 files changed

+312
-307
lines changed

backend/_flask/flask.py

Lines changed: 0 additions & 65 deletions
This file was deleted.

backend/_flask/login.py

Lines changed: 0 additions & 27 deletions
This file was deleted.

backend/_flask/signup.py

Lines changed: 0 additions & 52 deletions
This file was deleted.

backend/app/api/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
from fastapi import APIRouter
22

3-
api = APIRouter()
3+
public_api = APIRouter()
4+
private_api = APIRouter()
45

6+
from .auth import *
57
from .apps import *
68
from .frames import *
79
from .log import *
810
from .repositories import *
911
from .settings import *
1012
from .ssh import *
1113
from .templates import *
14+
from .users import *
1215

backend/app/api/apps.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,19 @@
1111
from app.database import get_db
1212
from app.models.apps import get_app_configs, get_one_app_sources
1313
from app.models.settings import get_settings_dict
14-
from . import api
14+
from . import private_api
1515

16-
@api.get("/apps")
16+
@private_api.get("/apps")
1717
async def api_apps_list(db: Session = Depends(get_db)):
1818
return JSONResponse(content={"apps": get_app_configs()}, status_code=200)
1919

2020

21-
@api.get("/apps/source")
21+
@private_api.get("/apps/source")
2222
async def api_apps_source(keyword: str = None, db: Session = Depends(get_db)):
2323
return JSONResponse(content=get_one_app_sources(keyword), status_code=200)
2424

2525

26-
@api.post("/apps/validate_source")
26+
@private_api.post("/apps/validate_source")
2727
async def validate_python_frame_source(request: Request):
2828
data = await request.json()
2929
file = data.get('file')
@@ -45,7 +45,7 @@ async def validate_python_frame_source(request: Request):
4545
return JSONResponse(content={"errors": errors}, status_code=200)
4646

4747

48-
@api.post("/apps/enhance_source")
48+
@private_api.post("/apps/enhance_source")
4949
async def enhance_python_frame_source(request: Request, db: Session = Depends(get_db)):
5050
data = await request.json()
5151
source = data.get('source')

backend/app/api/auth.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import datetime
2+
from typing import Optional
3+
4+
from fastapi import Depends, HTTPException, status
5+
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
6+
from jose import jwt, JWTError
7+
from pydantic import BaseModel
8+
from sqlalchemy.orm import Session
9+
10+
from app.config import get_config
11+
from app.models.user import User
12+
from app.database import get_db
13+
from werkzeug.security import generate_password_hash, check_password_hash
14+
15+
from . import public_api
16+
17+
config = get_config()
18+
SECRET_KEY = config.SECRET_KEY
19+
ALGORITHM = "HS256"
20+
ACCESS_TOKEN_EXPIRE_MINUTES = 60
21+
22+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/login")
23+
24+
class Token(BaseModel):
25+
access_token: str
26+
token_type: str
27+
28+
class UserLogin(BaseModel):
29+
email: str
30+
password: str
31+
32+
class UserSignup(BaseModel):
33+
email: str
34+
password: str
35+
password2: str
36+
newsletter: bool = False
37+
38+
def create_access_token(data: dict, expires_delta: Optional[datetime.timedelta] = None):
39+
to_encode = data.copy()
40+
if expires_delta:
41+
expire = datetime.datetime.utcnow() + expires_delta
42+
else:
43+
expire = datetime.datetime.utcnow() + datetime.timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
44+
to_encode.update({"exp": expire})
45+
# Encode the token using python-jose
46+
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
47+
return encoded_jwt
48+
49+
async def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
50+
credentials_exception = HTTPException(
51+
status_code=status.HTTP_401_UNAUTHORIZED,
52+
detail="Could not validate credentials",
53+
headers={"WWW-Authenticate": "Bearer"},
54+
)
55+
try:
56+
# Decode token using python-jose
57+
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
58+
email: str = payload.get("sub")
59+
if email is None:
60+
raise credentials_exception
61+
except JWTError:
62+
raise credentials_exception
63+
64+
user = db.query(User).filter(User.email == email).first()
65+
if user is None:
66+
raise credentials_exception
67+
return user
68+
69+
@public_api.post("/login", response_model=Token)
70+
def login(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
71+
email = form_data.username
72+
password = form_data.password
73+
user = db.query(User).filter_by(email=email).first()
74+
if user is None or not check_password_hash(user.password, password):
75+
raise HTTPException(status_code=401, detail="Invalid email or password")
76+
77+
access_token_expires = datetime.timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
78+
access_token = create_access_token(data={"sub": user.email}, expires_delta=access_token_expires)
79+
return {"access_token": access_token, "token_type": "bearer"}
80+
81+
@public_api.post("/signup")
82+
def signup(data: UserSignup, db: Session = Depends(get_db)):
83+
# Check if there is already a user registered (one-user system)
84+
if db.query(User).first() is not None:
85+
raise HTTPException(status_code=400, detail="Only one user is allowed. Please login!")
86+
87+
if not data.email:
88+
raise HTTPException(status_code=400, detail="Email is required.")
89+
if not data.password:
90+
raise HTTPException(status_code=400, detail="Password is required.")
91+
if data.password != data.password2:
92+
raise HTTPException(status_code=400, detail="Passwords do not match.")
93+
94+
if db.query(User).filter_by(email=data.email).first():
95+
raise HTTPException(status_code=400, detail="Email already in use.")
96+
97+
# Handle newsletter signup if needed
98+
99+
user = User(email=data.email)
100+
user.password = generate_password_hash(data.password)
101+
db.add(user)
102+
db.commit()
103+
104+
# Auto-login after signup:
105+
access_token_expires = datetime.timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
106+
access_token = create_access_token(data={"sub": user.email}, expires_delta=access_token_expires)
107+
return {"success": True, "access_token": access_token, "token_type": "bearer"}

0 commit comments

Comments
 (0)