Skip to content

Commit cbe98df

Browse files
authored
Merge pull request #102 from WXYC/fix-admin-panel
2 parents 056bc6f + c603fd2 commit cbe98df

File tree

2 files changed

+197
-39
lines changed

2 files changed

+197
-39
lines changed

apps/auth/app.ts

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,16 +148,73 @@ const createDefaultUser = async () => {
148148
},
149149
});
150150

151-
console.log('Default user created successfully.');
151+
// Set admin role for stationManager in default organization
152+
// This ensures the user has admin permissions for Better Auth Admin plugin
153+
const { db, user } = await import('@wxyc/database');
154+
const { eq } = await import('drizzle-orm');
155+
await db
156+
.update(user)
157+
.set({ role: 'admin' })
158+
.where(eq(user.id, newUser.id));
159+
160+
console.log('Default user created successfully with admin role.');
152161
} catch (error) {
153162
console.error('Error creating default user!');
154163
throw error;
155164
}
156165
};
157166

158-
// Initialize default user before starting the server
167+
// Fix admin roles for existing stationManagers (one-time migration)
168+
const syncAdminRoles = async () => {
169+
try {
170+
const { db, user, member, organization } = await import('@wxyc/database');
171+
const { eq, sql } = await import('drizzle-orm');
172+
173+
const defaultOrgSlug = process.env.DEFAULT_ORG_SLUG;
174+
if (!defaultOrgSlug) {
175+
console.log('[ADMIN PERMISSIONS] DEFAULT_ORG_SLUG not set, skipping admin role fix');
176+
return;
177+
}
178+
179+
// Find all users who are stationManager/admin/owner in default org but don't have admin role
180+
const usersNeedingFix = await db
181+
.select({
182+
userId: user.id,
183+
userEmail: user.email,
184+
userRole: user.role,
185+
memberRole: member.role,
186+
})
187+
.from(user)
188+
.innerJoin(member, sql`${member.userId} = ${user.id}` as any)
189+
.innerJoin(organization, sql`${member.organizationId} = ${organization.id}` as any)
190+
.where(
191+
sql`${organization.slug} = ${defaultOrgSlug}
192+
AND ${member.role} IN ('admin', 'owner', 'stationManager')
193+
AND (${user.role} IS NULL OR ${user.role} != 'admin')` as any
194+
);
195+
196+
if (usersNeedingFix.length > 0) {
197+
console.log(`[ADMIN PERMISSIONS] Found ${usersNeedingFix.length} users needing admin role fix:`);
198+
for (const u of usersNeedingFix) {
199+
console.log(`[ADMIN PERMISSIONS] - ${u.userEmail} (${u.memberRole}) - current role: ${u.userRole || 'null'}`);
200+
await db
201+
.update(user)
202+
.set({ role: 'admin' })
203+
.where(eq(user.id, u.userId));
204+
console.log(`[ADMIN PERMISSIONS] - Fixed: ${u.userEmail} now has admin role`);
205+
}
206+
} else {
207+
console.log('[ADMIN PERMISSIONS] All stationManagers already have admin role');
208+
}
209+
} catch (error) {
210+
console.error('[ADMIN PERMISSIONS] Error fixing admin roles:', error);
211+
}
212+
};
213+
214+
// Initialize default user and sync admin roles before starting the server
159215
(async () => {
160216
await createDefaultUser();
217+
await syncAdminRoles();
161218

162219
app.listen(parseInt(port), async () => {
163220
console.log(`listening on port: ${port}! (auth service)`);

shared/authentication/src/auth.definition.ts

Lines changed: 138 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,144 @@ export const auth = betterAuth({
9696
organizationLimit: 1, // Users can only be in one organization
9797
roles: WXYCRoles,
9898
// Role information is included via custom JWT definePayload function above
99+
organizationHooks: {
100+
// Sync global user.role when members are added to default organization
101+
afterAddMember: async ({ member, user: userData, organization: orgData }) => {
102+
try {
103+
const defaultOrgSlug = process.env.DEFAULT_ORG_SLUG;
104+
if (!defaultOrgSlug) {
105+
console.warn(
106+
"DEFAULT_ORG_SLUG is not set, skipping admin role sync"
107+
);
108+
return;
109+
}
110+
111+
// Only update for default organization
112+
if (orgData.slug !== defaultOrgSlug) {
113+
return;
114+
}
115+
116+
// Check if role should grant admin permissions
117+
const adminRoles = ["stationManager", "admin", "owner"];
118+
if (adminRoles.includes(member.role)) {
119+
// Update user.role to "admin" for Better Auth Admin plugin
120+
const userId = userData.id;
121+
await db
122+
.update(user)
123+
.set({ role: "admin" })
124+
.where(eq(user.id, userId));
125+
console.log(
126+
`Granted admin role to user ${userId} (${userData.email}) with ${member.role} role in default organization`
127+
);
128+
}
129+
} catch (error) {
130+
console.error("Error syncing admin role in afterAddMember:", error);
131+
}
132+
},
133+
134+
// Sync global user.role when member roles are updated
135+
afterUpdateMemberRole: async ({
136+
member,
137+
previousRole,
138+
user: userData,
139+
organization: orgData,
140+
}) => {
141+
try {
142+
const defaultOrgSlug = process.env.DEFAULT_ORG_SLUG;
143+
if (!defaultOrgSlug) {
144+
console.warn(
145+
"DEFAULT_ORG_SLUG is not set, skipping admin role sync"
146+
);
147+
return;
148+
}
149+
150+
// Only update for default organization
151+
if (orgData.slug !== defaultOrgSlug) {
152+
return;
153+
}
154+
155+
const adminRoles = ["stationManager", "admin", "owner"];
156+
const shouldHaveAdmin = adminRoles.includes(member.role);
157+
const previouslyHadAdmin = adminRoles.includes(previousRole);
158+
159+
const userId = userData.id;
160+
if (shouldHaveAdmin && !previouslyHadAdmin) {
161+
// Promoted to admin role - grant admin
162+
await db
163+
.update(user)
164+
.set({ role: "admin" })
165+
.where(eq(user.id, userId));
166+
console.log(
167+
`Granted admin role to user ${userId} (${userData.email}) after promotion to ${member.role}`
168+
);
169+
} else if (!shouldHaveAdmin && previouslyHadAdmin) {
170+
// Demoted from admin role - remove admin
171+
await db
172+
.update(user)
173+
.set({ role: null })
174+
.where(eq(user.id, userId));
175+
console.log(
176+
`Removed admin role from user ${userId} (${userData.email}) after demotion from ${previousRole} to ${member.role}`
177+
);
178+
}
179+
} catch (error) {
180+
console.error(
181+
"Error syncing admin role in afterUpdateMemberRole:",
182+
error
183+
);
184+
}
185+
},
186+
187+
// Sync global user.role when members are removed from default organization
188+
afterRemoveMember: async ({ user: userData, organization: orgData }) => {
189+
try {
190+
const defaultOrgSlug = process.env.DEFAULT_ORG_SLUG;
191+
if (!defaultOrgSlug) {
192+
console.warn(
193+
"DEFAULT_ORG_SLUG is not set, skipping admin role sync"
194+
);
195+
return;
196+
}
197+
198+
// Only update for default organization
199+
if (orgData.slug !== defaultOrgSlug) {
200+
return;
201+
}
202+
203+
// Check if user has any other memberships with admin roles
204+
const otherAdminMemberships = await db
205+
.select({ role: member.role })
206+
.from(member)
207+
.innerJoin(
208+
organization,
209+
sql`${member.organizationId} = ${organization.id}` as any
210+
)
211+
.where(
212+
sql`${member.userId} = ${userData.id}
213+
AND ${organization.slug} = ${defaultOrgSlug}
214+
AND ${member.role} IN ('admin', 'owner', 'stationManager')` as any
215+
)
216+
.limit(1);
217+
218+
// If no other admin memberships exist, remove admin role
219+
if (otherAdminMemberships.length === 0) {
220+
const userId = userData.id;
221+
await db
222+
.update(user)
223+
.set({ role: null })
224+
.where(eq(user.id, userId));
225+
console.log(
226+
`Removed admin role from user ${userId} (${userData.email}) after removal from default organization`
227+
);
228+
}
229+
} catch (error) {
230+
console.error(
231+
"Error syncing admin role in afterRemoveMember:",
232+
error
233+
);
234+
}
235+
},
236+
},
99237
}),
100238
],
101239

@@ -110,43 +248,6 @@ export const auth = betterAuth({
110248
},
111249
},
112250

113-
// Admin configuration - organization owners/admins/stationManagers are admins
114-
admin: {
115-
// Check if user has admin, owner, or stationManager role in the WXYC organization
116-
requireAdmin: async (user: any, request?: any) => {
117-
if (!user?.id) return false;
118-
119-
try {
120-
const defaultOrgSlug = process.env.DEFAULT_ORG_SLUG;
121-
122-
if (!defaultOrgSlug) {
123-
throw new Error(
124-
"DEFAULT_ORG_SLUG is not set in environment variables."
125-
);
126-
}
127-
128-
// Query the member table to check if user has admin/owner/stationManager role in WXYC org
129-
const adminMember = await db
130-
.select({ memberId: member.id })
131-
.from(member)
132-
.innerJoin(
133-
organization,
134-
sql`${member.organizationId} = ${organization.id}` as any
135-
)
136-
.where(
137-
sql`${member.userId} = ${user.id}
138-
AND ${organization.slug} = ${defaultOrgSlug}
139-
AND ${member.role} IN ('admin', 'owner', 'stationManager')` as any
140-
)
141-
.limit(1);
142-
143-
return adminMember.length > 0;
144-
} catch (error) {
145-
console.error("Error checking admin status:", error);
146-
return false;
147-
}
148-
},
149-
},
150251
});
151252

152253
export type Auth = typeof auth;

0 commit comments

Comments
 (0)