Merge branch 'dev' into swiftyos/oscp-staping

This commit is contained in:
Swifty
2025-07-04 14:45:50 +02:00
committed by GitHub
9 changed files with 141 additions and 17 deletions

View File

@@ -170,7 +170,14 @@ async def get_library_agent(id: str, user_id: str) -> library_model.LibraryAgent
if not library_agent:
raise NotFoundError(f"Library agent #{id} not found")
return library_model.LibraryAgent.from_db(library_agent)
return library_model.LibraryAgent.from_db(
library_agent,
sub_graphs=(
await graph_db.get_sub_graphs(library_agent.AgentGraph)
if library_agent.AgentGraph
else None
),
)
except prisma.errors.PrismaError as e:
logger.error(f"Database error fetching library agent: {e}")

View File

@@ -51,7 +51,7 @@ class LibraryAgent(pydantic.BaseModel):
description: str
input_schema: dict[str, Any] # Should be BlockIOObjectSubSchema in frontend
credentials_input_schema: dict[str, Any] = pydantic.Field(
credentials_input_schema: dict[str, Any] | None = pydantic.Field(
description="Input schema for credentials required by the agent",
)
@@ -70,7 +70,10 @@ class LibraryAgent(pydantic.BaseModel):
is_latest_version: bool
@staticmethod
def from_db(agent: prisma.models.LibraryAgent) -> "LibraryAgent":
def from_db(
agent: prisma.models.LibraryAgent,
sub_graphs: Optional[list[prisma.models.AgentGraph]] = None,
) -> "LibraryAgent":
"""
Factory method that constructs a LibraryAgent from a Prisma LibraryAgent
model instance.
@@ -78,7 +81,7 @@ class LibraryAgent(pydantic.BaseModel):
if not agent.AgentGraph:
raise ValueError("Associated Agent record is required.")
graph = graph_model.GraphModel.from_db(agent.AgentGraph)
graph = graph_model.GraphModel.from_db(agent.AgentGraph, sub_graphs=sub_graphs)
agent_updated_at = agent.AgentGraph.updatedAt
lib_agent_updated_at = agent.updatedAt
@@ -123,7 +126,9 @@ class LibraryAgent(pydantic.BaseModel):
name=graph.name,
description=graph.description,
input_schema=graph.input_schema,
credentials_input_schema=graph.credentials_input_schema,
credentials_input_schema=(
graph.credentials_input_schema if sub_graphs else None
),
has_external_trigger=graph.has_webhook_trigger,
trigger_setup_info=(
LibraryAgentTriggerInfo(

View File

@@ -27,7 +27,7 @@ export async function sendResetEmail(email: string, turnstileToken: string) {
}
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `${origin}/reset-password`,
redirectTo: `${origin}/api/auth/callback/reset-password`,
});
if (error) {

View File

@@ -18,18 +18,23 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import LoadingBox from "@/components/ui/loading";
import { useToast } from "@/components/ui/use-toast";
import { useTurnstile } from "@/hooks/useTurnstile";
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
import { getBehaveAs } from "@/lib/utils";
import { changePasswordFormSchema, sendEmailFormSchema } from "@/types/auth";
import { zodResolver } from "@hookform/resolvers/zod";
import { useCallback, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { useCallback, useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { changePassword, sendResetEmail } from "./actions";
export default function ResetPasswordPage() {
const { supabase, user, isUserLoading } = useSupabase();
const { toast } = useToast();
const searchParams = useSearchParams();
const router = useRouter();
const [isLoading, setIsLoading] = useState(false);
const [feedback, setFeedback] = useState<string | null>(null);
const [isError, setIsError] = useState(false);
@@ -37,6 +42,21 @@ export default function ResetPasswordPage() {
const [sendEmailCaptchaKey, setSendEmailCaptchaKey] = useState(0);
const [changePasswordCaptchaKey, setChangePasswordCaptchaKey] = useState(0);
useEffect(() => {
const error = searchParams.get("error");
if (error) {
toast({
title: "Password Reset Failed",
description: error,
variant: "destructive",
});
const newUrl = new URL(window.location.href);
newUrl.searchParams.delete("error");
router.replace(newUrl.pathname + newUrl.search);
}
}, [searchParams, toast, router]);
const sendEmailTurnstile = useTurnstile({
action: "reset_password",
autoVerify: false,

View File

@@ -0,0 +1,41 @@
import { exchangePasswordResetCode } from "@/lib/supabase/helpers";
import { getServerSupabase } from "@/lib/supabase/server/getServerSupabase";
import { NextRequest, NextResponse } from "next/server";
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const code = searchParams.get("code");
const origin =
process.env.NEXT_PUBLIC_FRONTEND_BASE_URL || "http://localhost:3000";
if (!code) {
return NextResponse.redirect(
`${origin}/reset-password?error=Missing verification code`,
);
}
try {
const supabase = await getServerSupabase();
if (!supabase) {
return NextResponse.redirect(
`${origin}/reset-password?error=no-auth-client`,
);
}
const result = await exchangePasswordResetCode(supabase, code);
if (!result.success) {
return NextResponse.redirect(
`${origin}/reset-password?error=${encodeURIComponent(result.error || "Password reset failed")}`,
);
}
return NextResponse.redirect(`${origin}/reset-password`);
} catch (error) {
console.error("Password reset callback error:", error);
return NextResponse.redirect(
`${origin}/reset-password?error=Password reset failed`,
);
}
}

View File

@@ -23,7 +23,16 @@ async function handleJsonRequest(
method: string,
backendUrl: string,
): Promise<any> {
const payload = await req.json();
let payload;
try {
payload = await req.json();
} catch (error) {
// Handle cases where request body is empty, invalid JSON, or already consumed
console.warn("Failed to parse JSON from request body:", error);
payload = null;
}
return await makeAuthenticatedRequest(
method,
backendUrl,

View File

@@ -104,7 +104,14 @@ export default function AgentRunDraftView({
const [allRequiredInputsAreSet, missingInputs] = useMemo(() => {
const nonEmptyInputs = new Set(
Object.keys(inputValues).filter((k) => !isEmpty(inputValues[k])),
Object.keys(inputValues).filter((k) => {
const value = inputValues[k];
return (
value !== undefined &&
value !== "" &&
(typeof value !== "object" || !isEmpty(value))
);
}),
);
const requiredInputs = new Set(
agentInputSchema.required as string[] | undefined,

View File

@@ -1,4 +1,5 @@
import { type CookieOptions } from "@supabase/ssr";
import { SupabaseClient } from "@supabase/supabase-js";
// Detect if we're in a Playwright test environment
const isTest = process.env.NEXT_PUBLIC_PW_TEST === "true";
@@ -98,3 +99,38 @@ export function setupSessionEventListeners(
},
};
}
export interface CodeExchangeResult {
success: boolean;
error?: string;
}
export async function exchangePasswordResetCode(
supabase: SupabaseClient<any, "public", any>,
code: string,
): Promise<CodeExchangeResult> {
try {
const { data, error } = await supabase.auth.exchangeCodeForSession(code);
if (error) {
return {
success: false,
error: error.message,
};
}
if (!data.session) {
return {
success: false,
error: "Failed to create session",
};
}
return { success: true };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : "Unknown error",
};
}
}

View File

@@ -43,18 +43,17 @@ export async function updateSession(request: NextRequest) {
},
);
const userResponse = await supabase.auth.getUser();
const user = userResponse.data.user;
const userRole = user?.role;
const url = request.nextUrl.clone();
const pathname = request.nextUrl.pathname;
// IMPORTANT: Avoid writing any logic between createServerClient and
// supabase.auth.getUser(). A simple mistake could make it very hard to debug
// issues with users being randomly logged out.
const {
data: { user },
} = await supabase.auth.getUser();
const userRole = user?.role;
const url = request.nextUrl.clone();
const pathname = request.nextUrl.pathname;
// AUTH REDIRECTS
// 1. Check if user is not authenticated but trying to access protected content
if (!user) {