mirror of
https://github.com/yogeshojha/rengine.git
synced 2026-09-29 21:24:54 +02:00
cleanup precommit hooks
This commit is contained in:
@@ -45,12 +45,12 @@ body:
|
||||
- **Docker compose**: 2.15.1
|
||||
- **Browser**: Microsoft Edge 112.0.1722.58
|
||||
value: |
|
||||
- reNgine:
|
||||
- OS:
|
||||
- Python:
|
||||
- Docker Engine:
|
||||
- Docker Compose:
|
||||
- Browser:
|
||||
- reNgine:
|
||||
- OS:
|
||||
- Python:
|
||||
- Docker Engine:
|
||||
- Docker Compose:
|
||||
- Browser:
|
||||
render: markdown
|
||||
validations:
|
||||
required: true
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
|
||||
# Security Policy
|
||||
|
||||
> **[IMPORTANT NOTICE - February 9, 2025]**
|
||||
> **[IMPORTANT NOTICE - February 9, 2025]**
|
||||
> reNgine is currently undergoing a major refactoring to address all XSS-related vulnerabilities. While we are committed to security, we are temporarily suspending new XSS vulnerability reports until this refactoring is complete. We will continue to accept and investigate all other types of security vulnerabilities. Thank you for your understanding and continued support in making reNgine more secure.
|
||||
>
|
||||
> Please note that most reported XSS vulnerabilities in reNgine affect on-premise installations with limited exploitability. Nevertheless, we are committed to fixing these issues systematically through our ongoing refactoring effort.
|
||||
|
||||
@@ -23,35 +23,35 @@ jobs:
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
const author = context.payload.sender.login;
|
||||
|
||||
|
||||
if (context.eventName === 'issues' && context.payload.action === 'opened') {
|
||||
const issueTitle = context.payload.issue.title.toLowerCase();
|
||||
let commentBody;
|
||||
|
||||
|
||||
if (issueTitle.includes('feat')) {
|
||||
commentBody = `Hey @${author}! 🚀 Thanks for this exciting feature idea!
|
||||
|
||||
We love seeing fresh concepts that could take reNgine to the next level. 🌟
|
||||
|
||||
|
||||
To help us understand your vision better, could you:
|
||||
|
||||
|
||||
📝 Provide a detailed description of the feature
|
||||
🎯 Explain the problem it solves or the value it adds
|
||||
💡 Share any implementation ideas you might have
|
||||
|
||||
|
||||
Your input is invaluable in shaping the future of reNgine. Let's innovate together! 💪`;
|
||||
} else {
|
||||
commentBody = `Hey @${author}! 👋 Thanks for flagging this bug! 🐛🔍
|
||||
|
||||
You're our superhero bug hunter! 🦸♂️🦸♀️ Before we suit up to squash this bug, could you please:
|
||||
|
||||
|
||||
📚 Double-check our documentation: https://rengine.wiki
|
||||
🕵️ Make sure it's not a known issue
|
||||
📝 Provide all the juicy details about this sneaky bug
|
||||
|
||||
|
||||
Once again - thanks for your vigilance! 🛠️🚀`;
|
||||
}
|
||||
|
||||
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner,
|
||||
@@ -74,8 +74,8 @@ jobs:
|
||||
if (isPRMerged) {
|
||||
commentBody = `Holy smokes! 🤯 You've just made reNgine even more awesome!
|
||||
|
||||
Your code is now part of the reNgine hall of fame. 🏆
|
||||
|
||||
Your code is now part of the reNgine hall of fame. 🏆
|
||||
|
||||
Keep the cool ideas coming - maybe next time you'll break the internet! 💻💥
|
||||
|
||||
Virtual high fives all around! 🙌`;
|
||||
@@ -83,9 +83,9 @@ jobs:
|
||||
commentBody = `Hey, thanks for your contribution! 🙏
|
||||
|
||||
We appreciate the time and effort you put into this PR. Sadly this is not the right fit for reNgine at the moment.
|
||||
|
||||
|
||||
While we couldn't merge it this time, we value your interest in improving reNgine.
|
||||
|
||||
|
||||
Feel free to reach out if you have any questions. Thanks again!`;
|
||||
}
|
||||
|
||||
@@ -95,4 +95,4 @@ jobs:
|
||||
repo,
|
||||
body: commentBody
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ name: 🏗️ Build Docker image for pull request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
branches:
|
||||
- master
|
||||
- release/*
|
||||
|
||||
|
||||
@@ -55,4 +55,4 @@ jobs:
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
+1
-1
@@ -14,4 +14,4 @@ docker-compose.override.yml
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.temp
|
||||
.cache/
|
||||
.cache/
|
||||
|
||||
Vendored
+4
-4
@@ -3,12 +3,12 @@
|
||||
"**/__pycache__": true,
|
||||
"**/*.pyc": true,
|
||||
"**/*.pyo": true,
|
||||
".ruff_cache": true,
|
||||
".ruff_cache": true
|
||||
},
|
||||
"search.exclude": {
|
||||
"**/__pycache__": true,
|
||||
".venv": true,
|
||||
"venv": true,
|
||||
".ruff_cache": true,
|
||||
},
|
||||
}
|
||||
".ruff_cache": true
|
||||
}
|
||||
}
|
||||
|
||||
+14
-14
@@ -202,7 +202,7 @@
|
||||
* Fix uninitialised variable cmd in custom_subdomain_tools by @cpandya2909 in https://github.com/yogeshojha/rengine/pull/1207
|
||||
* [FIX] security: OS Command Injection vulnerability (x2) #1219 by @0xtejas in https://github.com/yogeshojha/rengine/pull/1227
|
||||
|
||||
### New Contributors :rocket:
|
||||
### New Contributors :rocket:
|
||||
* @yarysp made their first contribution in https://github.com/yogeshojha/rengine/pull/1199
|
||||
* @jostasik made their first contribution in https://github.com/yogeshojha/rengine/pull/1226
|
||||
* @cpandya2909 made their first contribution in https://github.com/yogeshojha/rengine/pull/1207
|
||||
@@ -278,19 +278,19 @@
|
||||
* fix(tool_arsenal): incorrect regex version numbers by @AnonymousWP in https://github.com/yogeshojha/rengine/pull/1086
|
||||
|
||||
### New Contributors
|
||||
* @luizmlo made their first contribution in https://github.com/yogeshojha/rengine/pull/1029 :partying_face:
|
||||
* @aqhmal made their first contribution in https://github.com/yogeshojha/rengine/pull/1021 :partying_face:
|
||||
* @C0wnuts made their first contribution in https://github.com/yogeshojha/rengine/pull/973 :partying_face:
|
||||
* @ocervell made their first contribution in https://github.com/yogeshojha/rengine/pull/1058 :partying_face:
|
||||
* @Vijayragha1 made their first contribution in https://github.com/yogeshojha/rengine/pull/1074 :partying_face:
|
||||
* @jxdv made their first contribution in https://github.com/yogeshojha/rengine/pull/1081 :partying_face:
|
||||
* @SeanOverton made their first contribution in https://github.com/yogeshojha/rengine/pull/1106 :partying_face:
|
||||
* @SubGlitch1 made their first contribution in https://github.com/yogeshojha/rengine/pull/1107 :partying_face:
|
||||
* @Linuxinet made their first contribution in https://github.com/yogeshojha/rengine/pull/1112 :partying_face:
|
||||
* @luizmlo made their first contribution in https://github.com/yogeshojha/rengine/pull/1029 :partying_face:
|
||||
* @aqhmal made their first contribution in https://github.com/yogeshojha/rengine/pull/1021 :partying_face:
|
||||
* @C0wnuts made their first contribution in https://github.com/yogeshojha/rengine/pull/973 :partying_face:
|
||||
* @ocervell made their first contribution in https://github.com/yogeshojha/rengine/pull/1058 :partying_face:
|
||||
* @Vijayragha1 made their first contribution in https://github.com/yogeshojha/rengine/pull/1074 :partying_face:
|
||||
* @jxdv made their first contribution in https://github.com/yogeshojha/rengine/pull/1081 :partying_face:
|
||||
* @SeanOverton made their first contribution in https://github.com/yogeshojha/rengine/pull/1106 :partying_face:
|
||||
* @SubGlitch1 made their first contribution in https://github.com/yogeshojha/rengine/pull/1107 :partying_face:
|
||||
* @Linuxinet made their first contribution in https://github.com/yogeshojha/rengine/pull/1112 :partying_face:
|
||||
|
||||
**Full Changelog**: https://github.com/yogeshojha/rengine/compare/v2.0.1...v2.0.2
|
||||
|
||||
Once again excellent work on reNgine v2.0.2 by @AnonymousWP, @psyray, @ocervell and everybody else! :rocket:
|
||||
Once again excellent work on reNgine v2.0.2 by @AnonymousWP, @psyray, @ocervell and everybody else! :rocket:
|
||||
|
||||
## 2.0.1
|
||||
|
||||
@@ -299,7 +299,7 @@ Once again excellent work on reNgine v2.0.2 by @AnonymousWP, @psyray, @ocervell
|
||||
|
||||
2.0.1 fixes a ton of issues in reNgine 2.0.
|
||||
|
||||
Fixes:
|
||||
Fixes:
|
||||
1. Prevent duplicating Nuclei vulns for subdomain #1012 @psyray
|
||||
2. Fixes for empty subdomain returned during nuclei scan #1011 @psyray
|
||||
3. Add all the missing slug in scanEngine view & other places #1005 @psyray
|
||||
@@ -314,13 +314,13 @@ Fixes:
|
||||
11. Add stack trace into make logs if DEBUG True #994 @psyray
|
||||
12. Fix dirfuzz base64 name display #993 #992 @psyray
|
||||
13. Fix target subdomains list not loading #991 @psyray
|
||||
14. Change WORDLIST constant value #987, fixes #986@psyray
|
||||
14. Change WORDLIST constant value #987, fixes #986@psyray
|
||||
15. fix(notification_settings): submitting results in error 502 #981 fixes #970 @psyray
|
||||
16. Fixes with documentation and installation/update/uninstall scripts @anonymousWP
|
||||
17. Fix file directory popup not showing in detailed scan #912 @psyray
|
||||
|
||||
|
||||
@AnonymousWP and @psyray have been phenomenal in fixing these bugs. Thanks to both of you! :heart: :rocket:
|
||||
@AnonymousWP and @psyray have been phenomenal in fixing these bugs. Thanks to both of you! :heart: :rocket:
|
||||
|
||||
|
||||
## 2.0.0
|
||||
|
||||
+1
-1
@@ -126,4 +126,4 @@ dmypy.json
|
||||
.DS_Store
|
||||
|
||||
# FastAPI specific
|
||||
.uvicorn_cache/
|
||||
.uvicorn_cache/
|
||||
|
||||
@@ -15,4 +15,4 @@ RUN uv sync --no-install-project
|
||||
# Expose port
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["uv", "run", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
|
||||
CMD ["uv", "run", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
from typing import Annotated
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import Depends, HTTPException, status, Request
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from fastapi import Depends, HTTPException, Request, status
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlmodel import select
|
||||
|
||||
from app.core.security import decode_token
|
||||
from app.core.database import get_session
|
||||
from app.core.security import decode_token
|
||||
from app.models.user import User
|
||||
|
||||
|
||||
security = HTTPBearer(auto_error=False)
|
||||
|
||||
|
||||
@@ -70,12 +69,12 @@ async def get_current_user(
|
||||
|
||||
try:
|
||||
user_id = UUID(user_id_str)
|
||||
except ValueError:
|
||||
except ValueError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid user ID in token",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
) from e
|
||||
|
||||
# Fetch user
|
||||
result = await session.execute(select(User).where(User.id == user_id))
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from app.api.v1 import auth, users
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
from typing import Annotated
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Response, Request
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlmodel import select
|
||||
|
||||
from app.api.deps import CurrentSuperuser, CurrentUser
|
||||
from app.config import settings
|
||||
from app.core.database import get_session
|
||||
from app.core.security import (
|
||||
verify_password,
|
||||
hash_password,
|
||||
create_access_token,
|
||||
create_refresh_token,
|
||||
decode_token,
|
||||
hash_password,
|
||||
verify_password,
|
||||
)
|
||||
from app.config import settings
|
||||
from app.core.database import get_session
|
||||
from app.models.user import User, UserCreate, UserRead
|
||||
from app.schemas.auth import LoginRequest, TokenResponse
|
||||
from app.api.deps import CurrentUser, CurrentSuperuser
|
||||
|
||||
|
||||
router = APIRouter(prefix="/auth", tags=["authentication"])
|
||||
|
||||
@@ -127,12 +126,12 @@ async def refresh_access_token(
|
||||
|
||||
try:
|
||||
user_id = UUID(user_id_str)
|
||||
except ValueError:
|
||||
except ValueError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid user ID in token",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
) from e
|
||||
|
||||
result = await session.execute(select(User).where(User.id == user_id))
|
||||
user = result.scalar_one_or_none()
|
||||
@@ -180,7 +179,7 @@ async def get_current_user_info(current_user: CurrentUser):
|
||||
async def register_user(
|
||||
user_in: UserCreate,
|
||||
session: Annotated[AsyncSession, Depends(get_session)],
|
||||
current_user: CurrentSuperuser,
|
||||
current_user: CurrentSuperuser, # noqa: ARG001
|
||||
):
|
||||
"""
|
||||
Register a new user. **Admin only**.
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
from typing import Annotated
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlmodel import select
|
||||
|
||||
from app.api.deps import CurrentSuperuser
|
||||
from app.core.database import get_session
|
||||
from app.models.user import User, UserRead
|
||||
from app.api.deps import CurrentSuperuser
|
||||
|
||||
|
||||
router = APIRouter(prefix="/users", tags=["users"])
|
||||
|
||||
@@ -15,7 +15,7 @@ router = APIRouter(prefix="/users", tags=["users"])
|
||||
@router.get("/", response_model=list[UserRead])
|
||||
async def list_users(
|
||||
session: Annotated[AsyncSession, Depends(get_session)],
|
||||
current_user: CurrentSuperuser, # Only superusers can list all users
|
||||
current_user: CurrentSuperuser, # noqa: ARG001
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
):
|
||||
@@ -26,29 +26,26 @@ async def list_users(
|
||||
# Get paginated users
|
||||
query = select(User).offset(skip).limit(limit)
|
||||
result = await session.execute(query)
|
||||
users = result.scalars().all()
|
||||
|
||||
return users
|
||||
return result.scalars().all()
|
||||
|
||||
|
||||
@router.get("/{user_id}", response_model=UserRead)
|
||||
async def get_user(
|
||||
user_id: str,
|
||||
session: Annotated[AsyncSession, Depends(get_session)],
|
||||
current_user: CurrentSuperuser, # superuser only
|
||||
current_user: CurrentSuperuser, # noqa: ARG001
|
||||
):
|
||||
"""
|
||||
Get user by ID. **Admin only**.
|
||||
"""
|
||||
from uuid import UUID
|
||||
|
||||
try:
|
||||
uuid_id = UUID(user_id)
|
||||
except ValueError:
|
||||
except ValueError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Invalid user ID format",
|
||||
)
|
||||
) from e
|
||||
|
||||
result = await session.execute(select(User).where(User.id == uuid_id))
|
||||
user = result.scalar_one_or_none()
|
||||
@@ -71,15 +68,14 @@ async def delete_user(
|
||||
"""
|
||||
Delete user by ID. **Admin only**.
|
||||
"""
|
||||
from uuid import UUID
|
||||
|
||||
try:
|
||||
uuid_id = UUID(user_id)
|
||||
except ValueError:
|
||||
except ValueError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Invalid user ID format",
|
||||
)
|
||||
) from e
|
||||
|
||||
# Prevent self-deletion
|
||||
if uuid_id == current_user.id:
|
||||
|
||||
+31
-3
@@ -1,6 +1,8 @@
|
||||
from pydantic_settings import BaseSettings
|
||||
from functools import lru_cache
|
||||
|
||||
from pydantic import field_validator
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
################################################################
|
||||
@@ -29,7 +31,7 @@ class Settings(BaseSettings):
|
||||
###############################################################
|
||||
# Auth configs
|
||||
###############################################################
|
||||
SECRET_KEY: str = "change-me-in-production-use-openssl-rand-hex-32"
|
||||
SECRET_KEY: str = "change-me-in-production-use-openssl-rand-hex-32" # noqa: S105
|
||||
ALGORITHM: str = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
|
||||
REFRESH_TOKEN_EXPIRE_DAYS: int = 7
|
||||
@@ -39,7 +41,33 @@ class Settings(BaseSettings):
|
||||
###############################################################
|
||||
ADMIN_EMAIL: str = "admin@rengine.local"
|
||||
ADMIN_USERNAME: str = "rengine"
|
||||
ADMIN_PASSWORD: str = "rengine@123"
|
||||
ADMIN_PASSWORD: str = "rengine@123" # noqa: S105
|
||||
|
||||
@field_validator("SECRET_KEY")
|
||||
@classmethod
|
||||
def validate_secret_key(cls, v: str, info) -> str:
|
||||
"""Ensure SECRET_KEY is not using default value in production."""
|
||||
if (
|
||||
not info.data.get("DEBUG", False)
|
||||
and v == "change-me-in-production-use-openssl-rand-hex-32"
|
||||
):
|
||||
return_error = (
|
||||
"SECRET_KEY must be set in production. "
|
||||
"Generate one with: openssl rand -hex 32"
|
||||
)
|
||||
raise ValueError(return_error)
|
||||
return v
|
||||
|
||||
@field_validator("ADMIN_PASSWORD")
|
||||
@classmethod
|
||||
def validate_admin_password(cls, v: str, info) -> str:
|
||||
"""Ensure ADMIN_PASSWORD is not using default value in production."""
|
||||
if not info.data.get("DEBUG", False) and v == "rengine@123":
|
||||
return_error = (
|
||||
"ADMIN_PASSWORD must be changed from default value in production"
|
||||
)
|
||||
raise ValueError(return_error)
|
||||
return v
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
||||
from sqlmodel import SQLModel
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
|
||||
|
||||
from app.config import settings
|
||||
|
||||
engine = create_async_engine(
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
from shared.logging import setup_logging
|
||||
|
||||
from app.config import settings
|
||||
|
||||
logger = setup_logging(
|
||||
name="rengine.backend",
|
||||
level=settings.LOG_LEVEL if hasattr(settings, "LOG_LEVEL") else "INFO",
|
||||
colored=True,
|
||||
)
|
||||
@@ -1,13 +1,12 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from typing import Any
|
||||
|
||||
from argon2 import PasswordHasher
|
||||
from argon2.exceptions import VerifyMismatchError
|
||||
from jose import jwt, JWTError
|
||||
from jose import JWTError, jwt
|
||||
|
||||
from app.config import settings
|
||||
|
||||
|
||||
ph = PasswordHasher(
|
||||
time_cost=2,
|
||||
memory_cost=65536,
|
||||
@@ -54,7 +53,7 @@ def create_token(
|
||||
expires_delta: timedelta,
|
||||
) -> str:
|
||||
"""Create a JWT token with the given subject and expiration."""
|
||||
expire = datetime.now(timezone.utc) + expires_delta
|
||||
expire = datetime.now(UTC) + expires_delta
|
||||
to_encode = {
|
||||
"exp": expire,
|
||||
"sub": str(subject),
|
||||
@@ -67,7 +66,7 @@ def create_access_token(subject: str | Any) -> str:
|
||||
"""Create an access token for the given subject."""
|
||||
return create_token(
|
||||
subject=subject,
|
||||
token_type="access",
|
||||
token_type="access", # noqa: S106
|
||||
expires_delta=timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES),
|
||||
)
|
||||
|
||||
@@ -76,7 +75,7 @@ def create_refresh_token(subject: str | Any) -> str:
|
||||
"""Create a refresh token for the given subject."""
|
||||
return create_token(
|
||||
subject=subject,
|
||||
token_type="refresh",
|
||||
token_type="refresh", # noqa: S106
|
||||
expires_delta=timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS),
|
||||
)
|
||||
|
||||
@@ -89,11 +88,10 @@ def decode_token(token: str) -> dict | None:
|
||||
Decoded payload dict if valid, None otherwise
|
||||
"""
|
||||
try:
|
||||
payload = jwt.decode(
|
||||
return jwt.decode(
|
||||
token,
|
||||
settings.SECRET_KEY,
|
||||
algorithms=[settings.ALGORITHM],
|
||||
)
|
||||
return payload
|
||||
except JWTError:
|
||||
return None
|
||||
|
||||
+7
-12
@@ -1,22 +1,17 @@
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from app.config import settings
|
||||
from app.utils.helpers import create_initial_admin
|
||||
from app.core.database import init_db
|
||||
|
||||
from app.api.router import router as api_router
|
||||
from shared.logging import setup_logging, get_logger
|
||||
|
||||
|
||||
logger = setup_logging(
|
||||
name="rengine.backend",
|
||||
level=settings.LOG_LEVEL if hasattr(settings, "LOG_LEVEL") else "INFO",
|
||||
colored=True,
|
||||
)
|
||||
from app.config import settings
|
||||
from app.core.database import init_db
|
||||
from app.core.logging import logger
|
||||
from app.utils.helpers import create_initial_admin
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
async def lifespan(_app: FastAPI):
|
||||
# app starts up
|
||||
logger.info("Starting Backend...")
|
||||
try:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import uuid
|
||||
from sqlmodel import SQLModel, Field
|
||||
|
||||
from sqlmodel import Field, SQLModel
|
||||
|
||||
|
||||
class ProjectBase(SQLModel):
|
||||
|
||||
+13
-13
@@ -1,16 +1,13 @@
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional
|
||||
import uuid as uuid_pkg
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from pydantic import field_validator, ValidationInfo
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlmodel import SQLModel, Field
|
||||
from pydantic import ValidationInfo, field_validator
|
||||
from sqlmodel import Field, SQLModel
|
||||
from zxcvbn import zxcvbn
|
||||
|
||||
from app.config import settings
|
||||
|
||||
|
||||
# Password policy
|
||||
MIN_PASSWORD_SCORE = 3
|
||||
MIN_PASSWORD_LENGTH = 10
|
||||
@@ -31,9 +28,9 @@ class User(UserBase, table=True):
|
||||
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
||||
hashed_password: str
|
||||
created_at: datetime = Field(
|
||||
default_factory=lambda: datetime.now(timezone.utc).replace(tzinfo=None)
|
||||
default_factory=lambda: datetime.now(UTC).replace(tzinfo=None)
|
||||
)
|
||||
updated_at: Optional[datetime] = Field(default=None)
|
||||
updated_at: datetime | None = Field(default=None)
|
||||
|
||||
|
||||
class UserCreate(SQLModel):
|
||||
@@ -49,9 +46,10 @@ class UserCreate(SQLModel):
|
||||
return password
|
||||
|
||||
if len(password) < MIN_PASSWORD_LENGTH:
|
||||
raise ValueError(
|
||||
return_error = (
|
||||
f"Password must be at least {MIN_PASSWORD_LENGTH} characters long"
|
||||
)
|
||||
raise ValueError(return_error)
|
||||
|
||||
result = zxcvbn(
|
||||
password,
|
||||
@@ -59,13 +57,15 @@ class UserCreate(SQLModel):
|
||||
)
|
||||
|
||||
if result["score"] < MIN_PASSWORD_SCORE:
|
||||
raise ValueError(
|
||||
return_error = (
|
||||
"Password is too weak. Consider using a stronger password with a "
|
||||
"mix of uppercase, lowercase, numbers, and special characters."
|
||||
)
|
||||
raise ValueError(return_error)
|
||||
|
||||
if result["guesses_log10"] < 3:
|
||||
raise ValueError("This password is too common or easily guessable.")
|
||||
if result["guesses_log10"] < MIN_PASSWORD_SCORE:
|
||||
return_error = "Password is too guessable. Choose a less common password."
|
||||
raise ValueError(return_error)
|
||||
|
||||
return password
|
||||
|
||||
@@ -73,4 +73,4 @@ class UserCreate(SQLModel):
|
||||
class UserRead(UserBase):
|
||||
id: uuid_pkg.UUID
|
||||
created_at: datetime
|
||||
updated_at: Optional[datetime] = None
|
||||
updated_at: datetime | None = None
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
from sqlmodel import select
|
||||
from app.core.database import async_db_session
|
||||
from app.models.user import User
|
||||
from app.core.security import hash_password
|
||||
|
||||
from app.config import settings
|
||||
from app.core.database import async_db_session
|
||||
from app.core.logger import logger
|
||||
from app.core.security import hash_password
|
||||
from app.models.user import User
|
||||
|
||||
|
||||
async def create_initial_admin() -> None:
|
||||
@@ -20,4 +22,4 @@ async def create_initial_admin() -> None:
|
||||
)
|
||||
session.add(admin)
|
||||
await session.commit()
|
||||
print(f"Initial admin created: {settings.ADMIN_USERNAME}")
|
||||
logger.info(f"Initial admin created: {settings.ADMIN_USERNAME}")
|
||||
|
||||
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1 +1 @@
|
||||
export { default as Navbar } from './navbar.svelte';
|
||||
export { default as Navbar } from './navbar.svelte';
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export * from './button';
|
||||
export * from './input';
|
||||
export * from './card';
|
||||
export * from './card';
|
||||
|
||||
@@ -118,4 +118,4 @@
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,4 +15,4 @@ const config = {
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
export default config;
|
||||
|
||||
+1
-1
@@ -126,4 +126,4 @@ dmypy.json
|
||||
.DS_Store
|
||||
|
||||
# FastAPI specific
|
||||
.uvicorn_cache/
|
||||
.uvicorn_cache/
|
||||
|
||||
+11
-11
@@ -21,11 +21,11 @@ class ColoredFormatter(logging.Formatter):
|
||||
'CRITICAL': '\033[35m',
|
||||
'RESET': '\033[0m',
|
||||
}
|
||||
|
||||
|
||||
def format(self, record: logging.LogRecord) -> str:
|
||||
color = self.COLORS.get(record.levelname, self.COLORS['RESET'])
|
||||
reset = self.COLORS['RESET']
|
||||
record.levelname = f"{color}{record.levelname}{reset}"
|
||||
record.levelname = f"{color}{record.levelname}{reset}"
|
||||
return super().format(record)
|
||||
|
||||
|
||||
@@ -36,24 +36,24 @@ def setup_logging(
|
||||
) -> logging.Logger:
|
||||
"""
|
||||
Set up logging configuration.
|
||||
|
||||
|
||||
Args:
|
||||
name: Logger name (e.g., "rengine.backend", "rengine.worker")
|
||||
level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
||||
colored: Whether to use colored output for console
|
||||
|
||||
|
||||
Returns:
|
||||
Configured logger instance
|
||||
|
||||
|
||||
"""
|
||||
logger = logging.getLogger(name)
|
||||
logger.setLevel(getattr(logging, level.upper()))
|
||||
|
||||
|
||||
logger.handlers.clear()
|
||||
|
||||
|
||||
console_handler = logging.StreamHandler(sys.stdout)
|
||||
console_handler.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
if colored:
|
||||
console_format = ColoredFormatter(
|
||||
fmt="%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
|
||||
@@ -64,12 +64,12 @@ def setup_logging(
|
||||
fmt="%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
)
|
||||
|
||||
|
||||
console_handler.setFormatter(console_format)
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
|
||||
return logger
|
||||
|
||||
|
||||
def get_logger(name: str) -> logging.Logger:
|
||||
return logging.getLogger(name)
|
||||
return logging.getLogger(name)
|
||||
|
||||
Reference in New Issue
Block a user