From ac070d3ce94d1bd97ab5264a1b9cdc95dbebfc08 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 25 Oct 2022 10:45:56 -0700 Subject: [PATCH] Fixed change password issue. API refactoring. Closes #1592. --- components/forms/ChangePasswordForm.js | 6 +++--- lib/auth.js | 16 +++++++++++----- lib/constants.js | 3 +++ lib/session.js | 4 ++-- pages/api/accounts/[id]/password.js | 14 +++++++------- pages/api/accounts/index.js | 4 ++-- pages/api/auth/login.js | 4 ++-- pages/api/share/[id].js | 4 ++-- pages/api/websites/[id]/active.js | 3 ++- pages/api/websites/[id]/eventdata.js | 3 ++- pages/api/websites/[id]/events.js | 3 ++- pages/api/websites/[id]/index.js | 15 ++++++++------- pages/api/websites/[id]/metrics.js | 8 ++++---- pages/api/websites/[id]/pageviews.js | 3 ++- pages/api/websites/[id]/reset.js | 3 ++- pages/api/websites/[id]/stats.js | 3 ++- queries/admin/account/getAccountById.js | 9 --------- queries/admin/account/getAccountByUsername.js | 9 --------- queries/admin/website/deleteWebsite.js | 15 ++++++--------- queries/admin/website/getWebsite.js | 15 ++++++++++++--- queries/admin/website/getWebsiteById.js | 9 --------- queries/admin/website/getWebsiteByShareId.js | 9 --------- queries/admin/website/getWebsiteByUuid.js | 18 ------------------ queries/index.js | 5 ----- 24 files changed, 74 insertions(+), 111 deletions(-) delete mode 100644 queries/admin/account/getAccountById.js delete mode 100644 queries/admin/account/getAccountByUsername.js delete mode 100644 queries/admin/website/getWebsiteById.js delete mode 100644 queries/admin/website/getWebsiteByShareId.js delete mode 100644 queries/admin/website/getWebsiteByUuid.js diff --git a/components/forms/ChangePasswordForm.js b/components/forms/ChangePasswordForm.js index 4ee657e6..dcad6f17 100644 --- a/components/forms/ChangePasswordForm.js +++ b/components/forms/ChangePasswordForm.js @@ -9,7 +9,7 @@ import FormLayout, { FormRow, } from 'components/layout/FormLayout'; import useApi from 'hooks/useApi'; -import useUser from '../../hooks/useUser'; +import useUser from 'hooks/useUser'; const initialValues = { current_password: '', @@ -43,13 +43,13 @@ export default function ChangePasswordForm({ values, onSave, onClose }) { const { user } = useUser(); const handleSubmit = async values => { - const { ok, data } = await post(`/accounts/${user.userId}/password`, values); + const { ok, error } = await post(`/accounts/${user.accountUuid}/password`, values); if (ok) { onSave(); } else { setMessage( - data || , + error || , ); } }; diff --git a/lib/auth.js b/lib/auth.js index 446d5169..f43f2002 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -1,6 +1,6 @@ import { parseSecureToken, parseToken } from 'next-basics'; -import { getWebsite } from 'queries'; -import { SHARE_TOKEN_HEADER } from 'lib/constants'; +import { getAccount, getWebsite } from 'queries'; +import { SHARE_TOKEN_HEADER, TYPE_ACCOUNT, TYPE_WEBSITE } from 'lib/constants'; import { secret } from 'lib/crypto'; export function getAuthToken(req) { @@ -35,7 +35,7 @@ export function isValidToken(token, validation) { return false; } -export async function allowQuery(req) { +export async function allowQuery(req, type) { const { id } = req.query; const { userId, isAdmin, shareToken } = req.auth ?? {}; @@ -49,9 +49,15 @@ export async function allowQuery(req) { } if (userId) { - const website = await getWebsite({ id }); + if (type === TYPE_WEBSITE) { + const website = await getWebsite({ websiteUuid: id }); - return website && website.userId === userId; + return website && website.userId === userId; + } else if (type === TYPE_ACCOUNT) { + const account = await getAccount({ accountUuid: id }); + + return account && account.id === id; + } } return false; diff --git a/lib/constants.js b/lib/constants.js index feef540d..8b3ee8d0 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -21,6 +21,9 @@ export const DEFAULT_WEBSITE_LIMIT = 10; export const REALTIME_RANGE = 30; export const REALTIME_INTERVAL = 3000; +export const TYPE_WEBSITE = 'website'; +export const TYPE_ACCOUNT = 'account'; + export const THEME_COLORS = { light: { primary: '#2680eb', diff --git a/lib/session.js b/lib/session.js index acd5b6b0..d1a88768 100644 --- a/lib/session.js +++ b/lib/session.js @@ -4,7 +4,7 @@ import { secret, uuid } from 'lib/crypto'; import redis, { DELETED } from 'lib/redis'; import clickhouse from 'lib/clickhouse'; import { getClientInfo, getJsonBody } from 'lib/request'; -import { createSession, getSessionByUuid, getWebsiteByUuid } from 'queries'; +import { createSession, getSessionByUuid, getWebsite } from 'queries'; export async function getSession(req) { const { payload } = getJsonBody(req); @@ -38,7 +38,7 @@ export async function getSession(req) { // Check database if does not exists in Redis if (!websiteId) { - const website = await getWebsiteByUuid(websiteUuid); + const website = await getWebsite({ websiteUuid }); websiteId = website ? website.id : null; } diff --git a/pages/api/accounts/[id]/password.js b/pages/api/accounts/[id]/password.js index 3a46c56a..89649c20 100644 --- a/pages/api/accounts/[id]/password.js +++ b/pages/api/accounts/[id]/password.js @@ -1,4 +1,4 @@ -import { getAccountById, updateAccount } from 'queries'; +import { getAccount, updateAccount } from 'queries'; import { useAuth } from 'lib/middleware'; import { badRequest, @@ -8,21 +8,21 @@ import { checkPassword, hashPassword, } from 'next-basics'; +import { allowQuery } from 'lib/auth'; +import { TYPE_ACCOUNT } from 'lib/constants'; export default async (req, res) => { await useAuth(req, res); - const { userId: currentUserId, isAdmin: currentUserIsAdmin } = req.auth; const { current_password, new_password } = req.body; - const { id } = req.query; - const userId = +id; + const { id: accountUuid } = req.query; - if (!currentUserIsAdmin && userId !== currentUserId) { + if (!(await allowQuery(req, TYPE_ACCOUNT))) { return unauthorized(res); } if (req.method === 'POST') { - const account = await getAccountById(userId); + const account = await getAccount({ accountUuid }); if (!checkPassword(current_password, account.password)) { return badRequest(res, 'Current password is incorrect'); @@ -30,7 +30,7 @@ export default async (req, res) => { const password = hashPassword(new_password); - const updated = await updateAccount(userId, { password }); + const updated = await updateAccount({ password }, { accountUuid }); return ok(res, updated); } diff --git a/pages/api/accounts/index.js b/pages/api/accounts/index.js index aa52ca55..5addd63a 100644 --- a/pages/api/accounts/index.js +++ b/pages/api/accounts/index.js @@ -1,7 +1,7 @@ import { ok, unauthorized, methodNotAllowed, badRequest, hashPassword } from 'next-basics'; import { useAuth } from 'lib/middleware'; import { uuid } from 'lib/crypto'; -import { createAccount, getAccountByUsername, getAccounts } from 'queries'; +import { createAccount, getAccount, getAccounts } from 'queries'; export default async (req, res) => { await useAuth(req, res); @@ -21,7 +21,7 @@ export default async (req, res) => { if (req.method === 'POST') { const { username, password, account_uuid } = req.body; - const accountByUsername = await getAccountByUsername(username); + const accountByUsername = await getAccount({ username }); if (accountByUsername) { return badRequest(res, 'Account already exists'); diff --git a/pages/api/auth/login.js b/pages/api/auth/login.js index d5379a22..aa4803d8 100644 --- a/pages/api/auth/login.js +++ b/pages/api/auth/login.js @@ -1,5 +1,5 @@ import { ok, unauthorized, badRequest, checkPassword, createSecureToken } from 'next-basics'; -import { getAccountByUsername } from 'queries/admin/account/getAccountByUsername'; +import { getAccount } from 'queries'; import { secret } from 'lib/crypto'; export default async (req, res) => { @@ -9,7 +9,7 @@ export default async (req, res) => { return badRequest(res); } - const account = await getAccountByUsername(username); + const account = await getAccount({ username }); if (account && checkPassword(password, account.password)) { const { id, username, isAdmin, accountUuid } = account; diff --git a/pages/api/share/[id].js b/pages/api/share/[id].js index a89829fa..4ff6b81c 100644 --- a/pages/api/share/[id].js +++ b/pages/api/share/[id].js @@ -1,4 +1,4 @@ -import { getWebsiteByShareId } from 'queries'; +import { getWebsite } from 'queries'; import { ok, notFound, methodNotAllowed, createToken } from 'next-basics'; import { secret } from 'lib/crypto'; @@ -6,7 +6,7 @@ export default async (req, res) => { const { id } = req.query; if (req.method === 'GET') { - const website = await getWebsiteByShareId(id); + const website = await getWebsite({ shareId: id }); if (website) { const { websiteUuid } = website; diff --git a/pages/api/websites/[id]/active.js b/pages/api/websites/[id]/active.js index 10e73ea8..59af938e 100644 --- a/pages/api/websites/[id]/active.js +++ b/pages/api/websites/[id]/active.js @@ -2,13 +2,14 @@ import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { allowQuery } from 'lib/auth'; import { useAuth, useCors } from 'lib/middleware'; import { getActiveVisitors } from 'queries'; +import { TYPE_WEBSITE } from 'lib/constants'; export default async (req, res) => { await useCors(req, res); await useAuth(req, res); if (req.method === 'GET') { - if (!(await allowQuery(req))) { + if (!(await allowQuery(req, TYPE_WEBSITE))) { return unauthorized(res); } diff --git a/pages/api/websites/[id]/eventdata.js b/pages/api/websites/[id]/eventdata.js index 86a17b77..0e6ad2e9 100644 --- a/pages/api/websites/[id]/eventdata.js +++ b/pages/api/websites/[id]/eventdata.js @@ -3,13 +3,14 @@ import { getEventData } from 'queries'; import { ok, badRequest, methodNotAllowed, unauthorized } from 'next-basics'; import { allowQuery } from 'lib/auth'; import { useAuth, useCors } from 'lib/middleware'; +import { TYPE_WEBSITE } from 'lib/constants'; export default async (req, res) => { await useCors(req, res); await useAuth(req, res); if (req.method === 'POST') { - if (!(await allowQuery(req))) { + if (!(await allowQuery(req, TYPE_WEBSITE))) { return unauthorized(res); } diff --git a/pages/api/websites/[id]/events.js b/pages/api/websites/[id]/events.js index 192e284a..da88794e 100644 --- a/pages/api/websites/[id]/events.js +++ b/pages/api/websites/[id]/events.js @@ -3,6 +3,7 @@ import { getEventMetrics } from 'queries'; import { ok, badRequest, methodNotAllowed, unauthorized } from 'next-basics'; import { allowQuery } from 'lib/auth'; import { useAuth, useCors } from 'lib/middleware'; +import { TYPE_WEBSITE } from 'lib/constants'; const unitTypes = ['year', 'month', 'hour', 'day']; @@ -11,7 +12,7 @@ export default async (req, res) => { await useAuth(req, res); if (req.method === 'GET') { - if (!(await allowQuery(req))) { + if (!(await allowQuery(req, TYPE_WEBSITE))) { return unauthorized(res); } diff --git a/pages/api/websites/[id]/index.js b/pages/api/websites/[id]/index.js index d802982a..539a3288 100644 --- a/pages/api/websites/[id]/index.js +++ b/pages/api/websites/[id]/index.js @@ -2,19 +2,20 @@ import { allowQuery } from 'lib/auth'; import { useAuth, useCors } from 'lib/middleware'; import { getRandomChars, methodNotAllowed, ok, serverError, unauthorized } from 'next-basics'; import { deleteWebsite, getAccount, getWebsite, updateWebsite } from 'queries'; +import { TYPE_WEBSITE } from 'lib/constants'; export default async (req, res) => { await useCors(req, res); await useAuth(req, res); - const { id: websiteId } = req.query; + const { id: websiteUuid } = req.query; - if (!(await allowQuery(req))) { + if (!(await allowQuery(req, TYPE_WEBSITE))) { return unauthorized(res); } if (req.method === 'GET') { - const website = await getWebsite({ websiteUuid: websiteId }); + const website = await getWebsite({ websiteUuid }); return ok(res, website); } @@ -32,7 +33,7 @@ export default async (req, res) => { } } - const website = await getWebsite({ websiteUuid: websiteId }); + const website = await getWebsite({ websiteUuid }); const newShareId = enableShareUrl ? website.shareId || getRandomChars(8) : null; @@ -44,7 +45,7 @@ export default async (req, res) => { shareId: shareId ? shareId : newShareId, userId: account ? account.id : +owner || undefined, }, - { websiteUuid: websiteId }, + { websiteUuid }, ); } catch (e) { if (e.message.includes('Unique constraint') && e.message.includes('share_id')) { @@ -56,11 +57,11 @@ export default async (req, res) => { } if (req.method === 'DELETE') { - if (!(await allowQuery(req, true))) { + if (!(await allowQuery(req, TYPE_WEBSITE))) { return unauthorized(res); } - await deleteWebsite(websiteId); + await deleteWebsite(websiteUuid); return ok(res); } diff --git a/pages/api/websites/[id]/metrics.js b/pages/api/websites/[id]/metrics.js index e0eab028..0aafe7d3 100644 --- a/pages/api/websites/[id]/metrics.js +++ b/pages/api/websites/[id]/metrics.js @@ -1,8 +1,8 @@ import { allowQuery } from 'lib/auth'; -import { FILTER_IGNORED } from 'lib/constants'; +import { FILTER_IGNORED, TYPE_WEBSITE } from 'lib/constants'; import { useAuth, useCors } from 'lib/middleware'; import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics'; -import { getPageviewMetrics, getSessionMetrics, getWebsiteByUuid } from 'queries'; +import { getPageviewMetrics, getSessionMetrics, getWebsite } from 'queries'; const sessionColumns = ['browser', 'os', 'device', 'screen', 'country', 'language']; const pageviewColumns = ['url', 'referrer', 'query']; @@ -38,7 +38,7 @@ export default async (req, res) => { await useAuth(req, res); if (req.method === 'GET') { - if (!(await allowQuery(req))) { + if (!(await allowQuery(req, TYPE_WEBSITE))) { return unauthorized(res); } @@ -94,7 +94,7 @@ export default async (req, res) => { let domain; if (type === 'referrer') { - const website = await getWebsiteByUuid(websiteId); + const website = await getWebsite({ websiteUuid: websiteId }); if (!website) { return badRequest(res); diff --git a/pages/api/websites/[id]/pageviews.js b/pages/api/websites/[id]/pageviews.js index 9e05417b..5b628e3a 100644 --- a/pages/api/websites/[id]/pageviews.js +++ b/pages/api/websites/[id]/pageviews.js @@ -3,6 +3,7 @@ import { getPageviewStats } from 'queries'; import { ok, badRequest, methodNotAllowed, unauthorized } from 'next-basics'; import { allowQuery } from 'lib/auth'; import { useAuth, useCors } from 'lib/middleware'; +import { TYPE_WEBSITE } from 'lib/constants'; const unitTypes = ['year', 'month', 'hour', 'day']; @@ -11,7 +12,7 @@ export default async (req, res) => { await useAuth(req, res); if (req.method === 'GET') { - if (!(await allowQuery(req))) { + if (!(await allowQuery(req, TYPE_WEBSITE))) { return unauthorized(res); } diff --git a/pages/api/websites/[id]/reset.js b/pages/api/websites/[id]/reset.js index fe527ad4..0dde02df 100644 --- a/pages/api/websites/[id]/reset.js +++ b/pages/api/websites/[id]/reset.js @@ -2,6 +2,7 @@ import { resetWebsite } from 'queries'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { allowQuery } from 'lib/auth'; import { useAuth, useCors } from 'lib/middleware'; +import { TYPE_WEBSITE } from 'lib/constants'; export default async (req, res) => { await useCors(req, res); @@ -10,7 +11,7 @@ export default async (req, res) => { const { id: websiteId } = req.query; if (req.method === 'POST') { - if (!(await allowQuery(req))) { + if (!(await allowQuery(req, TYPE_WEBSITE))) { return unauthorized(res); } diff --git a/pages/api/websites/[id]/stats.js b/pages/api/websites/[id]/stats.js index 596ebc90..2c5b0156 100644 --- a/pages/api/websites/[id]/stats.js +++ b/pages/api/websites/[id]/stats.js @@ -2,13 +2,14 @@ import { getWebsiteStats } from 'queries'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { allowQuery } from 'lib/auth'; import { useAuth, useCors } from 'lib/middleware'; +import { TYPE_WEBSITE } from 'lib/constants'; export default async (req, res) => { await useCors(req, res); await useAuth(req, res); if (req.method === 'GET') { - if (!(await allowQuery(req))) { + if (!(await allowQuery(req, TYPE_WEBSITE))) { return unauthorized(res); } diff --git a/queries/admin/account/getAccountById.js b/queries/admin/account/getAccountById.js deleted file mode 100644 index eee1b76a..00000000 --- a/queries/admin/account/getAccountById.js +++ /dev/null @@ -1,9 +0,0 @@ -import prisma from 'lib/prisma'; - -export async function getAccountById(userId) { - return prisma.client.account.findUnique({ - where: { - id: userId, - }, - }); -} diff --git a/queries/admin/account/getAccountByUsername.js b/queries/admin/account/getAccountByUsername.js deleted file mode 100644 index ff64c8ce..00000000 --- a/queries/admin/account/getAccountByUsername.js +++ /dev/null @@ -1,9 +0,0 @@ -import prisma from 'lib/prisma'; - -export async function getAccountByUsername(username) { - return prisma.client.account.findUnique({ - where: { - username, - }, - }); -} diff --git a/queries/admin/website/deleteWebsite.js b/queries/admin/website/deleteWebsite.js index b5e2ff76..f08dc63e 100644 --- a/queries/admin/website/deleteWebsite.js +++ b/queries/admin/website/deleteWebsite.js @@ -1,27 +1,24 @@ import prisma from 'lib/prisma'; import redis, { DELETED } from 'lib/redis'; -import { getWebsiteByUuid } from 'queries'; -export async function deleteWebsite(websiteId) { +export async function deleteWebsite(websiteUuid) { const { client, transaction } = prisma; - const { websiteUuid } = await getWebsiteByUuid(websiteId); - return transaction([ client.pageview.deleteMany({ - where: { session: { website: { websiteUuid: websiteId } } }, + where: { session: { website: { websiteUuid } } }, }), client.eventData.deleteMany({ - where: { event: { session: { website: { websiteUuid: websiteId } } } }, + where: { event: { session: { website: { websiteUuid } } } }, }), client.event.deleteMany({ - where: { session: { website: { websiteUuid: websiteId } } }, + where: { session: { website: { websiteUuid } } }, }), client.session.deleteMany({ - where: { website: { websiteUuid: websiteId } }, + where: { website: { websiteUuid } }, }), client.website.delete({ - where: { websiteUuid: websiteId }, + where: { websiteUuid }, }), ]).then(async res => { if (redis.client) { diff --git a/queries/admin/website/getWebsite.js b/queries/admin/website/getWebsite.js index 83c3e83a..d33c9ead 100644 --- a/queries/admin/website/getWebsite.js +++ b/queries/admin/website/getWebsite.js @@ -1,7 +1,16 @@ import prisma from 'lib/prisma'; +import redis from 'lib/redis'; export async function getWebsite(where) { - return prisma.client.website.findUnique({ - where, - }); + return prisma.client.website + .findUnique({ + where, + }) + .then(async data => { + if (redis.enabled && data) { + await redis.client.set(`website:${data.websiteUuid}`, data.id); + } + + return data; + }); } diff --git a/queries/admin/website/getWebsiteById.js b/queries/admin/website/getWebsiteById.js deleted file mode 100644 index f486bd8f..00000000 --- a/queries/admin/website/getWebsiteById.js +++ /dev/null @@ -1,9 +0,0 @@ -import prisma from 'lib/prisma'; - -export async function getWebsiteById(websiteId) { - return prisma.client.website.findUnique({ - where: { - id: websiteId, - }, - }); -} diff --git a/queries/admin/website/getWebsiteByShareId.js b/queries/admin/website/getWebsiteByShareId.js deleted file mode 100644 index bfc99ce7..00000000 --- a/queries/admin/website/getWebsiteByShareId.js +++ /dev/null @@ -1,9 +0,0 @@ -import prisma from 'lib/prisma'; - -export async function getWebsiteByShareId(shareId) { - return prisma.client.website.findUnique({ - where: { - shareId, - }, - }); -} diff --git a/queries/admin/website/getWebsiteByUuid.js b/queries/admin/website/getWebsiteByUuid.js deleted file mode 100644 index 158d357a..00000000 --- a/queries/admin/website/getWebsiteByUuid.js +++ /dev/null @@ -1,18 +0,0 @@ -import prisma from 'lib/prisma'; -import redis from 'lib/redis'; - -export async function getWebsiteByUuid(websiteUuid) { - return prisma.client.website - .findUnique({ - where: { - websiteUuid, - }, - }) - .then(async res => { - if (redis.client && res) { - await redis.client.set(`website:${res.websiteUuid}`, res.id); - } - - return res; - }); -} diff --git a/queries/index.js b/queries/index.js index abff147a..a570af65 100644 --- a/queries/index.js +++ b/queries/index.js @@ -1,8 +1,6 @@ export * from './admin/account/createAccount'; export * from './admin/account/deleteAccount'; export * from './admin/account/getAccount'; -export * from './admin/account/getAccountById'; -export * from './admin/account/getAccountByUsername'; export * from './admin/account/getAccounts'; export * from './admin/account/updateAccount'; export * from './admin/website/createWebsite'; @@ -10,9 +8,6 @@ export * from './admin/website/deleteWebsite'; export * from './admin/website/getAllWebsites'; export * from './admin/website/getUserWebsites'; export * from './admin/website/getWebsite'; -export * from './admin/website/getWebsiteById'; -export * from './admin/website/getWebsiteByShareId'; -export * from './admin/website/getWebsiteByUuid'; export * from './admin/website/resetWebsite'; export * from './admin/website/updateWebsite'; export * from './analytics/event/getEventMetrics';