Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions app/api/[...path]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { NextRequest, NextResponse } from 'next/server'
import { getServerSession } from 'next-auth'
import { authOptions } from '../auth/[...nextauth]/authOptions'
import { getEncodedJWT } from '@/lib/api/jwt-utils'
import {
getArchivistBaseUrl,
getDataForgeBaseUrl,
Expand Down Expand Up @@ -31,11 +32,16 @@ function getBaseUrlForInternalService(service: string): string | null {

async function proxyToBackend(req: NextRequest, path: string[]) {
const session = await getServerSession(authOptions)

if (!session?.user?.access_token) {
if (!session?.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}

// Backend expects a signed NextAuth-style JWT (HS256), not the GitHub OAuth token.
const backendToken = await getEncodedJWT(req)
if (!backendToken) {
return NextResponse.json({ error: 'Unauthorized', message: 'No valid session token for backend' }, { status: 401 })
}

const [service, ...backendPathSegments] = path
if (!service || backendPathSegments.length === 0) {
return NextResponse.json(
Expand All @@ -60,7 +66,7 @@ async function proxyToBackend(req: NextRequest, path: string[]) {
method: req.method,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${session.user.access_token}`,
Authorization: `Bearer ${backendToken}`,
'X-Internal-Secret': process.env.INTERNAL_API_SECRET!,
},
body: req.method !== 'GET' && req.method !== 'DELETE'
Expand Down
6 changes: 4 additions & 2 deletions app/api/auth/[...nextauth]/authOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export const authOptions: NextAuthOptions = {
u.user_id = (token as any).user_id;
u.profile_id = (token as any).profile_id;
u.requires_onboarding = (token as any).requires_onboarding;
u.roles = (token as any).roles || [];
u.access_token = (token as any).access_token;
}
return session;
Expand Down Expand Up @@ -121,6 +122,7 @@ export const authOptions: NextAuthOptions = {
updated_at: (profile as any).updated_at,
organization: (profile as any).organization,
hireable: (profile as any).hireable,
roles: [] as string[],
access_token: account.access_token,
};

Expand All @@ -131,7 +133,7 @@ export const authOptions: NextAuthOptions = {
if (onboardingStatus.onboarded) {
// User is onboarded - fetch auth credentials
const authData = await getAuthDataByGithubUsername(githubUsername);

newToken.roles = authData.roles;
newToken.user_id = authData.user_id;
newToken.profile_id = authData.profile_id;
newToken.github_user_name = githubUsername;
Expand Down Expand Up @@ -172,7 +174,7 @@ export const authOptions: NextAuthOptions = {
if (onboardingStatus.onboarded) {
// User is now onboarded - fetch auth credentials
const authData = await getAuthDataByGithubUsername(token.github_user_name);

token.roles = authData.roles;
token.user_id = authData.user_id;
token.profile_id = authData.profile_id;
token.requires_onboarding = false;
Expand Down
19 changes: 1 addition & 18 deletions app/api/qa-gate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ import { TEAMNAMES } from "@/constants/constants";
import { ORG } from "@/constants/constants";
// import { DOMAIN } from "@/lib/constants";

export async function POST(req: Request) {
console.log("QA Gate API called, ENV:", ENV);

export async function POST(req: Request) {
if (ENV !== "QA") {
return NextResponse.json({ error: "Not found" }, { status: 404 });
}
Expand Down Expand Up @@ -43,12 +41,9 @@ export async function POST(req: Request) {
crypto.timingSafeEqual(Buffer.from(password), Buffer.from(expectedPassword));

if (!isValid) {
console.log("Invalid password for user:", username);
return NextResponse.json({ error: "Invalid password" }, { status: 401 });
}

console.log("Checking GitHub org membership for:", username);

// Check if user is in org
const orgRes = await fetch(`https://api.github.com/orgs/${ORG}/members/${username}`, {
headers: {
Expand All @@ -58,7 +53,6 @@ export async function POST(req: Request) {
},
});

console.log("Org check response:", orgRes.status);

if (orgRes.status === 404) {
return NextResponse.json({
Expand All @@ -70,15 +64,13 @@ export async function POST(req: Request) {
return NextResponse.json({ error: "Organization check failed" }, { status: 403 });
}

console.log("Checking team membership for:", username, "in teams:", TEAMNAMES);

// Split team names and check membership in any of them
const allowedTeams = TEAMNAMES.split(',').map(team => team.trim());
let userInTeam = false;
let memberOfTeams: string[] = [];

for (const teamName of allowedTeams) {
console.log("Checking membership in team:", teamName);

const teamRes = await fetch(
`https://api.github.com/orgs/${ORG}/teams/team-${teamName}/members/${username}`,
Expand All @@ -91,7 +83,6 @@ export async function POST(req: Request) {
}
);

console.log(`Team '${teamName}' check response:`, teamRes.status);

if (teamRes.status === 200 || teamRes.status === 204) {
userInTeam = true;
Expand Down Expand Up @@ -148,14 +139,6 @@ function issueSuccessResponse(note: string, req: Request) {
maxAge: 60 * 60 * 8, // 8 hours
};

// Additional debug logging
console.log("Setting cookie with options:", {
...cookieOptions,
url: url.origin,
protocol: url.protocol,
hostname,
});

res.headers.set("Set-Cookie", serialize("qa_verified", "true", cookieOptions));

return res;
Expand Down
6 changes: 1 addition & 5 deletions app/api/qa-logout/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ import { NextResponse } from "next/server";
import { serialize } from "cookie";
import { ENV } from "@/constants/constants";

export async function POST(req: Request) {
console.log("QA logout API called");

export async function POST(req: Request) {
// Allow this in any environment for cleanup purposes
try {
const url = new URL(req.url);
Expand All @@ -21,7 +19,6 @@ export async function POST(req: Request) {
};

const cookieHeader = serialize("qa_verified", "", cookieOptions);
console.log("Setting cookie header:", cookieHeader);

const res = NextResponse.json({
success: true,
Expand All @@ -34,7 +31,6 @@ export async function POST(req: Request) {
// Set multiple cookie clearing headers to be sure
res.headers.set("Set-Cookie", cookieHeader);

console.log("QA cookie cleared successfully");
return res;
} catch (err) {
console.error("QA logout error:", err);
Expand Down
5 changes: 0 additions & 5 deletions app/qa-gate/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,15 @@ export default function QAGatePage() {
setLoading(true);

try {
console.log("Submitting QA gate request...");
const res = await fetch("/api/qa-gate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password }),
credentials: "include",
});

console.log("Response status:", res.status);
console.log("Response ok:", res.ok);

if (res.ok) {
const data = await res.json();
console.log("QA gate passed:", data);
// Redirect to login or home page
window.location.href = "/";
} else {
Expand Down
1 change: 1 addition & 0 deletions app/types/next-auth.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ declare module "next-auth" {
user_id?: string;
profile_id?: string;
requires_onboarding?: boolean;
roles?: string[];
access_token?: string;
};
}
Expand Down
3 changes: 0 additions & 3 deletions components/dijkstra-gpt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -250,14 +250,11 @@ export default function DijkstraGPT() {
const response = await callGemini("test");
if (response) {
setApiStatus('active');
console.log('✅ API key is active');
} else {
setApiStatus('inactive');
console.log('❌ API key is not configured or invalid');
}
} catch (error) {
setApiStatus('inactive');
console.error('❌ API check failed:', error);
}
};

Expand Down
8 changes: 2 additions & 6 deletions components/login/sign-in-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ export function SignInForm() {
// Redirect based on onboarding status from session
useEffect(() => {
if (justLoggedIn && session?.user) {
console.log('Login verification:', {
githubUsername: session.user.github_user_name || session.user.login,
requiresOnboarding: session.user.requires_onboarding
});

// Ensure we have GitHub username (either from github_user_name or login field)
const githubUsername = session.user.github_user_name || (session.user as any).login;
Expand All @@ -37,10 +33,10 @@ export function SignInForm() {
const requiresOnboarding = session.user.requires_onboarding !== false;

if (requiresOnboarding) {
console.log('User not onboarded, redirecting to onboarding');
// User not onboarded, redirecting to onboarding
window.location.href = '/onboarding';
} else {
console.log('User onboarded, redirecting to dashboard');
// User onboarded, redirecting to dashboard
window.location.href = '/dashboard';
}
}
Expand Down
140 changes: 140 additions & 0 deletions components/multiselects/role-multi-select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
"use client"
import { useState } from "react"
import { Popover, PopoverTrigger, PopoverContent } from "@/components/ui/popover"
import { Command, CommandInput, CommandItem, CommandList, CommandEmpty, CommandGroup } from "@/components/ui/command"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { Check, ChevronsUpDown, X, Shield } from "lucide-react"
import { cn } from "@/lib/utils"
import { ROLE_OPTIONS } from "@/constants/roles"
import type { Role } from "@/constants/roles"

interface RoleMultiSelectProps {
value: string[]
onChange: (roles: string[]) => void
availableRoles: string[]
placeholder?: string
disabled?: boolean
}

export function RoleMultiSelect({
value,
onChange,
availableRoles,
placeholder = "Select roles...",
disabled = false
}: RoleMultiSelectProps) {
const [open, setOpen] = useState(false)
const [query, setQuery] = useState("")

const selectedRoles = value || []

// Filter roles based on query and available roles
const filteredRoles = ROLE_OPTIONS.filter(role =>
availableRoles.includes(role.value) &&
role.label.toLowerCase().includes(query.toLowerCase()) &&
!selectedRoles.includes(role.value)
)

const handleSelect = (roleValue: Role) => {
if (!selectedRoles.includes(roleValue)) {
onChange([...selectedRoles, roleValue])
}
setQuery("")
}

const handleRemove = (roleValue: string) => {
onChange(selectedRoles.filter(role => role !== roleValue))
}

const formatDisplayText = () => {
if (selectedRoles.length === 0) return placeholder
if (selectedRoles.length === 1) {
const role = ROLE_OPTIONS.find(r => r.value === selectedRoles[0])
return role?.label || selectedRoles[0]
}
return `${selectedRoles.length} roles selected`
}

return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="w-full justify-between min-h-[40px] h-auto"
disabled={disabled}
>
<div className="flex items-center gap-2 flex-wrap">
<Shield className="w-4 h-4 text-muted-foreground shrink-0" />
<div className="flex flex-wrap gap-1 flex-1">
{selectedRoles.length > 0 ? (
selectedRoles.map((roleValue) => {
const role = ROLE_OPTIONS.find(r => r.value === roleValue)
return (
<Badge
key={roleValue}
variant="secondary"
className="text-xs px-2 py-1"
>
{role?.label || roleValue}
<div
onClick={(e) => {
e.stopPropagation()
handleRemove(roleValue)
}}
className="ml-1 hover:bg-muted-foreground/20 rounded-full p-0.5 cursor-pointer"
>
<X className="w-3 h-3" />
</div>
</Badge>
)
})
) : (
<span className="text-muted-foreground">{placeholder}</span>
)}
</div>
</div>
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>

<PopoverContent className="w-full p-0 bg-popover text-popover-foreground border">
<Command className="bg-popover text-popover-foreground">
<CommandInput
placeholder="Search roles..."
value={query}
onValueChange={setQuery}
className="text-foreground"
/>
<CommandList className="bg-popover text-popover-foreground">
{filteredRoles.length === 0 && query && (
<CommandEmpty>No roles found.</CommandEmpty>
)}

{filteredRoles.length > 0 && (
<CommandGroup heading="Available Roles">
{filteredRoles.map((role) => (
<CommandItem
key={role.value}
value={role.label}
onSelect={() => handleSelect(role.value)}
className="flex items-center gap-2 hover:bg-accent"
>
<Shield className="w-4 h-4 text-muted-foreground shrink-0" />
<span className="flex-1">{role.label}</span>
<Check className={cn("ml-auto h-4 w-4",
selectedRoles.includes(role.value) ? "opacity-100" : "opacity-0"
)} />
</CommandItem>
))}
</CommandGroup>
)}
</CommandList>
</Command>
</PopoverContent>
</Popover>
)
}

2 changes: 0 additions & 2 deletions components/profile/profile-container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,6 @@ export function ProfileContainer() {
});
};

console.log("Session", session);

return (
<div className="max-w-8xl mx-auto space-y-6">
{/* Header */}
Expand Down
Loading