Use Logto manager scope for access
Build and deploy Docker image / build (push) Successful in 2m49s

This commit is contained in:
Ruslan Bakiev
2026-06-05 12:07:02 +07:00
parent bfa02574f3
commit e776002bec
2 changed files with 8 additions and 29 deletions
+5 -16
View File
@@ -23,27 +23,13 @@ function displayName(firstName: string, lastName: string, phone: string): string
return name.length > 0 ? name : phone return name.length > 0 ? name : phone
} }
async function isManagerUser(userId: number): Promise<boolean> {
const user = await prisma.user.findUnique({ where: { id: userId } })
if (user === null) return false
if (user.isStaff || user.isSuperuser) return true
const managerMembership = await prisma.teamMember.findFirst({
where: { userId, role: { in: ['OWNER', 'MANAGER', 'ADMIN'] } },
})
return managerMembership !== null
}
async function requireManagerProfile(ctx: AuthContext) { async function requireManagerProfile(ctx: AuthContext) {
requireScopes(ctx, 'manager') requireScopes(ctx, 'manager')
if (!ctx.userId) throw new GraphQLError('Not authenticated') if (!ctx.userId) throw new GraphQLError('Not authenticated')
const profile = await prisma.userProfile.findUnique({ return prisma.userProfile.findUnique({
where: { logtoId: ctx.userId }, where: { logtoId: ctx.userId },
include: { user: true, activeTeam: true }, include: { user: true, activeTeam: true },
}) })
if (profile === null || !(await isManagerUser(profile.userId))) {
throw new GraphQLError('Manager access required', { extensions: { code: 'FORBIDDEN' } })
}
return profile
} }
export const managerResolvers = { export const managerResolvers = {
@@ -51,7 +37,10 @@ export const managerResolvers = {
managerUsers: async (_: unknown, __: unknown, ctx: AuthContext) => { managerUsers: async (_: unknown, __: unknown, ctx: AuthContext) => {
const profile = await requireManagerProfile(ctx) const profile = await requireManagerProfile(ctx)
const members = await prisma.teamMember.findMany({ const members = await prisma.teamMember.findMany({
where: profile.activeTeamId === null ? {} : { teamId: profile.activeTeamId }, where:
profile?.activeTeamId === undefined || profile.activeTeamId === null
? {}
: { teamId: profile.activeTeamId },
include: { user: { include: { profile: { include: { activeTeam: true } } } }, team: true }, include: { user: { include: { profile: { include: { activeTeam: true } } } }, team: true },
orderBy: { joinedAt: 'desc' }, orderBy: { joinedAt: 'desc' },
}) })
+3 -13
View File
@@ -112,24 +112,14 @@ function displayName(
return name.length > 0 ? name : phone; return name.length > 0 ? name : phone;
} }
async function isManagerUser(userId: number): Promise<boolean> {
const user = await prisma.user.findUnique({ where: { id: userId } });
if (user === null) return false;
if (user.isStaff || user.isSuperuser) return true;
const managerMembership = await prisma.teamMember.findFirst({
where: { userId, role: { in: ["OWNER", "MANAGER", "ADMIN"] } },
});
return managerMembership !== null;
}
async function mapProfileUser( async function mapProfileUser(
profile: Awaited<ReturnType<typeof getOrCreateProfile>>, profile: Awaited<ReturnType<typeof getOrCreateProfile>>,
ctx: AuthContext,
) { ) {
const memberships = await prisma.teamMember.findMany({ const memberships = await prisma.teamMember.findMany({
where: { userId: profile.userId }, where: { userId: profile.userId },
include: { team: true }, include: { team: true },
}); });
const isManager = await isManagerUser(profile.userId);
return { return {
id: profile.logtoId, id: profile.logtoId,
firstName: profile.user.firstName, firstName: profile.user.firstName,
@@ -141,7 +131,7 @@ async function mapProfileUser(
), ),
phone: profile.phone, phone: profile.phone,
avatarId: profile.avatarId, avatarId: profile.avatarId,
isManager, isManager: ctx.scopes.includes("manager"),
activeTeamId: profile.activeTeam?.uuid ?? null, activeTeamId: profile.activeTeam?.uuid ?? null,
activeTeam: profile.activeTeam activeTeam: profile.activeTeam
? { ? {
@@ -167,7 +157,7 @@ export const userResolvers = {
me: async (_: unknown, __: unknown, ctx: AuthContext) => { me: async (_: unknown, __: unknown, ctx: AuthContext) => {
if (!ctx.userId) throw new GraphQLError("Not authenticated"); if (!ctx.userId) throw new GraphQLError("Not authenticated");
const profile = await getOrCreateProfile(ctx.userId); const profile = await getOrCreateProfile(ctx.userId);
return mapProfileUser(profile); return mapProfileUser(profile, ctx);
}, },
getTeam: async (_: unknown, args: { teamId: string }, ctx: AuthContext) => { getTeam: async (_: unknown, args: { teamId: string }, ctx: AuthContext) => {