From 3085bdd713a7ac5ef0275eb5de4b9c415b685595 Mon Sep 17 00:00:00 2001 From: Brian Cao Date: Wed, 30 Nov 2022 18:40:44 -0800 Subject: [PATCH] Auth checkpoint. --- .../migrations/01_init/migration.sql | 8 +- lib/auth.ts | 86 ++++++------------- lib/constants.ts | 3 +- pages/api/teams/[id]/index.ts | 14 ++- queries/admin/permission.ts | 20 ++--- queries/admin/role.ts | 11 --- 6 files changed, 51 insertions(+), 91 deletions(-) diff --git a/db/postgresql/migrations/01_init/migration.sql b/db/postgresql/migrations/01_init/migration.sql index 2ab8f8c3..620a8468 100644 --- a/db/postgresql/migrations/01_init/migration.sql +++ b/db/postgresql/migrations/01_init/migration.sql @@ -287,10 +287,10 @@ INSERT INTO "user" (user_id, username, password) VALUES ('41e2b680-648e-4b09-bcd -- Add Roles INSERT INTO "role" ("role_id", "name", "description") VALUES (gen_random_uuid(), 'Admin', 'System Admin.'); -INSERT INTO "role" ("role_id", "name", "description") (gen_random_uuid(), 'Member', 'Create and maintain websites.'); -INSERT INTO "role" ("role_id", "name", "description") (gen_random_uuid(), 'Team Owner', 'Create and maintain the team, memberships, websites, and responsible for billing.'); -INSERT INTO "role" ("role_id", "name", "description") (gen_random_uuid(), 'Team Member', 'Create and maintain websites.'); -INSERT INTO "role" ("role_id", "name", "description") (gen_random_uuid(), 'Team Guest', 'View Websites.'); +INSERT INTO "role" ("role_id", "name", "description") VALUES (gen_random_uuid(), 'Member', 'Create and maintain websites.'); +INSERT INTO "role" ("role_id", "name", "description") VALUES (gen_random_uuid(), 'Team Owner', 'Create and maintain the team, memberships, websites, and responsible for billing.'); +INSERT INTO "role" ("role_id", "name", "description") VALUES (gen_random_uuid(), 'Team Member', 'Create and maintain websites.'); +INSERT INTO "role" ("role_id", "name", "description") VALUES (gen_random_uuid(), 'Team Guest', 'View Websites.'); -- Add Permissions INSERT INTO "permission" ("permission_id", "name", "description") VALUES (gen_random_uuid(), 'admin', 'System Admin'); diff --git a/lib/auth.ts b/lib/auth.ts index 21225982..87cf016d 100644 --- a/lib/auth.ts +++ b/lib/auth.ts @@ -1,10 +1,9 @@ import debug from 'debug'; -import { NextApiRequestAuth } from 'interface/api/nextApi'; import cache from 'lib/cache'; import { SHARE_TOKEN_HEADER, UmamiApi } from 'lib/constants'; import { secret } from 'lib/crypto'; import { parseSecureToken, parseToken } from 'next-basics'; -import { getPermissionsByUserId, getTeamUser, getUser } from 'queries'; +import { getPermissionsByUserId, getTeamPermissionsByUserId } from 'queries'; const log = debug('umami:auth'); @@ -34,13 +33,6 @@ export function parseShareToken(req) { } } -export function hasPermission( - value: UmamiApi.Role | UmamiApi.Permission, - permissions: UmamiApi.Role[] | UmamiApi.Permission[], -) { - return permissions.some(a => a === value); -} - export function isValidToken(token, validation) { try { if (typeof validation === 'object') { @@ -57,66 +49,44 @@ export function isValidToken(token, validation) { } export async function allowQuery( - req: NextApiRequestAuth, + requestUserId: string, type: UmamiApi.AuthType, typeId?: string, + permission?: UmamiApi.Permission, ) { - const { id } = req.query as { id: string }; + if (type === UmamiApi.AuthType.Website) { + const website = await cache.fetchWebsite(typeId); - const { user, shareToken } = req.auth; - - if (shareToken) { - return isValidToken(shareToken, { id }); - } - - if (user?.id) { - if (type === UmamiApi.AuthType.Website) { - const website = await cache.fetchWebsite(typeId ?? id); - - if (website && website.userId === user.id) { - return true; - } - - if (website.teamId) { - const teamUser = getTeamUser({ userId: user.id, teamId: website.teamId, isDeleted: false }); - - return teamUser; - } - - return false; - } else if (type === UmamiApi.AuthType.User) { - const user = await getUser({ id }); - - return user && user.id === id; - } else if (type === UmamiApi.AuthType.Team) { - const teamUser = await getTeamUser({ - userId: user.id, - teamId: typeId ?? id, - }); - - return teamUser; - } else if (type === UmamiApi.AuthType.TeamOwner) { - const teamUser = await getTeamUser({ - userId: user.id, - teamId: typeId ?? id, - }); - - return ( - teamUser && - (teamUser.roleId === UmamiApi.Role.TeamOwner || teamUser.roleId === UmamiApi.Role.Admin) - ); + if (website && website.userId === requestUserId) { + return true; } + + if (website.teamId) { + return checkTeamPermission(requestUserId, typeId, permission); + } + + return false; + } else if (type === UmamiApi.AuthType.User) { + if (requestUserId !== typeId) { + return checkUserPermission(requestUserId, permission || UmamiApi.Permission.Admin); + } + + return requestUserId === typeId; + } else if (type === UmamiApi.AuthType.Team) { + return checkTeamPermission(requestUserId, typeId, permission); } return false; } -export async function checkPermission(req: NextApiRequestAuth, type: UmamiApi.Permission) { - const { - user: { id: userId }, - } = req.auth; - +export async function checkUserPermission(userId: string, type: UmamiApi.Permission) { const userRole = await getPermissionsByUserId(userId, type); return userRole.length > 0; } + +export async function checkTeamPermission(userId, teamId: string, type: UmamiApi.Permission) { + const userRole = await getTeamPermissionsByUserId(userId, teamId, type); + + return userRole.length > 0; +} diff --git a/lib/constants.ts b/lib/constants.ts index bea2cc0a..7537298a 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -9,11 +9,10 @@ export namespace UmamiApi { Website, User, Team, - TeamOwner, } export enum Permission { - Admin = 'Admin', + Admin = 'admin', WebsiteCreate = 'website:create', WebsiteRead = 'website:read', WebsiteUpdate = 'website:update', diff --git a/pages/api/teams/[id]/index.ts b/pages/api/teams/[id]/index.ts index e93621eb..ce330aa5 100644 --- a/pages/api/teams/[id]/index.ts +++ b/pages/api/teams/[id]/index.ts @@ -21,12 +21,16 @@ export default async ( ) => { await useAuth(req, res); + const { + user: { id: userId }, + } = req.auth; const { id: teamId } = req.query; if (req.method === 'GET') { - if (!(await allowQuery(req, UmamiApi.AuthType.Team))) { + if (!(await allowQuery(userId, UmamiApi.AuthType.Team, teamId))) { return unauthorized(res); } + const user = await getTeam({ id: teamId }); return ok(res, user); @@ -35,7 +39,9 @@ export default async ( if (req.method === 'POST') { const { name } = req.body; - if (!(await allowQuery(req, UmamiApi.AuthType.TeamOwner))) { + if ( + !(await allowQuery(userId, UmamiApi.AuthType.Team, teamId, UmamiApi.Permission.TeamUpdate)) + ) { return unauthorized(res, 'You must be the owner of this team.'); } @@ -45,7 +51,9 @@ export default async ( } if (req.method === 'DELETE') { - if (!(await allowQuery(req, UmamiApi.AuthType.TeamOwner))) { + if ( + !(await allowQuery(userId, UmamiApi.AuthType.Team, teamId, UmamiApi.Permission.TeamDelete)) + ) { return unauthorized(res, 'You must be the owner of this team.'); } diff --git a/queries/admin/permission.ts b/queries/admin/permission.ts index 9667114a..bd851369 100644 --- a/queries/admin/permission.ts +++ b/queries/admin/permission.ts @@ -19,7 +19,7 @@ export async function getPermissions(where: Prisma.PermissionWhereInput): Promis }); } -export async function getPermissionsByUserId(userId, name?: string): Promise { +export async function getPermissionsByUserId(userId: string, name?: string): Promise { return prisma.client.permission.findMany({ where: { ...(name ? { name } : {}), @@ -40,7 +40,11 @@ export async function getPermissionsByUserId(userId, name?: string): Promise { +export async function getTeamPermissionsByUserId( + userId: string, + teamId: string, + name?: string, +): Promise { return prisma.client.permission.findMany({ where: { ...(name ? { name } : {}), @@ -50,6 +54,7 @@ export async function getPermissionsByTeamId(teamId, name?: string): Promise { - return prisma.client.permission.update({ - data: { - isDeleted: true, - }, - where: { - id: permissionId, - }, - }); -} diff --git a/queries/admin/role.ts b/queries/admin/role.ts index 2bf39930..08da6237 100644 --- a/queries/admin/role.ts +++ b/queries/admin/role.ts @@ -44,14 +44,3 @@ export async function updateRole( where, }); } - -export async function deleteRole(roleId: string): Promise { - return prisma.client.role.update({ - data: { - isDeleted: true, - }, - where: { - id: roleId, - }, - }); -}