basyx commited on
Commit
62d5e35
·
verified ·
1 Parent(s): 7732d87

Update auth/routes.py

Browse files
Files changed (1) hide show
  1. auth/routes.py +91 -127
auth/routes.py CHANGED
@@ -1,184 +1,148 @@
1
- """
2
- auth/routes.py
3
- Enterprise Authentication Router
4
- Basyx Whisper V10.1
5
- """
6
-
7
- from datetime import timedelta
8
-
9
  from fastapi import APIRouter, Depends, HTTPException, status
 
10
  from sqlalchemy.orm import Session
 
 
 
 
11
 
12
- from database import get_db
13
- from models import User
14
- from security import (
15
  hash_password,
16
  verify_password,
17
  create_access_token,
18
  ACCESS_TOKEN_EXPIRE_MINUTES,
19
  )
20
 
21
- router = APIRouter(prefix="/api/auth", tags=["Auth"])
22
-
23
 
24
- # ==========================================================
25
- # REQUEST SCHEMAS (inline to avoid dependency fragmentation)
26
- # ==========================================================
27
 
28
- from pydantic import BaseModel, EmailStr, Field
29
 
30
 
31
- class SignupRequest(BaseModel):
32
- email: EmailStr
33
- username: str = Field(min_length=3, max_length=50)
34
- password: str = Field(min_length=8, max_length=72)
35
 
 
 
 
 
 
 
36
 
37
- class LoginRequest(BaseModel):
38
- email: EmailStr
39
- password: str = Field(min_length=1, max_length=72)
40
 
41
-
42
- # ==========================================================
43
- # HELPERS
44
- # ==========================================================
45
-
46
- def get_user_by_email(db: Session, email: str):
47
- return db.query(User).filter(User.email == email.lower()).first()
48
-
49
-
50
- # ==========================================================
51
  # SIGNUP
52
- # ==========================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
- @router.post("/signup", status_code=201)
55
- def signup(payload: SignupRequest, db: Session = Depends(get_db)):
56
- email = payload.email.lower().strip()
57
 
58
- # ---- duplicate check
59
- existing = get_user_by_email(db, email)
60
  if existing:
61
  raise HTTPException(
62
  status_code=status.HTTP_409_CONFLICT,
63
- detail="Account already exists",
64
  )
65
 
66
- # ---- security: bcrypt safe boundary enforcement
67
- password = payload.password
68
- if len(password.encode("utf-8")) > 72:
69
- raise HTTPException(
70
- status_code=400,
71
- detail="Password exceeds bcrypt 72-byte limit",
72
  )
73
 
74
- # ---- create user
75
- user = User(
76
- email=email,
77
- username=payload.username.strip(),
78
- password_hash=hash_password(password),
79
- is_active=True,
80
- is_verified=False,
81
- )
82
 
83
- db.add(user)
84
- db.commit()
85
- db.refresh(user)
86
 
87
- return {
88
- "status": "created",
89
- "user": user.to_dict(),
90
- }
 
 
91
 
 
 
 
 
 
 
 
92
 
93
- # ==========================================================
 
94
  # LOGIN
95
- # ==========================================================
96
 
97
  @router.post("/login")
98
- def login(payload: LoginRequest, db: Session = Depends(get_db)):
99
- email = payload.email.lower().strip()
100
-
101
- user = get_user_by_email(db, email)
 
 
 
 
 
 
 
102
 
103
- # generic response (prevents enumeration)
104
  if not user:
105
  raise HTTPException(
106
  status_code=status.HTTP_401_UNAUTHORIZED,
107
  detail="Invalid credentials",
108
  )
109
 
110
- # verify password safely
111
- if not verify_password(payload.password, user.password_hash):
112
  raise HTTPException(
113
  status_code=status.HTTP_401_UNAUTHORIZED,
114
  detail="Invalid credentials",
115
  )
116
 
117
- if not user.is_active:
118
- raise HTTPException(
119
- status_code=status.HTTP_403_FORBIDDEN,
120
- detail="Account disabled",
121
- )
122
-
123
  token = create_access_token(
124
- data={
125
- "sub": str(user.id),
126
- "email": user.email,
127
- },
128
  expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES),
129
  )
130
 
131
  return {
132
  "access_token": token,
133
  "token_type": "bearer",
134
- "user": user.to_dict(),
135
  }
136
 
137
 
138
- # ==========================================================
139
- # ME (TOKEN VALIDATION ENTRYPOINT)
140
- # ==========================================================
141
-
142
- from fastapi import Header
143
- from security import decode_token
144
-
145
 
146
  @router.get("/me")
147
- def me(authorization: str = Header(None), db: Session = Depends(get_db)):
148
- if not authorization or not authorization.startswith("Bearer "):
149
- raise HTTPException(
150
- status_code=401,
151
- detail="Missing token",
152
- )
153
-
154
- token = authorization.split(" ")[1]
155
-
156
- payload = decode_token(token)
157
- if not payload:
158
- raise HTTPException(
159
- status_code=401,
160
- detail="Invalid token",
161
- )
162
-
163
- user_id = payload.get("sub")
164
- user = db.query(User).filter(User.id == user_id).first()
165
-
166
- if not user:
167
- raise HTTPException(
168
- status_code=404,
169
- detail="User not found",
170
- )
171
-
172
- return user.to_dict()
173
-
174
-
175
- # ==========================================================
176
- # HEALTH CHECK (AUTH MODULE)
177
- # ==========================================================
178
-
179
- @router.get("/health")
180
- def auth_health():
181
- return {
182
- "status": "auth_online",
183
- "module": "Basyx Auth V10.1",
184
- }
 
 
 
 
 
 
 
 
 
1
  from fastapi import APIRouter, Depends, HTTPException, status
2
+ from fastapi.security import OAuth2PasswordRequestForm
3
  from sqlalchemy.orm import Session
4
+ from datetime import timedelta
5
+
6
+ from auth.database import SessionLocal
7
+ from auth.models import User
8
 
9
+ from auth.security import (
 
 
10
  hash_password,
11
  verify_password,
12
  create_access_token,
13
  ACCESS_TOKEN_EXPIRE_MINUTES,
14
  )
15
 
16
+ import logging
 
17
 
18
+ logger = logging.getLogger("auth.routes")
 
 
19
 
20
+ router = APIRouter(prefix="/api/auth", tags=["Authentication"])
21
 
22
 
23
+ # =========================================================
24
+ # DB SESSION DEPENDENCY
25
+ # =========================================================
 
26
 
27
+ def get_db():
28
+ db = SessionLocal()
29
+ try:
30
+ yield db
31
+ finally:
32
+ db.close()
33
 
 
 
 
34
 
35
+ # =========================================================
 
 
 
 
 
 
 
 
 
36
  # SIGNUP
37
+ # =========================================================
38
+
39
+ @router.post("/signup")
40
+ def signup(
41
+ username: str,
42
+ password: str,
43
+ db: Session = Depends(get_db),
44
+ ):
45
+ """
46
+ Creates a new user account.
47
+ """
48
+
49
+ if not username or not password:
50
+ raise HTTPException(
51
+ status_code=status.HTTP_400_BAD_REQUEST,
52
+ detail="Username and password are required",
53
+ )
54
 
55
+ # Check existing user
56
+ existing = db.query(User).filter(User.username == username).first()
 
57
 
 
 
58
  if existing:
59
  raise HTTPException(
60
  status_code=status.HTTP_409_CONFLICT,
61
+ detail="Username already exists",
62
  )
63
 
64
+ try:
65
+ new_user = User(
66
+ username=username,
67
+ password_hash=hash_password(password),
 
 
68
  )
69
 
70
+ db.add(new_user)
71
+ db.commit()
72
+ db.refresh(new_user)
 
 
 
 
 
73
 
74
+ token = create_access_token(
75
+ data={"sub": str(new_user.id)}
76
+ )
77
 
78
+ return {
79
+ "message": "User created successfully",
80
+ "access_token": token,
81
+ "token_type": "bearer",
82
+ "user_id": new_user.id,
83
+ }
84
 
85
+ except Exception as e:
86
+ db.rollback()
87
+ logger.exception("Signup failed")
88
+ raise HTTPException(
89
+ status_code=500,
90
+ detail="Internal authentication error",
91
+ )
92
 
93
+
94
+ # =========================================================
95
  # LOGIN
96
+ # =========================================================
97
 
98
  @router.post("/login")
99
+ def login(
100
+ form_data: OAuth2PasswordRequestForm = Depends(),
101
+ db: Session = Depends(get_db),
102
+ ):
103
+ """
104
+ OAuth2 compatible login endpoint.
105
+ """
106
+
107
+ user = db.query(User).filter(
108
+ User.username == form_data.username
109
+ ).first()
110
 
 
111
  if not user:
112
  raise HTTPException(
113
  status_code=status.HTTP_401_UNAUTHORIZED,
114
  detail="Invalid credentials",
115
  )
116
 
117
+ if not verify_password(form_data.password, user.password_hash):
 
118
  raise HTTPException(
119
  status_code=status.HTTP_401_UNAUTHORIZED,
120
  detail="Invalid credentials",
121
  )
122
 
 
 
 
 
 
 
123
  token = create_access_token(
124
+ data={"sub": str(user.id)},
 
 
 
125
  expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES),
126
  )
127
 
128
  return {
129
  "access_token": token,
130
  "token_type": "bearer",
131
+ "user_id": user.id,
132
  }
133
 
134
 
135
+ # =========================================================
136
+ # GET CURRENT USER (DEBUG / INTERNAL USE)
137
+ # =========================================================
 
 
 
 
138
 
139
  @router.get("/me")
140
+ def me(
141
+ db: Session = Depends(get_db),
142
+ token_data=Depends(lambda: None), # placeholder if you wire get_current_user later
143
+ ):
144
+ """
145
+ Optional endpoint (safe stub).
146
+ Extend with get_current_user from security.py if needed.
147
+ """
148
+ return {"status": "auth module active"}