Skip to content

Commit 96fe30e

Browse files
deocagunotclaude
andcommitted
fix(projects): replace name-based founder override with role_id check
getUserProjects previously granted global admin project access via a hardcoded first_name + last_name match. Any codev row matching those strings would silently inherit founder scope — a backdoor. Replace with role_id === 1 (Admin), matching the convention used across the codebase (admin-dashboard, Navbar, surveys/actions, etc.). Also swap the .or() interpolation that mixed user-controlled email into PostgREST grammar for two parameterized .eq() lookups (id-first, email fallback for legacy rows seeded before auth.users.id was linked). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 8c3b782 commit 96fe30e

1 file changed

Lines changed: 25 additions & 20 deletions

File tree

‎apps/codebility/app/home/projects/actions.ts‎

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -77,42 +77,48 @@ export async function getUserProjects(): Promise<{
7777
}
7878

7979

80-
const { data: codevData, error: codevError } = await supabase
80+
// Look up the caller's codev profile. Try by Supabase auth user id first
81+
// (UUID is stable across email changes); fall back to email for legacy
82+
// rows seeded before auth.users.id was linked.
83+
let { data: codevData, error: codevError } = await supabase
8184
.from("codev")
82-
.select("id, first_name, last_name")
83-
.or(`id.eq.${user.id},email_address.eq.${user.email}`)
85+
.select("id, role_id")
86+
.eq("id", user.id)
8487
.maybeSingle();
8588

89+
if (!codevError && !codevData && user.email) {
90+
({ data: codevData, error: codevError } = await supabase
91+
.from("codev")
92+
.select("id, role_id")
93+
.eq("email_address", user.email)
94+
.maybeSingle());
95+
}
96+
8697
if (codevError) {
8798
console.error("Error fetching codev profile:", codevError);
8899
return { error: { message: "Failed to fetch user profile" }, data: null };
89100
}
90101

91-
// 2. FOUNDER/ADMIN OVERRIDE
92-
// If user is Jzeff Kendrew Somera, grant full access to all projects automatically.
93-
const isFounder = codevData?.first_name === "Jzeff Kendrew" && codevData?.last_name === "Somera";
94-
95-
if (isFounder) {
102+
if (!codevData) {
103+
console.error("User profile not found for user:", user.id);
104+
return { error: { message: "User profile not found" }, data: null };
105+
}
106+
107+
// Admins get global project visibility for oversight.
108+
if (codevData.role_id === 1) {
96109
const { data: allProjects, error: allProjectsError } = await supabase
97110
.from("projects")
98111
.select(`id, name, status, kanban_display, public_display, meeting_link`)
99112
.neq("status", "deleted");
100113

101114
if (!allProjectsError && allProjects) {
102-
const adminProjects = allProjects.map(p => ({
103-
project: p as Project,
104-
role: "admin",
105-
}));
106-
return { error: null, data: adminProjects };
115+
return {
116+
error: null,
117+
data: allProjects.map(p => ({ project: p as Project, role: "admin" })),
118+
};
107119
}
108120
}
109121

110-
// 3. Fallback check for missing profile
111-
if (!codevData) {
112-
console.error("User profile not found for email:", user.email);
113-
return { error: { message: "User profile not found" }, data: null };
114-
}
115-
116122
const userCodevId = codevData.id;
117123

118124
interface DbProject {
@@ -130,7 +136,6 @@ export async function getUserProjects(): Promise<{
130136
project: DbProject;
131137
}
132138

133-
// 4. Fetch Assigned Projects
134139
const { data: projectMembers, error: projectMembersError } = await supabase
135140
.from("project_members")
136141
.select(`

0 commit comments

Comments
 (0)