Allow staff access to admin UI with scoped permissions
This commit is contained in:
parent
d6956cd99d
commit
cf31158a9e
11 changed files with 155 additions and 52 deletions
|
|
@ -2,13 +2,17 @@ import { NextResponse } from "next/server"
|
|||
|
||||
import { Prisma } from "@prisma/client"
|
||||
import { prisma } from "@/lib/prisma"
|
||||
import { assertAdminSession } from "@/lib/auth-server"
|
||||
import { assertStaffSession } from "@/lib/auth-server"
|
||||
import { isAdmin } from "@/lib/authz"
|
||||
|
||||
export const runtime = "nodejs"
|
||||
|
||||
export async function PATCH(request: Request, { params }: { params: Promise<{ id: string }> }) {
|
||||
const session = await assertAdminSession()
|
||||
const session = await assertStaffSession()
|
||||
if (!session) return NextResponse.json({ error: "Não autorizado" }, { status: 401 })
|
||||
if (!isAdmin(session.user.role)) {
|
||||
return NextResponse.json({ error: "Apenas administradores podem editar empresas" }, { status: 403 })
|
||||
}
|
||||
const { id } = await params
|
||||
const raw = (await request.json()) as Partial<{
|
||||
name: string
|
||||
|
|
@ -49,8 +53,11 @@ export async function PATCH(request: Request, { params }: { params: Promise<{ id
|
|||
}
|
||||
|
||||
export async function DELETE(_: Request, { params }: { params: Promise<{ id: string }> }) {
|
||||
const session = await assertAdminSession()
|
||||
const session = await assertStaffSession()
|
||||
if (!session) return NextResponse.json({ error: "Não autorizado" }, { status: 401 })
|
||||
if (!isAdmin(session.user.role)) {
|
||||
return NextResponse.json({ error: "Apenas administradores podem excluir empresas" }, { status: 403 })
|
||||
}
|
||||
const { id } = await params
|
||||
|
||||
const company = await prisma.company.findUnique({
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import { NextResponse } from "next/server"
|
||||
|
||||
import { prisma } from "@/lib/prisma"
|
||||
import { assertAdminSession } from "@/lib/auth-server"
|
||||
import { assertStaffSession } from "@/lib/auth-server"
|
||||
import { isAdmin } from "@/lib/authz"
|
||||
|
||||
export const runtime = "nodejs"
|
||||
|
||||
export async function GET() {
|
||||
const session = await assertAdminSession()
|
||||
const session = await assertStaffSession()
|
||||
if (!session) return NextResponse.json({ error: "Não autorizado" }, { status: 401 })
|
||||
|
||||
const companies = await prisma.company.findMany({
|
||||
|
|
@ -16,8 +17,11 @@ export async function GET() {
|
|||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const session = await assertAdminSession()
|
||||
const session = await assertStaffSession()
|
||||
if (!session) return NextResponse.json({ error: "Não autorizado" }, { status: 401 })
|
||||
if (!isAdmin(session.user.role)) {
|
||||
return NextResponse.json({ error: "Apenas administradores podem criar empresas" }, { status: 403 })
|
||||
}
|
||||
|
||||
const body = (await request.json()) as Partial<{
|
||||
name: string
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import { Prisma } from "@prisma/client"
|
|||
import { ConvexHttpClient } from "convex/browser"
|
||||
|
||||
import { api } from "@/convex/_generated/api"
|
||||
import { assertAdminSession } from "@/lib/auth-server"
|
||||
import { assertStaffSession } from "@/lib/auth-server"
|
||||
import { isAdmin } from "@/lib/authz"
|
||||
import { env } from "@/lib/env"
|
||||
import { prisma } from "@/lib/prisma"
|
||||
import { computeInviteStatus, normalizeInvite, type NormalizedInvite } from "@/server/invite-utils"
|
||||
|
|
@ -43,7 +44,7 @@ async function syncInvite(invite: NormalizedInvite) {
|
|||
|
||||
export async function PATCH(request: Request, context: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await context.params
|
||||
const session = await assertAdminSession()
|
||||
const session = await assertStaffSession()
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "Não autorizado" }, { status: 401 })
|
||||
}
|
||||
|
|
@ -61,6 +62,11 @@ export async function PATCH(request: Request, context: { params: Promise<{ id: s
|
|||
return NextResponse.json({ error: "Convite não encontrado" }, { status: 404 })
|
||||
}
|
||||
|
||||
const inviteRole = invite.role?.toLowerCase?.()
|
||||
if (!isAdmin(session.user.role) && inviteRole && ["admin", "agent"].includes(inviteRole)) {
|
||||
return NextResponse.json({ error: "Permissão insuficiente para alterar convites de administradores ou agentes" }, { status: 403 })
|
||||
}
|
||||
|
||||
const now = new Date()
|
||||
const status = computeInviteStatus(invite, now)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import { Prisma } from "@prisma/client"
|
|||
import { ConvexHttpClient } from "convex/browser"
|
||||
|
||||
import { api } from "@/convex/_generated/api"
|
||||
import { assertAdminSession } from "@/lib/auth-server"
|
||||
import { assertStaffSession } from "@/lib/auth-server"
|
||||
import { DEFAULT_TENANT_ID } from "@/lib/constants"
|
||||
import { ROLE_OPTIONS, type RoleOption } from "@/lib/authz"
|
||||
import { ROLE_OPTIONS, type RoleOption, isAdmin } from "@/lib/authz"
|
||||
import { env } from "@/lib/env"
|
||||
import { prisma } from "@/lib/prisma"
|
||||
import { computeInviteStatus, normalizeInvite, type InviteWithEvents, type NormalizedInvite } from "@/server/invite-utils"
|
||||
|
|
@ -91,7 +91,7 @@ function buildInvitePayload(invite: InviteWithEvents, now: Date) {
|
|||
}
|
||||
|
||||
export async function GET() {
|
||||
const session = await assertAdminSession()
|
||||
const session = await assertStaffSession()
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "Não autorizado" }, { status: 401 })
|
||||
}
|
||||
|
|
@ -127,7 +127,7 @@ type CreateInvitePayload = {
|
|||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const session = await assertAdminSession()
|
||||
const session = await assertStaffSession()
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "Não autorizado" }, { status: 401 })
|
||||
}
|
||||
|
|
@ -144,6 +144,10 @@ export async function POST(request: Request) {
|
|||
|
||||
const name = typeof body.name === "string" ? body.name.trim() : undefined
|
||||
const role = normalizeRole(body.role)
|
||||
const isSessionAdmin = isAdmin(session.user.role)
|
||||
if (!isSessionAdmin && !["manager", "collaborator"].includes(role)) {
|
||||
return NextResponse.json({ error: "Agentes só podem convidar gestores ou colaboradores" }, { status: 403 })
|
||||
}
|
||||
const tenantId = typeof body.tenantId === "string" && body.tenantId.trim() ? body.tenantId.trim() : session.user.tenantId || DEFAULT_TENANT_ID
|
||||
const expiresInDays = Number.isFinite(body.expiresInDays) ? Math.max(1, Number(body.expiresInDays)) : DEFAULT_EXPIRATION_DAYS
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { ConvexHttpClient } from "convex/browser"
|
|||
|
||||
import { api } from "@/convex/_generated/api"
|
||||
import type { Id } from "@/convex/_generated/dataModel"
|
||||
import { assertAdminSession } from "@/lib/auth-server"
|
||||
import { assertStaffSession } from "@/lib/auth-server"
|
||||
import { DEFAULT_TENANT_ID } from "@/lib/constants"
|
||||
|
||||
export const runtime = "nodejs"
|
||||
|
|
@ -17,7 +17,7 @@ const schema = z.object({
|
|||
})
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const session = await assertAdminSession()
|
||||
const session = await assertStaffSession()
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "Não autorizado" }, { status: 401 })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import { NextResponse } from "next/server"
|
|||
import { hashPassword } from "better-auth/crypto"
|
||||
|
||||
import { prisma } from "@/lib/prisma"
|
||||
import { assertAdminSession } from "@/lib/auth-server"
|
||||
import { assertStaffSession } from "@/lib/auth-server"
|
||||
import { isAdmin } from "@/lib/authz"
|
||||
|
||||
function generatePassword(length = 12) {
|
||||
const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
|
|
@ -19,10 +20,11 @@ export const runtime = "nodejs"
|
|||
|
||||
export async function POST(request: Request, { params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params
|
||||
const session = await assertAdminSession()
|
||||
const session = await assertStaffSession()
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "Não autorizado" }, { status: 401 })
|
||||
}
|
||||
const sessionIsAdmin = isAdmin(session.user.role)
|
||||
|
||||
const user = await prisma.authUser.findUnique({
|
||||
where: { id },
|
||||
|
|
@ -33,7 +35,12 @@ export async function POST(request: Request, { params }: { params: Promise<{ id:
|
|||
return NextResponse.json({ error: "Usuário não encontrado" }, { status: 404 })
|
||||
}
|
||||
|
||||
if ((user.role ?? "").toLowerCase() === "machine") {
|
||||
const targetRole = (user.role ?? "").toLowerCase()
|
||||
if (!sessionIsAdmin && (targetRole === "admin" || targetRole === "agent")) {
|
||||
return NextResponse.json({ error: "Você não pode redefinir a senha desse usuário" }, { status: 403 })
|
||||
}
|
||||
|
||||
if (targetRole === "machine") {
|
||||
return NextResponse.json({ error: "Contas de máquina não possuem senha web" }, { status: 400 })
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import { api } from "@/convex/_generated/api"
|
|||
import { ConvexHttpClient } from "convex/browser"
|
||||
import { prisma } from "@/lib/prisma"
|
||||
import { DEFAULT_TENANT_ID } from "@/lib/constants"
|
||||
import { assertAdminSession } from "@/lib/auth-server"
|
||||
import { ROLE_OPTIONS, type RoleOption } from "@/lib/authz"
|
||||
import { assertStaffSession } from "@/lib/auth-server"
|
||||
import { ROLE_OPTIONS, type RoleOption, isAdmin } from "@/lib/authz"
|
||||
|
||||
function normalizeRole(input: string | null | undefined): RoleOption {
|
||||
const candidate = (input ?? "agent").toLowerCase() as RoleOption
|
||||
|
|
@ -20,11 +20,16 @@ function mapToUserRole(role: RoleOption): UserRole {
|
|||
return USER_ROLE_OPTIONS.includes(candidate) ? candidate : "AGENT"
|
||||
}
|
||||
|
||||
function canManageRole(role: string | null | undefined) {
|
||||
const normalized = (role ?? "").toLowerCase()
|
||||
return normalized !== "admin" && normalized !== "agent"
|
||||
}
|
||||
|
||||
export const runtime = "nodejs"
|
||||
|
||||
export async function GET(_: Request, { params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params
|
||||
const session = await assertAdminSession()
|
||||
const session = await assertStaffSession()
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "Não autorizado" }, { status: 401 })
|
||||
}
|
||||
|
|
@ -73,10 +78,11 @@ export async function GET(_: Request, { params }: { params: Promise<{ id: string
|
|||
|
||||
export async function PATCH(request: Request, { params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params
|
||||
const session = await assertAdminSession()
|
||||
const session = await assertStaffSession()
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "Não autorizado" }, { status: 401 })
|
||||
}
|
||||
const sessionIsAdmin = isAdmin(session.user.role)
|
||||
|
||||
const payload = (await request.json().catch(() => null)) as {
|
||||
name?: string
|
||||
|
|
@ -105,10 +111,18 @@ export async function PATCH(request: Request, { params }: { params: Promise<{ id
|
|||
return NextResponse.json({ error: "Informe um e-mail válido" }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!sessionIsAdmin && !canManageRole(user.role)) {
|
||||
return NextResponse.json({ error: "Você não pode editar esse usuário" }, { status: 403 })
|
||||
}
|
||||
|
||||
if ((user.role ?? "").toLowerCase() === "machine") {
|
||||
return NextResponse.json({ error: "Ajustes de máquinas devem ser feitos em Admin ▸ Máquinas" }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!sessionIsAdmin && !canManageRole(nextRole)) {
|
||||
return NextResponse.json({ error: "Papel inválido para este perfil" }, { status: 403 })
|
||||
}
|
||||
|
||||
if (nextEmail !== user.email) {
|
||||
const conflict = await prisma.authUser.findUnique({ where: { email: nextEmail } })
|
||||
if (conflict && conflict.id !== user.id) {
|
||||
|
|
@ -210,10 +224,11 @@ export async function PATCH(request: Request, { params }: { params: Promise<{ id
|
|||
|
||||
export async function DELETE(_: Request, { params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params
|
||||
const session = await assertAdminSession()
|
||||
const session = await assertStaffSession()
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "Não autorizado" }, { status: 401 })
|
||||
}
|
||||
const sessionIsAdmin = isAdmin(session.user.role)
|
||||
|
||||
const target = await prisma.authUser.findUnique({
|
||||
where: { id },
|
||||
|
|
@ -224,6 +239,10 @@ export async function DELETE(_: Request, { params }: { params: Promise<{ id: str
|
|||
return NextResponse.json({ error: "Usuário não encontrado" }, { status: 404 })
|
||||
}
|
||||
|
||||
if (!sessionIsAdmin && !canManageRole(target.role)) {
|
||||
return NextResponse.json({ error: "Você não pode remover esse usuário" }, { status: 403 })
|
||||
}
|
||||
|
||||
if (target.role === "machine") {
|
||||
return NextResponse.json({ error: "Os agentes de máquina devem ser removidos via módulo de máquinas." }, { status: 400 })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import type { UserRole } from "@prisma/client"
|
|||
import { api } from "@/convex/_generated/api"
|
||||
import { prisma } from "@/lib/prisma"
|
||||
import { DEFAULT_TENANT_ID } from "@/lib/constants"
|
||||
import { assertAdminSession } from "@/lib/auth-server"
|
||||
import { ROLE_OPTIONS, type RoleOption } from "@/lib/authz"
|
||||
import { assertStaffSession } from "@/lib/auth-server"
|
||||
import { ROLE_OPTIONS, type RoleOption, isAdmin } from "@/lib/authz"
|
||||
|
||||
export const runtime = "nodejs"
|
||||
|
||||
|
|
@ -33,7 +33,7 @@ function generatePassword(length = 12) {
|
|||
}
|
||||
|
||||
export async function GET() {
|
||||
const session = await assertAdminSession()
|
||||
const session = await assertStaffSession()
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "Não autorizado" }, { status: 401 })
|
||||
}
|
||||
|
|
@ -55,10 +55,13 @@ export async function GET() {
|
|||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const session = await assertAdminSession()
|
||||
const session = await assertStaffSession()
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "Não autorizado" }, { status: 401 })
|
||||
}
|
||||
if (!isAdmin(session.user.role)) {
|
||||
return NextResponse.json({ error: "Apenas administradores podem criar usuários" }, { status: 403 })
|
||||
}
|
||||
|
||||
const payload = await request.json().catch(() => null)
|
||||
if (!payload || typeof payload !== "object") {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue