From a4e80ca3e564e86261fd7d52af977f536a805e91 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 1 Dec 2022 10:58:50 -0800 Subject: [PATCH] Refactored permissions check. Updated redis lib. --- lib/auth.ts | 20 ++++++++++++++++++++ lib/cache.ts | 4 ++-- lib/constants.ts | 24 +++++++++++++++++++++--- lib/redis.js | 10 ++++++---- pages/api/websites/[id]/index.ts | 15 +++++++++------ 5 files changed, 58 insertions(+), 15 deletions(-) diff --git a/lib/auth.ts b/lib/auth.ts index 21225982..67b9b57c 100644 --- a/lib/auth.ts +++ b/lib/auth.ts @@ -120,3 +120,23 @@ export async function checkPermission(req: NextApiRequestAuth, type: UmamiApi.Pe return userRole.length > 0; } + +export async function canViewWebsite(userId: string, websiteId: string) { + const website = await cache.fetchWebsite(websiteId); + + if (website.userId) { + return userId === website.userId; + } + + return false; +} + +export async function canUpdateWebsite(userId: string, websiteId: string) { + const website = await cache.fetchWebsite(websiteId); + + if (website.userId) { + return userId === website.userId; + } + + return false; +} diff --git a/lib/cache.ts b/lib/cache.ts index 0cf6e2c8..5c79def0 100644 --- a/lib/cache.ts +++ b/lib/cache.ts @@ -1,5 +1,5 @@ import { User, Website } from '@prisma/client'; -import redis, { DELETED } from 'lib/redis'; +import redis from 'lib/redis'; import { getSession, getUser, getWebsite } from '../queries'; async function fetchObject(key, query) { @@ -23,7 +23,7 @@ async function storeObject(key, data) { } async function deleteObject(key) { - return redis.set(key, DELETED); + return redis.set(key, redis.DELETED); } async function fetchWebsite(id): Promise { diff --git a/lib/constants.ts b/lib/constants.ts index bea2cc0a..456fbf55 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -34,6 +34,27 @@ export namespace UmamiApi { TeamGuest = 'Team Guest,', } } + +export const PERMISSIONS = { + all: 'all', + websiteCreate: 'website:create', + websiteUpdate: 'website:update', + websiteDelete: 'website:delete', + teamCreate: 'team:create', + teamUpdate: 'team:update', + teamDelete: 'team:delete', +}; + +export const ROLES = { + admin: { name: 'admin', permissions: [PERMISSIONS.all] }, + teamOwner: { name: 'team-owner', permissions: [PERMISSIONS.teamUpdate, PERMISSIONS.teamDelete] }, + teamMember: { + name: 'team-member', + permissions: [PERMISSIONS.websiteCreate, PERMISSIONS.websiteUpdate, PERMISSIONS.websiteDelete], + }, + teamGuest: { name: 'team-guest' }, +}; + export const CURRENT_VERSION = process.env.currentVersion; export const AUTH_TOKEN = 'umami.auth'; export const LOCALE_CONFIG = 'umami.locale'; @@ -57,9 +78,6 @@ export const DEFAULT_WEBSITE_LIMIT = 10; export const REALTIME_RANGE = 30; export const REALTIME_INTERVAL = 3000; -export const TYPE_WEBSITE = 'website'; -export const TYPE_USER = 'user'; - export const THEME_COLORS = { light: { primary: '#2680eb', diff --git a/lib/redis.js b/lib/redis.js index 5ec4147d..fe236feb 100644 --- a/lib/redis.js +++ b/lib/redis.js @@ -3,16 +3,18 @@ import debug from 'debug'; const log = debug('umami:redis'); const REDIS = Symbol(); +const DELETED = 'DELETED'; let redis; -const enabled = Boolean(process.env.REDIS_URL); +const url = process.env.REDIS_URL; +const enabled = Boolean(url); async function getClient() { - if (!process.env.REDIS_URL) { + if (!enabled) { return null; } - const client = createClient({ url: process.env.REDIS_URL }); + const client = createClient({ url }); client.on('error', err => log(err)); await client.connect(); @@ -59,4 +61,4 @@ async function connect() { return redis; } -export default { enabled, client: redis, log, connect, get, set, del }; +export default { enabled, client: redis, log, connect, get, set, del, DELETED }; diff --git a/pages/api/websites/[id]/index.ts b/pages/api/websites/[id]/index.ts index 356feb0a..85b04148 100644 --- a/pages/api/websites/[id]/index.ts +++ b/pages/api/websites/[id]/index.ts @@ -1,7 +1,6 @@ import { Website } from 'interface/api/models'; import { NextApiRequestQueryBody } from 'interface/api/nextApi'; -import { allowQuery } from 'lib/auth'; -import { UmamiApi } from 'lib/constants'; +import { canViewWebsite, canUpdateWebsite } from 'lib/auth'; import { useAuth, useCors } from 'lib/middleware'; import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, serverError, unauthorized } from 'next-basics'; @@ -26,17 +25,21 @@ export default async ( const { id: websiteId } = req.query; - if (!(await allowQuery(req, UmamiApi.AuthType.Website))) { - return unauthorized(res); - } - if (req.method === 'GET') { + if (!(await canViewWebsite(req.auth.user.id, websiteId))) { + return unauthorized(res); + } + const website = await getWebsite({ id: websiteId }); return ok(res, website); } if (req.method === 'POST') { + if (!(await canUpdateWebsite(req.auth.user.id, websiteId))) { + return unauthorized(res); + } + const { name, domain, shareId } = req.body; try {