feat: security hardening — SSRF guard, template sandbox timeout, webhook log prune, auth & backup polish
- Add outbound URL validation (SSRF) for webhook/Discord/Slack/ntfy/Matrix dispatch - Template renderer: input/output caps and thread-based render timeout - Webhook log filter: strip Authorization/signature/token-like headers; atomic prune - Auth/JWT/backup/config tightening; misc frontend UX fixes
This commit is contained in:
@@ -69,8 +69,8 @@ async def setup(request: Request, body: SetupRequest, session: AsyncSession = De
|
||||
await session.refresh(user)
|
||||
|
||||
return TokenResponse(
|
||||
access_token=create_access_token(user.id, user.role),
|
||||
refresh_token=create_refresh_token(user.id),
|
||||
access_token=create_access_token(user.id, user.role, user.token_version),
|
||||
refresh_token=create_refresh_token(user.id, user.token_version),
|
||||
)
|
||||
|
||||
|
||||
@@ -83,29 +83,33 @@ async def login(request: Request, body: LoginRequest, session: AsyncSession = De
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid username or password")
|
||||
|
||||
return TokenResponse(
|
||||
access_token=create_access_token(user.id, user.role),
|
||||
refresh_token=create_refresh_token(user.id),
|
||||
access_token=create_access_token(user.id, user.role, user.token_version),
|
||||
refresh_token=create_refresh_token(user.id, user.token_version),
|
||||
)
|
||||
|
||||
|
||||
@router.post("/refresh", response_model=TokenResponse)
|
||||
async def refresh(body: RefreshRequest, session: AsyncSession = Depends(get_session)):
|
||||
@limiter.limit("10/minute")
|
||||
async def refresh(request: Request, body: RefreshRequest, session: AsyncSession = Depends(get_session)):
|
||||
import jwt as pyjwt
|
||||
try:
|
||||
payload = decode_token(body.refresh_token)
|
||||
if payload.get("type") != "refresh":
|
||||
raise HTTPException(status_code=401, detail="Invalid token type")
|
||||
user_id = int(payload["sub"])
|
||||
token_version = int(payload.get("ver", 1))
|
||||
except (pyjwt.PyJWTError, KeyError, ValueError) as exc:
|
||||
raise HTTPException(status_code=401, detail="Invalid refresh token") from exc
|
||||
|
||||
user = await session.get(User, user_id)
|
||||
if not user:
|
||||
raise HTTPException(status_code=401, detail="User not found")
|
||||
if token_version != user.token_version:
|
||||
raise HTTPException(status_code=401, detail="Refresh token revoked")
|
||||
|
||||
return TokenResponse(
|
||||
access_token=create_access_token(user.id, user.role),
|
||||
refresh_token=create_refresh_token(user.id),
|
||||
access_token=create_access_token(user.id, user.role, user.token_version),
|
||||
refresh_token=create_refresh_token(user.id, user.token_version),
|
||||
)
|
||||
|
||||
|
||||
@@ -130,6 +134,7 @@ async def change_password(
|
||||
if len(body.new_password) < 8:
|
||||
raise HTTPException(status_code=400, detail="New password must be at least 8 characters")
|
||||
user.hashed_password = _hash_password(body.new_password)
|
||||
user.token_version = (user.token_version or 1) + 1
|
||||
session.add(user)
|
||||
await session.commit()
|
||||
return {"success": True}
|
||||
|
||||
Reference in New Issue
Block a user