mirror of
https://github.com/furyhawk/ai_agent.git
synced 2026-07-21 02:05:42 +00:00
- Implemented `conversation-store` for managing conversations and messages. - Created `file-preview-store` to handle file preview state. - Added `sidebar-store` for sidebar visibility management. - Developed `theme-store` for theme persistence and management. - Introduced `kb-selection-store` for managing active knowledge base selections with persistence. chore: define API and chat types - Added types for API responses, authentication, chat messages, conversations, and projects. - Defined interfaces for various entities including users, sessions, and message ratings. build: configure TypeScript and testing setup - Set up `tsconfig.json` for TypeScript configuration. - Created `vitest.config.ts` for testing configuration with Vitest. - Added `vitest.setup.ts` for global test setup including mocks for Next.js router and media queries. - Configured Vercel deployment settings in `vercel.json`.
104 lines
3.2 KiB
Python
104 lines
3.2 KiB
Python
"""Security utilities for JWT authentication."""
|
|
|
|
from datetime import UTC, datetime, timedelta
|
|
from typing import Any
|
|
|
|
import bcrypt
|
|
import jwt
|
|
|
|
from app.core.config import settings
|
|
|
|
|
|
def create_access_token(
|
|
subject: str | Any,
|
|
expires_delta: timedelta | None = None,
|
|
) -> str:
|
|
"""Create a JWT access token."""
|
|
if expires_delta:
|
|
expire = datetime.now(UTC) + expires_delta
|
|
else:
|
|
expire = datetime.now(UTC) + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
|
|
|
to_encode = {"exp": expire, "sub": str(subject), "type": "access"}
|
|
return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
|
|
|
|
|
|
def create_refresh_token(
|
|
subject: str | Any,
|
|
expires_delta: timedelta | None = None,
|
|
) -> str:
|
|
"""Create a JWT refresh token."""
|
|
if expires_delta:
|
|
expire = datetime.now(UTC) + expires_delta
|
|
else:
|
|
expire = datetime.now(UTC) + timedelta(minutes=settings.REFRESH_TOKEN_EXPIRE_MINUTES)
|
|
|
|
to_encode = {"exp": expire, "sub": str(subject), "type": "refresh"}
|
|
return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
|
|
|
|
|
|
def verify_token(token: str) -> dict[str, Any] | None:
|
|
"""Verify a JWT token and return payload."""
|
|
try:
|
|
payload = jwt.decode(
|
|
token,
|
|
settings.SECRET_KEY,
|
|
algorithms=[settings.ALGORITHM],
|
|
)
|
|
return payload
|
|
except jwt.PyJWTError:
|
|
return None
|
|
|
|
|
|
def create_password_reset_token(
|
|
subject: str | Any,
|
|
expires_delta: timedelta | None = None,
|
|
) -> str:
|
|
"""Single-use JWT for password reset.
|
|
|
|
Short-lived (1h default). The `type` claim distinguishes it from access /
|
|
refresh / magic-link tokens — a stolen reset token can't be used as an
|
|
access token.
|
|
"""
|
|
expire = datetime.now(UTC) + (expires_delta or timedelta(hours=1))
|
|
to_encode = {"exp": expire, "sub": str(subject), "type": "password_reset"}
|
|
return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
|
|
|
|
|
|
def create_magic_link_token(
|
|
subject: str | Any,
|
|
expires_delta: timedelta | None = None,
|
|
) -> str:
|
|
"""Sign-in-by-email JWT. Short-lived (15 min default)."""
|
|
expire = datetime.now(UTC) + (expires_delta or timedelta(minutes=15))
|
|
to_encode = {"exp": expire, "sub": str(subject), "type": "magic_link"}
|
|
return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
|
|
|
|
|
|
def verify_special_token(token: str, expected_type: str) -> dict[str, Any] | None:
|
|
"""Verify a non-access JWT (password_reset, magic_link) and require a
|
|
specific `type` claim. Returns payload on success, None otherwise.
|
|
"""
|
|
payload = verify_token(token)
|
|
if payload is None:
|
|
return None
|
|
if payload.get("type") != expected_type:
|
|
return None
|
|
return payload
|
|
|
|
|
|
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
|
"""Verify a password against a hash."""
|
|
return bcrypt.checkpw(
|
|
plain_password.encode("utf-8"),
|
|
hashed_password.encode("utf-8"),
|
|
)
|
|
|
|
|
|
def get_password_hash(password: str) -> str:
|
|
"""Hash a password."""
|
|
return bcrypt.hashpw(
|
|
password.encode("utf-8"),
|
|
bcrypt.gensalt(),
|
|
).decode("utf-8")
|