Write new secret manager using existing RSA logic
This commit is contained in:
@ -2,6 +2,7 @@
|
||||
|
||||
# pyright: reportUnusedFunction=false
|
||||
#
|
||||
from collections.abc import AsyncGenerator
|
||||
import logging
|
||||
import os
|
||||
from contextlib import asynccontextmanager
|
||||
@ -12,15 +13,15 @@ from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.responses import JSONResponse, RedirectResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sshecret_backend.db import DatabaseSessionManager
|
||||
from starlette.middleware.sessions import SessionMiddleware
|
||||
|
||||
from sshecret_admin import api, frontend
|
||||
from sshecret_admin.auth.models import PasswordDB, init_db
|
||||
from sshecret_admin.auth.models import Base
|
||||
from sshecret_admin.core.db import setup_database
|
||||
from sshecret_admin.frontend.exceptions import RedirectException
|
||||
from sshecret_admin.services.master_password import setup_master_password
|
||||
from sshecret_admin.services.secret_manager import setup_private_key
|
||||
|
||||
from .dependencies import BaseDependencies
|
||||
from .settings import AdminServerSettings
|
||||
@ -40,44 +41,28 @@ def setup_frontend(app: FastAPI, dependencies: BaseDependencies) -> None:
|
||||
|
||||
|
||||
def create_admin_app(
|
||||
settings: AdminServerSettings, with_frontend: bool = True
|
||||
settings: AdminServerSettings,
|
||||
with_frontend: bool = True,
|
||||
create_db: bool = False,
|
||||
) -> FastAPI:
|
||||
"""Create admin app."""
|
||||
engine, get_db_session = setup_database(settings.admin_db)
|
||||
|
||||
async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
|
||||
"""Get async session."""
|
||||
session_manager = DatabaseSessionManager(settings.async_db_url)
|
||||
async with session_manager.session() as session:
|
||||
yield session
|
||||
|
||||
def setup_password_manager() -> None:
|
||||
"""Setup password manager."""
|
||||
encr_master_password = setup_master_password(
|
||||
settings=settings, regenerate=False
|
||||
)
|
||||
with Session(engine) as session:
|
||||
existing_password = session.scalars(
|
||||
select(PasswordDB).where(PasswordDB.id == 1)
|
||||
).first()
|
||||
|
||||
if not encr_master_password:
|
||||
if existing_password:
|
||||
LOG.info("Master password already defined.")
|
||||
return
|
||||
# Looks like we have to regenerate it
|
||||
LOG.warning(
|
||||
"Master password was set, but not saved to the database. Regenerating it."
|
||||
)
|
||||
encr_master_password = setup_master_password(
|
||||
settings=settings, regenerate=True
|
||||
)
|
||||
|
||||
assert encr_master_password is not None
|
||||
|
||||
with Session(engine) as session:
|
||||
pwdb = PasswordDB(id=1, encrypted_password=encr_master_password)
|
||||
session.add(pwdb)
|
||||
session.commit()
|
||||
setup_private_key(settings, regenerate=False)
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(_app: FastAPI):
|
||||
"""Create database before starting the server."""
|
||||
init_db(engine)
|
||||
if create_db:
|
||||
Base.metadata.create_all(engine)
|
||||
setup_password_manager()
|
||||
yield
|
||||
|
||||
@ -109,7 +94,7 @@ def create_admin_app(
|
||||
status_code=status.HTTP_200_OK, content=jsonable_encoder({"status": "LIVE"})
|
||||
)
|
||||
|
||||
dependencies = BaseDependencies(settings, get_db_session)
|
||||
dependencies = BaseDependencies(settings, get_db_session, get_async_session)
|
||||
|
||||
app.include_router(api.create_api_router(dependencies))
|
||||
if with_frontend:
|
||||
|
||||
@ -12,7 +12,7 @@ from pydantic import ValidationError
|
||||
from sqlalchemy import select, create_engine
|
||||
from sqlalchemy.orm import Session
|
||||
from sshecret_admin.auth.authentication import hash_password
|
||||
from sshecret_admin.auth.models import AuthProvider, PasswordDB, User, init_db
|
||||
from sshecret_admin.auth.models import AuthProvider, PasswordDB, User
|
||||
from sshecret_admin.core.settings import AdminServerSettings
|
||||
from sshecret_admin.services.admin_backend import AdminBackend
|
||||
|
||||
@ -72,7 +72,6 @@ def cli_create_user(
|
||||
"""Create user."""
|
||||
settings = cast(AdminServerSettings, ctx.obj)
|
||||
engine = create_engine(settings.admin_db)
|
||||
init_db(engine)
|
||||
with Session(engine) as session:
|
||||
create_user(session, username, email, password)
|
||||
|
||||
@ -87,7 +86,6 @@ def cli_change_user_passwd(ctx: click.Context, username: str, password: str) ->
|
||||
"""Change password on user."""
|
||||
settings = cast(AdminServerSettings, ctx.obj)
|
||||
engine = create_engine(settings.admin_db)
|
||||
init_db(engine)
|
||||
with Session(engine) as session:
|
||||
user = session.scalars(select(User).where(User.username == username)).first()
|
||||
if not user:
|
||||
@ -107,7 +105,6 @@ def cli_delete_user(ctx: click.Context, username: str) -> None:
|
||||
"""Remove a user."""
|
||||
settings = cast(AdminServerSettings, ctx.obj)
|
||||
engine = create_engine(settings.admin_db)
|
||||
init_db(engine)
|
||||
with Session(engine) as session:
|
||||
user = session.scalars(select(User).where(User.username == username)).first()
|
||||
if not user:
|
||||
@ -149,7 +146,6 @@ def cli_repl(ctx: click.Context) -> None:
|
||||
"""Run an interactive console."""
|
||||
settings = cast(AdminServerSettings, ctx.obj)
|
||||
engine = create_engine(settings.admin_db)
|
||||
init_db(engine)
|
||||
with Session(engine) as session:
|
||||
password_db = session.scalars(
|
||||
select(PasswordDB).where(PasswordDB.id == 1)
|
||||
@ -165,7 +161,7 @@ def cli_repl(ctx: click.Context) -> None:
|
||||
loop = asyncio.get_event_loop()
|
||||
return loop.run_until_complete(func)
|
||||
|
||||
admin = AdminBackend(settings, password_db.encrypted_password)
|
||||
admin = AdminBackend(settings, )
|
||||
locals = {
|
||||
"run": run,
|
||||
"admin": admin,
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
"""Database setup."""
|
||||
|
||||
import sqlite3
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from collections.abc import AsyncIterator, Generator, Callable
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.engine import URL
|
||||
from sqlalchemy import create_engine, Engine
|
||||
from sqlalchemy import create_engine, Engine, event
|
||||
|
||||
from sqlalchemy.ext.asyncio import (
|
||||
AsyncConnection,
|
||||
@ -18,11 +19,20 @@ from sqlalchemy.ext.asyncio import (
|
||||
|
||||
|
||||
def setup_database(
|
||||
db_url: URL | str,
|
||||
db_url: URL,
|
||||
) -> tuple[Engine, Callable[[], Generator[Session, None, None]]]:
|
||||
"""Setup database."""
|
||||
|
||||
engine = create_engine(db_url, echo=True, future=True)
|
||||
if db_url.drivername.startswith("sqlite"):
|
||||
|
||||
@event.listens_for(engine, "connect")
|
||||
def set_sqlite_pragma(
|
||||
dbapi_connection: sqlite3.Connection, _connection_record: object
|
||||
) -> None:
|
||||
cursor = dbapi_connection.cursor()
|
||||
cursor.execute("PRAGMA foreign_keys=ON")
|
||||
cursor.close()
|
||||
|
||||
def get_db_session() -> Generator[Session, None, None]:
|
||||
"""Get DB Session."""
|
||||
@ -33,8 +43,18 @@ def setup_database(
|
||||
|
||||
|
||||
class DatabaseSessionManager:
|
||||
def __init__(self, host: URL | str, **engine_kwargs: str):
|
||||
def __init__(self, host: URL, **engine_kwargs: str):
|
||||
self._engine: AsyncEngine | None = create_async_engine(host, **engine_kwargs)
|
||||
if host.drivername.startswith("sqlite+"):
|
||||
|
||||
@event.listens_for(self._engine.sync_engine, "connect")
|
||||
def set_sqlite_pragma(
|
||||
dbapi_connection: sqlite3.Connection, _connection_record: object
|
||||
) -> None:
|
||||
cursor = dbapi_connection.cursor()
|
||||
cursor.execute("PRAGMA foreign_keys=ON")
|
||||
cursor.close()
|
||||
|
||||
self._sessionmaker: async_sessionmaker[AsyncSession] | None = (
|
||||
async_sessionmaker(
|
||||
autocommit=False, bind=self._engine, expire_on_commit=False
|
||||
|
||||
@ -4,6 +4,8 @@ from collections.abc import AsyncGenerator, Awaitable, Callable, Generator
|
||||
from dataclasses import dataclass
|
||||
from typing import Self
|
||||
|
||||
from fastapi import Request
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import Session
|
||||
from sshecret_admin.auth import User
|
||||
from sshecret_admin.services import AdminBackend
|
||||
@ -11,8 +13,9 @@ from sshecret_admin.core.settings import AdminServerSettings
|
||||
|
||||
|
||||
DBSessionDep = Callable[[], Generator[Session, None, None]]
|
||||
AsyncSessionDep = Callable[[], AsyncGenerator[AsyncSession, None]]
|
||||
|
||||
AdminDep = Callable[[Session], AsyncGenerator[AdminBackend, None]]
|
||||
AdminDep = Callable[[Request, Session], AsyncGenerator[AdminBackend, None]]
|
||||
|
||||
GetUserDep = Callable[[User], Awaitable[User]]
|
||||
|
||||
@ -23,6 +26,8 @@ class BaseDependencies:
|
||||
|
||||
settings: AdminServerSettings
|
||||
get_db_session: DBSessionDep
|
||||
get_async_session: AsyncSessionDep
|
||||
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -43,6 +48,7 @@ class AdminDependencies(BaseDependencies):
|
||||
return cls(
|
||||
settings=deps.settings,
|
||||
get_db_session=deps.get_db_session,
|
||||
get_async_session=deps.get_async_session,
|
||||
get_admin_backend=get_admin_backend,
|
||||
get_current_active_user=get_current_active_user,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user