feat: added desktop sso endpoints
This commit is contained in:
46
vibn-frontend/app/api/auth/me/route.ts
Normal file
46
vibn-frontend/app/api/auth/me/route.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* GET /api/auth/me
|
||||
*
|
||||
* Exposes a profile endpoint for the VibnCode desktop app.
|
||||
* Resolves the Bearer vibn_sk_... Workspace API key, queries the database,
|
||||
* and returns the corresponding owner's user details.
|
||||
*/
|
||||
|
||||
import { NextResponse } from "next/server";
|
||||
import { requireWorkspacePrincipal } from "@/lib/auth/workspace-auth";
|
||||
import { queryOne } from "@/lib/db-postgres";
|
||||
|
||||
export async function GET(request: Request) {
|
||||
// 1. Authenticate the Workspace API key or Browser Session
|
||||
const principal = await requireWorkspacePrincipal(request);
|
||||
if (principal instanceof NextResponse) return principal;
|
||||
|
||||
try {
|
||||
// 2. Query user details from fs_users
|
||||
const user = await queryOne<{
|
||||
id: string;
|
||||
data: any;
|
||||
}>(
|
||||
`SELECT id, data FROM fs_users WHERE id = $1 LIMIT 1`,
|
||||
[principal.userId]
|
||||
);
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: "User not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
// 3. Return user profile compatible with the desktop client's User expectations
|
||||
return NextResponse.json({
|
||||
user: {
|
||||
id: user.id,
|
||||
name: user.data?.name || user.data?.display_name || "Vibn Owner",
|
||||
email: user.data?.email || "",
|
||||
photoUrl: user.data?.image || user.data?.photo_url || null,
|
||||
workspace: principal.workspace.slug,
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("[api/auth/me GET]", err);
|
||||
return NextResponse.json({ error: "Failed to resolve profile" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
61
vibn-frontend/app/api/auth/token/route.ts
Normal file
61
vibn-frontend/app/api/auth/token/route.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* GET /api/auth/token
|
||||
*
|
||||
* Secure endpoint called by the browser during desktop SSO.
|
||||
* Verifies the user has a valid NextAuth browser session, resolves or mints
|
||||
* a Workspace API key, and returns it.
|
||||
*/
|
||||
|
||||
import { NextResponse } from "next/server";
|
||||
import { authSession } from "@/lib/auth/session-server";
|
||||
import { queryOne } from "@/lib/db-postgres";
|
||||
import { getWorkspaceByOwner } from "@/lib/workspaces";
|
||||
import { mintWorkspaceApiKey, listWorkspaceApiKeys, revealWorkspaceApiKey } from "@/lib/auth/workspace-auth";
|
||||
|
||||
export async function GET() {
|
||||
// 1. Verify caller has an active NextAuth browser session cookie
|
||||
const session = await authSession();
|
||||
if (!session?.user?.email) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
// 2. Fetch the corresponding Postgres user
|
||||
const user = await queryOne<{ id: string }>(
|
||||
`SELECT id FROM fs_users WHERE data->>'email' = $1 LIMIT 1`,
|
||||
[session.user.email]
|
||||
);
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: "User not found" }, { status: 401 });
|
||||
}
|
||||
|
||||
// 3. Get the user's active workspace
|
||||
const workspace = await getWorkspaceByOwner(user.id);
|
||||
if (!workspace) {
|
||||
return NextResponse.json({ error: "Workspace not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
try {
|
||||
// 4. Try to reuse their existing, active workspace API key to avoid key bloating
|
||||
const keys = await listWorkspaceApiKeys(workspace.id);
|
||||
const activeKey = keys.find((k) => !k.revoked_at);
|
||||
|
||||
if (activeKey) {
|
||||
const revealed = await revealWorkspaceApiKey(workspace.id, activeKey.id);
|
||||
if (revealed) {
|
||||
return NextResponse.json({ token: revealed.token });
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Otherwise, mint a fresh key for the desktop client
|
||||
const minted = await mintWorkspaceApiKey({
|
||||
workspaceId: workspace.id,
|
||||
name: "VibnCode Desktop SSO",
|
||||
createdBy: user.id,
|
||||
});
|
||||
|
||||
return NextResponse.json({ token: minted.token });
|
||||
} catch (err) {
|
||||
console.error("[api/auth/token GET]", err);
|
||||
return NextResponse.json({ error: "Failed to resolve workspace token" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -21,14 +21,39 @@ function AuthPageInner() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const [ssoProcessing, setSsoProcessing] = React.useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (status === "authenticated" && session?.user?.email) {
|
||||
const isVibnCodeSSO = searchParams?.get("vibncode") === "true";
|
||||
|
||||
if (isVibnCodeSSO) {
|
||||
setSsoProcessing(true);
|
||||
// Call our new secure token endpoint
|
||||
fetch("/api/auth/token")
|
||||
.then((r) => r.json())
|
||||
.then((data) => {
|
||||
if (data.token) {
|
||||
// Deep-link redirect back to the VibnCode desktop app
|
||||
window.location.href = `vibncode://auth/callback?token=${data.token}`;
|
||||
} else {
|
||||
console.error("SSO Token missing from response", data);
|
||||
setSsoProcessing(false);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Desktop SSO failed:", err);
|
||||
setSsoProcessing(false);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const workspace = deriveWorkspace(session.user.email);
|
||||
|
||||
|
||||
// Check if user has projects. If 0, go to onboarding, else go to projects.
|
||||
fetch("/api/projects")
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
.then((r) => r.json())
|
||||
.then((d) => {
|
||||
if (d.projects && d.projects.length > 0) {
|
||||
router.push(`/${workspace}/projects`);
|
||||
} else {
|
||||
@@ -39,7 +64,7 @@ function AuthPageInner() {
|
||||
}
|
||||
}, [status, session, router, searchParams]);
|
||||
|
||||
if (status === "loading") {
|
||||
if (status === "loading" || ssoProcessing) {
|
||||
return (
|
||||
<div
|
||||
className="new-site-wrapper"
|
||||
@@ -80,7 +105,9 @@ function AuthPageInner() {
|
||||
textTransform: "uppercase",
|
||||
}}
|
||||
>
|
||||
Checking session
|
||||
{ssoProcessing
|
||||
? "Authorizing VibnCode Desktop..."
|
||||
: "Checking session"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user