diff --git a/studio/backend/auth/storage.py b/studio/backend/auth/storage.py index 2864cb9852..faea6266e3 100644 --- a/studio/backend/auth/storage.py +++ b/studio/backend/auth/storage.py @@ -84,6 +84,20 @@ def create_initial_user(username: str, password: str, jwt_secret: str) -> None: conn.close() +def delete_user(username: str) -> None: + """ + Delete a user from the database. + + Used for rollback when setup fails after user creation. + """ + conn = get_connection() + try: + conn.execute("DELETE FROM auth_user WHERE username = ?", (username,)) + conn.commit() + finally: + conn.close() + + def get_user_and_secret(username: str) -> Optional[Tuple[str, str, str]]: """ Get user's password salt, hash, and JWT secret. diff --git a/studio/backend/routes/auth.py b/studio/backend/routes/auth.py index 21ac5ac1eb..0ad5bb0ea8 100644 --- a/studio/backend/routes/auth.py +++ b/studio/backend/routes/auth.py @@ -57,25 +57,29 @@ async def setup_auth(payload: AuthSetupRequest) -> Token: # Generate a strong random JWT secret for this installation jwt_secret = secrets.token_urlsafe(64) - # Save username/password hash and secret in SQLite + # Create user + generate tokens atomically — rollback if anything fails try: storage.create_initial_user( username=payload.username, password=payload.password, jwt_secret=jwt_secret, ) + + # Reload JWT secret from DB (so authentication.py picks it up) + reload_secret() + + # Issue access + refresh tokens for the new user + access_token = create_access_token(subject=payload.username) + refresh_token = create_refresh_token(subject=payload.username) + except Exception as e: + # Rollback: remove the user row so setup can be retried + storage.delete_user(payload.username) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"Failed to create user: {str(e)}", + detail=f"Setup failed (rolled back): {str(e)}", ) - # Reload JWT secret from DB (so authentication.py picks it up) - reload_secret() - - # Issue access + refresh tokens for the new user - access_token = create_access_token(subject=payload.username) - refresh_token = create_refresh_token(subject=payload.username) return Token( access_token=access_token, refresh_token=refresh_token,