fix: resolve all CI lint and security issues
- ruff: 602→0 errors (428 auto-fixed, pyproject.toml ignores for FastAPI patterns) - bandit: skip B310 (urllib for vault file access is intentional) - Fixed SIM118 (dict.keys()→dict), PERF102, SIM113, SIM117 - Created pyproject.toml with ruff + bandit config - 285 tests still pass
This commit is contained in:
@@ -4,14 +4,14 @@
|
||||
# Revoked token JTIs persisted to data/revoked_tokens.json.
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import secrets
|
||||
import uuid
|
||||
import time
|
||||
import logging
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from jose import jwt, JWTError
|
||||
from typing import Optional
|
||||
|
||||
from jose import JWTError, jwt
|
||||
|
||||
logger = logging.getLogger("obsigate.auth.jwt")
|
||||
|
||||
@@ -76,7 +76,7 @@ def create_refresh_token(username: str) -> tuple:
|
||||
return jwt.encode(payload, get_secret_key(), algorithm=ALGORITHM), jti
|
||||
|
||||
|
||||
def decode_token(token: str) -> Optional[dict]:
|
||||
def decode_token(token: str) -> dict | None:
|
||||
"""Decode and validate a JWT. Returns None if invalid/expired."""
|
||||
try:
|
||||
return jwt.decode(token, get_secret_key(), algorithms=[ALGORITHM])
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
# FastAPI dependencies for authentication and authorization.
|
||||
# Reads JWT from Authorization header OR access_token cookie.
|
||||
|
||||
import os
|
||||
import logging
|
||||
from fastapi import Request, HTTPException, Depends
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from typing import Optional
|
||||
import os
|
||||
|
||||
from fastapi import Depends, HTTPException, Request
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
|
||||
from .jwt_handler import decode_token
|
||||
from .user_store import get_user
|
||||
@@ -26,8 +26,8 @@ def is_auth_enabled() -> bool:
|
||||
|
||||
def get_current_user(
|
||||
request: Request,
|
||||
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
|
||||
) -> Optional[dict]:
|
||||
credentials: HTTPAuthorizationCredentials | None = Depends(security),
|
||||
) -> dict | None:
|
||||
"""Extract and validate the current user from JWT.
|
||||
|
||||
Reads token from Authorization header first, falls back to access_token cookie.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# Parameters: time_cost=2, memory_cost=64MB, parallelism=2
|
||||
|
||||
from argon2 import PasswordHasher
|
||||
from argon2.exceptions import VerifyMismatchError, VerificationError
|
||||
from argon2.exceptions import VerificationError, VerifyMismatchError
|
||||
|
||||
ph = PasswordHasher(
|
||||
time_cost=2,
|
||||
|
||||
+34
-21
@@ -2,24 +2,37 @@
|
||||
# All /api/auth/* endpoints: login, logout, refresh, me, change-password,
|
||||
# and admin user CRUD.
|
||||
|
||||
import re
|
||||
import logging
|
||||
from fastapi import APIRouter, HTTPException, Response, Request, Depends
|
||||
import re
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, Response
|
||||
from pydantic import BaseModel, validator
|
||||
from typing import List, Optional
|
||||
|
||||
from backend.ratelimit import is_rate_limited
|
||||
from backend.ratelimit import record_failure as rl_record_failure
|
||||
from backend.ratelimit import record_success as rl_record_success
|
||||
|
||||
from .user_store import (
|
||||
get_user, get_all_users, create_user, update_user, delete_user,
|
||||
record_login_success, record_login_failure, is_locked, has_users,
|
||||
)
|
||||
from .jwt_handler import (
|
||||
create_access_token, create_refresh_token, decode_token,
|
||||
revoke_token, is_token_revoked,
|
||||
ACCESS_TOKEN_EXPIRE_SECONDS,
|
||||
create_access_token,
|
||||
create_refresh_token,
|
||||
decode_token,
|
||||
is_token_revoked,
|
||||
revoke_token,
|
||||
)
|
||||
from .middleware import is_auth_enabled, require_admin, require_auth
|
||||
from .password import hash_password, verify_password
|
||||
from .user_store import (
|
||||
create_user,
|
||||
delete_user,
|
||||
get_all_users,
|
||||
get_user,
|
||||
has_users,
|
||||
is_locked,
|
||||
record_login_failure,
|
||||
record_login_success,
|
||||
update_user,
|
||||
)
|
||||
from .middleware import require_auth, require_admin, is_auth_enabled
|
||||
from .password import verify_password, hash_password
|
||||
from backend.ratelimit import is_rate_limited, record_failure as rl_record_failure, record_success as rl_record_success
|
||||
|
||||
logger = logging.getLogger("obsigate.auth.router")
|
||||
|
||||
@@ -48,9 +61,9 @@ class ChangePasswordRequest(BaseModel):
|
||||
class CreateUserRequest(BaseModel):
|
||||
username: str
|
||||
password: str
|
||||
display_name: Optional[str] = None
|
||||
display_name: str | None = None
|
||||
role: str = "user"
|
||||
vaults: List[str] = []
|
||||
vaults: list[str] = []
|
||||
|
||||
@validator("username")
|
||||
def username_valid(cls, v):
|
||||
@@ -66,11 +79,11 @@ class CreateUserRequest(BaseModel):
|
||||
|
||||
|
||||
class UpdateUserRequest(BaseModel):
|
||||
display_name: Optional[str] = None
|
||||
vaults: Optional[List[str]] = None
|
||||
active: Optional[bool] = None
|
||||
password: Optional[str] = None
|
||||
role: Optional[str] = None
|
||||
display_name: str | None = None
|
||||
vaults: list[str] | None = None
|
||||
active: bool | None = None
|
||||
password: str | None = None
|
||||
role: str | None = None
|
||||
|
||||
|
||||
# ── Public endpoints ──────────────────────────────────────────────────
|
||||
@@ -249,8 +262,8 @@ async def get_me(current_user=Depends(require_auth)):
|
||||
|
||||
class UpdateMeRequest(BaseModel):
|
||||
"""Fields the user can update on their own profile."""
|
||||
display_name: Optional[str] = None
|
||||
language: Optional[str] = None
|
||||
display_name: str | None = None
|
||||
language: str | None = None
|
||||
|
||||
|
||||
@router.patch("/me")
|
||||
|
||||
@@ -4,12 +4,11 @@
|
||||
# via tmp+rename to prevent corruption on crash.
|
||||
|
||||
import json
|
||||
import uuid
|
||||
import logging
|
||||
import shutil
|
||||
import uuid
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from typing import Optional, List
|
||||
|
||||
from .password import hash_password
|
||||
|
||||
@@ -54,12 +53,12 @@ def has_users() -> bool:
|
||||
return bool(_read()["users"])
|
||||
|
||||
|
||||
def get_user(username: str) -> Optional[dict]:
|
||||
def get_user(username: str) -> dict | None:
|
||||
"""Get a user by username. Returns None if not found."""
|
||||
return _read()["users"].get(username)
|
||||
|
||||
|
||||
def get_all_users() -> List[dict]:
|
||||
def get_all_users() -> list[dict]:
|
||||
"""Get all users WITHOUT password_hash (safe for API responses)."""
|
||||
users = _read()["users"]
|
||||
return [
|
||||
@@ -72,8 +71,8 @@ def create_user(
|
||||
username: str,
|
||||
password: str,
|
||||
role: str = "user",
|
||||
vaults: Optional[List[str]] = None,
|
||||
display_name: Optional[str] = None,
|
||||
vaults: list[str] | None = None,
|
||||
display_name: str | None = None,
|
||||
) -> dict:
|
||||
"""Create a new user. Raises ValueError if username already taken."""
|
||||
data = _read()
|
||||
|
||||
Reference in New Issue
Block a user