Add api validations.

This commit is contained in:
Brian Cao 2023-08-19 22:23:15 -07:00
parent 7d5a24044a
commit 7a7233ead4
41 changed files with 690 additions and 180 deletions

View File

@ -1,19 +1,20 @@
import redis from '@umami/redis-client';
import cors from 'cors';
import debug from 'debug';
import { getAuthToken, parseShareToken } from 'lib/auth';
import { ROLES } from 'lib/constants';
import { isUuid, secret } from 'lib/crypto';
import { findSession } from 'lib/session';
import {
createMiddleware,
unauthorized,
badRequest,
createMiddleware,
parseSecureToken,
tooManyRequest,
unauthorized,
} from 'next-basics';
import debug from 'debug';
import cors from 'cors';
import redis from '@umami/redis-client';
import { findSession } from 'lib/session';
import { getAuthToken, parseShareToken } from 'lib/auth';
import { secret, isUuid } from 'lib/crypto';
import { ROLES } from 'lib/constants';
import { getUserById } from '../queries';
import { NextApiRequestCollect } from 'pages/api/send';
import { getUserById } from '../queries';
import { NextApiRequestQueryBody } from './types';
const log = debug('umami:middleware');
@ -75,3 +76,15 @@ export const useAuth = createMiddleware(async (req, res, next) => {
next();
});
export const useValidate = createMiddleware(async (req: any, res, next) => {
try {
const { yup } = req as NextApiRequestQueryBody;
yup[req.method].validateSync({ ...req.query, ...req.body });
} catch (e: any) {
return badRequest(res, e.message);
}
next();
});

View File

@ -5,11 +5,13 @@ import {
EVENT_TYPE,
KAFKA_TOPIC,
REPORT_FILTER_TYPES,
REPORT_TYPES,
ROLES,
TEAM_FILTER_TYPES,
USER_FILTER_TYPES,
WEBSITE_FILTER_TYPES,
} from './constants';
import * as yup from 'yup';
type ObjectValues<T> = T[keyof T];
@ -18,6 +20,8 @@ export type Role = ObjectValues<typeof ROLES>;
export type EventType = ObjectValues<typeof EVENT_TYPE>;
export type DynamicDataType = ObjectValues<typeof DATA_TYPE>;
export type KafkaTopic = ObjectValues<typeof KAFKA_TOPIC>;
export type ReportType = ObjectValues<typeof REPORT_TYPES>;
export type ReportSearchFilterType = ObjectValues<typeof REPORT_FILTER_TYPES>;
export type UserSearchFilterType = ObjectValues<typeof USER_FILTER_TYPES>;
export type WebsiteSearchFilterType = ObjectValues<typeof WEBSITE_FILTER_TYPES>;
@ -47,8 +51,8 @@ export interface ReportSearchFilter extends SearchFilter<ReportSearchFilterType>
export interface SearchFilter<T> {
filter?: string;
filterType?: T;
pageSize?: number;
page?: number;
pageSize: number;
page: number;
orderBy?: string;
}
@ -76,11 +80,19 @@ export interface Auth {
};
}
export interface YupRequest {
GET?: yup.ObjectSchema<any>;
POST?: yup.ObjectSchema<any>;
PUT?: yup.ObjectSchema<any>;
DELETE?: yup.ObjectSchema<any>;
}
export interface NextApiRequestQueryBody<TQuery = any, TBody = any> extends NextApiRequest {
auth?: Auth;
query: TQuery & { [key: string]: string | string[] };
body: TBody;
headers: any;
yup: YupRequest;
}
export interface NextApiRequestAuth extends NextApiRequest {
@ -168,7 +180,6 @@ export interface RealtimeUpdate {
export interface DateRange {
startDate: Date;
endDate: Date;
unit: string;
value: string;
}

19
lib/yup.ts Normal file
View File

@ -0,0 +1,19 @@
import * as yup from 'yup';
export function getDateRangeValidation() {
return {
startAt: yup.number().integer().required(),
endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(),
};
}
// ex: /funnel|insights|retention/i
export function getFilterValidation(matchRegex) {
return {
filter: yup.string(),
filterType: yup.string().matches(matchRegex),
pageSize: yup.number().integer().positive().max(200),
page: yup.number().integer().positive(),
orderBy: yup.string(),
};
}

View File

@ -1,19 +1,20 @@
import redis from '@umami/redis-client';
import debug from 'debug';
import { setAuthKey } from 'lib/auth';
import { secret } from 'lib/crypto';
import { useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, User } from 'lib/types';
import { NextApiResponse } from 'next';
import {
ok,
unauthorized,
badRequest,
checkPassword,
createSecureToken,
methodNotAllowed,
forbidden,
methodNotAllowed,
ok,
unauthorized,
} from 'next-basics';
import redis from '@umami/redis-client';
import { getUserByUsername } from 'queries';
import { secret } from 'lib/crypto';
import { NextApiRequestQueryBody, User } from 'lib/types';
import { setAuthKey } from 'lib/auth';
import * as yup from 'yup';
const log = debug('umami:auth');
@ -27,6 +28,13 @@ export interface LoginResponse {
user: User;
}
const schema = {
POST: yup.object().shape({
username: yup.string().required(),
password: yup.string().required(),
}),
};
export default async (
req: NextApiRequestQueryBody<any, LoginRequestBody>,
res: NextApiResponse<LoginResponse>,
@ -35,13 +43,12 @@ export default async (
return forbidden(res);
}
req.yup = schema;
await useValidate(req, res);
if (req.method === 'POST') {
const { username, password } = req.body;
if (!username || !password) {
return badRequest(res);
}
const user = await getUserByUsername(username, { includePassword: true });
if (user && checkPassword(password, user.password)) {

View File

@ -1,26 +1,37 @@
import { canViewWebsite } from 'lib/auth';
import { useCors, useAuth } from 'lib/middleware';
import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types';
import { NextApiResponse } from 'next';
import { ok, methodNotAllowed, unauthorized } from 'next-basics';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getEventDataEvents } from 'queries';
import * as yup from 'yup';
export interface EventDataEventsRequestQuery {
export interface EventDataFieldsRequestQuery {
websiteId: string;
dateRange: {
startDate: string;
endDate: string;
};
event?: string;
startAt: string;
endAt: string;
event: string;
}
const schema = {
GET: yup.object().shape({
websiteId: yup.string().uuid().required(),
startAt: yup.number().integer().required(),
endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(),
event: yup.string().required(),
}),
};
export default async (
req: NextApiRequestQueryBody<EventDataEventsRequestQuery>,
req: NextApiRequestQueryBody<EventDataFieldsRequestQuery, any>,
res: NextApiResponse<any>,
) => {
await useCors(req, res);
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
if (req.method === 'GET') {
const { websiteId, startAt, endAt, event } = req.query;

View File

@ -1,19 +1,27 @@
import { canViewWebsite } from 'lib/auth';
import { useCors, useAuth } from 'lib/middleware';
import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types';
import { NextApiResponse } from 'next';
import { ok, methodNotAllowed, unauthorized } from 'next-basics';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getEventDataFields } from 'queries';
import * as yup from 'yup';
export interface EventDataFieldsRequestQuery {
websiteId: string;
dateRange: {
startDate: string;
endDate: string;
};
startAt: string;
endAt: string;
field?: string;
}
const schema = {
GET: yup.object().shape({
websiteId: yup.string().uuid().required(),
startAt: yup.number().integer().required(),
endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(),
field: yup.string(),
}),
};
export default async (
req: NextApiRequestQueryBody<EventDataFieldsRequestQuery>,
res: NextApiResponse<any>,
@ -21,6 +29,9 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
if (req.method === 'GET') {
const { websiteId, startAt, endAt, field } = req.query;

View File

@ -1,18 +1,24 @@
import { canViewWebsite } from 'lib/auth';
import { useCors, useAuth } from 'lib/middleware';
import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types';
import { NextApiResponse } from 'next';
import { ok, methodNotAllowed, unauthorized } from 'next-basics';
import { getEventDataStats } from 'queries';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import * as yup from 'yup';
export interface EventDataStatsRequestQuery {
websiteId: string;
dateRange: {
startDate: string;
endDate: string;
};
startAt: string;
endAt: string;
}
const schema = {
GET: yup.object().shape({
websiteId: yup.string().uuid().required(),
startAt: yup.number().integer().required(),
endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(),
}),
};
export default async (
req: NextApiRequestQueryBody<EventDataStatsRequestQuery>,
res: NextApiResponse<any>,
@ -20,6 +26,9 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
if (req.method === 'GET') {
const { websiteId, startAt, endAt } = req.query;

View File

@ -1,15 +1,16 @@
import { useAuth, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, User } from 'lib/types';
import { useAuth } from 'lib/middleware';
import { NextApiResponse } from 'next';
import {
badRequest,
checkPassword,
forbidden,
hashPassword,
methodNotAllowed,
forbidden,
ok,
} from 'next-basics';
import { getUserById, updateUser } from 'queries';
import * as yup from 'yup';
export interface UserPasswordRequestQuery {
id: string;
@ -20,6 +21,14 @@ export interface UserPasswordRequestBody {
newPassword: string;
}
const schema = {
POST: yup.object().shape({
id: yup.string().uuid().required(),
currentPassword: yup.string().required(),
newPassword: yup.string().min(8).required(),
}),
};
export default async (
req: NextApiRequestQueryBody<UserPasswordRequestQuery, UserPasswordRequestBody>,
res: NextApiResponse<User>,
@ -30,6 +39,9 @@ export default async (
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { currentPassword, newPassword } = req.body;
const { id } = req.auth.user;

View File

@ -1,10 +1,20 @@
import { useCors } from 'lib/middleware';
import { useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, SearchFilter, TeamSearchFilterType } from 'lib/types';
import { getFilterValidation } from 'lib/yup';
import { NextApiResponse } from 'next';
import { methodNotAllowed } from 'next-basics';
import userTeams from 'pages/api/users/[id]/teams';
import * as yup from 'yup';
export interface MyTeamsRequestQuery extends SearchFilter<TeamSearchFilterType> {}
export interface MyTeamsRequestQuery extends SearchFilter<TeamSearchFilterType> {
id: string;
}
const schema = {
GET: yup.object().shape({
...getFilterValidation(/All|Name|Owner/i),
}),
};
export default async (
req: NextApiRequestQueryBody<MyTeamsRequestQuery, any>,
@ -12,7 +22,12 @@ export default async (
) => {
await useCors(req, res);
req.yup = schema;
await useValidate(req, res);
if (req.method === 'GET') {
req.query.id = req.auth.user.id;
return userTeams(req, res);
}

View File

@ -1,11 +1,20 @@
import { useAuth, useCors } from 'lib/middleware';
import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, SearchFilter, WebsiteSearchFilterType } from 'lib/types';
import { getFilterValidation } from 'lib/yup';
import { NextApiResponse } from 'next';
import { methodNotAllowed } from 'next-basics';
import userWebsites from 'pages/api/users/[id]/websites';
import * as yup from 'yup';
export interface MyWebsitesRequestQuery extends SearchFilter<WebsiteSearchFilterType> {}
export interface MyWebsitesRequestQuery extends SearchFilter<WebsiteSearchFilterType> {
id: string;
}
const schema = {
GET: yup.object().shape({
...getFilterValidation(/All|Name|Domain/i),
}),
};
export default async (
req: NextApiRequestQueryBody<MyWebsitesRequestQuery, any>,
@ -14,6 +23,9 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
if (req.method === 'GET') {
req.query.id = req.auth.user.id;

View File

@ -1,22 +1,34 @@
import { subMinutes } from 'date-fns';
import { canViewWebsite } from 'lib/auth';
import { useAuth } from 'lib/middleware';
import { useAuth, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, RealtimeInit } from 'lib/types';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getRealtimeData } from 'queries';
import * as yup from 'yup';
export interface RealtimeRequestQuery {
id: string;
startAt: number;
}
const currentDate = new Date().getTime();
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
startAt: yup.number().integer().max(currentDate).required(),
}),
};
export default async (
req: NextApiRequestQueryBody<RealtimeRequestQuery>,
res: NextApiResponse<RealtimeInit>,
) => {
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
if (req.method === 'GET') {
const { id: websiteId, startAt } = req.query;

View File

@ -1,9 +1,10 @@
import { canUpdateReport, canViewReport, canDeleteReport } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types';
import { canDeleteReport, canUpdateReport, canViewReport } from 'lib/auth';
import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, ReportType, YupRequest } from 'lib/types';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getReportById, updateReport, deleteReport } from 'queries';
import { deleteReport, getReportById, updateReport } from 'queries';
import * as yup from 'yup';
export interface ReportRequestQuery {
id: string;
@ -11,12 +12,34 @@ export interface ReportRequestQuery {
export interface ReportRequestBody {
websiteId: string;
type: string;
type: ReportType;
name: string;
description: string;
parameters: string;
}
const schema: YupRequest = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
}),
POST: yup.object().shape({
id: yup.string().uuid().required(),
websiteId: yup.string().uuid().required(),
type: yup
.string()
.matches(/funnel|insights|retention/i)
.required(),
name: yup.string().max(200).required(),
description: yup.string().max(500),
parameters: yup
.object()
.test('len', 'Must not exceed 6000 characters.', val => JSON.stringify(val).length < 6000),
}),
DELETE: yup.object().shape({
id: yup.string().uuid().required(),
}),
};
export default async (
req: NextApiRequestQueryBody<ReportRequestQuery, ReportRequestBody>,
res: NextApiResponse,
@ -24,6 +47,9 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { id: reportId } = req.query;
const {
user: { id: userId },

View File

@ -1,9 +1,10 @@
import { canViewWebsite } from 'lib/auth';
import { useCors, useAuth } from 'lib/middleware';
import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types';
import { NextApiResponse } from 'next';
import { ok, methodNotAllowed, unauthorized } from 'next-basics';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getFunnel } from 'queries';
import * as yup from 'yup';
export interface FunnelRequestBody {
websiteId: string;
@ -22,6 +23,21 @@ export interface FunnelResponse {
endAt: number;
}
const schema = {
POST: yup.object().shape({
websiteId: yup.string().uuid().required(),
urls: yup.array().min(2).of(yup.string()).required(),
window: yup.number().positive().required(),
dateRange: yup
.object()
.shape({
startDate: yup.date().required(),
endDate: yup.date().required(),
})
.required(),
}),
};
export default async (
req: NextApiRequestQueryBody<any, FunnelRequestBody>,
res: NextApiResponse<FunnelResponse>,
@ -29,6 +45,9 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
if (req.method === 'POST') {
const {
websiteId,

View File

@ -1,10 +1,11 @@
import { canViewWebsite } from 'lib/auth';
import { uuid } from 'lib/crypto';
import { useAuth, useCors } from 'lib/middleware';
import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, ReportSearchFilterType, SearchFilter } from 'lib/types';
import { getFilterValidation } from 'lib/yup';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { createReport, getReportsByUserId, getReportsByWebsiteId } from 'queries';
import { methodNotAllowed, ok } from 'next-basics';
import { createReport, getReportsByUserId } from 'queries';
import * as yup from 'yup';
export interface ReportsRequestQuery extends SearchFilter<ReportSearchFilterType> {}
@ -14,11 +15,28 @@ export interface ReportRequestBody {
type: string;
description: string;
parameters: {
window: string;
urls: string[];
[key: string]: any;
};
}
const schema = {
GET: yup.object().shape({
...getFilterValidation(/All|Name|Description|Type|Username|Website Name|Website Domain/i),
}),
POST: yup.object().shape({
websiteId: yup.string().uuid().required(),
name: yup.string().max(200).required(),
type: yup
.string()
.matches(/funnel|insights|retention/i)
.required(),
description: yup.string().max(500),
parameters: yup
.object()
.test('len', 'Must not exceed 6000 characters.', val => JSON.stringify(val).length < 6000),
}),
};
export default async (
req: NextApiRequestQueryBody<any, ReportRequestBody>,
res: NextApiResponse,
@ -26,6 +44,9 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const {
user: { id: userId },
} = req.auth;

View File

@ -1,9 +1,10 @@
import { canViewWebsite } from 'lib/auth';
import { useCors, useAuth } from 'lib/middleware';
import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types';
import { NextApiResponse } from 'next';
import { ok, methodNotAllowed, unauthorized } from 'next-basics';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getInsights } from 'queries';
import * as yup from 'yup';
export interface InsightsRequestBody {
websiteId: string;
@ -16,6 +17,37 @@ export interface InsightsRequestBody {
groups: { name: string; type: string }[];
}
const schema = {
POST: yup.object().shape({
websiteId: yup.string().uuid().required(),
dateRange: yup
.object()
.shape({
startDate: yup.date().required(),
endDate: yup.date().required(),
})
.required(),
fields: yup
.array()
.of(
yup.object().shape({
name: yup.string().required(),
type: yup.string().required(),
value: yup.string().required(),
}),
)
.min(1)
.required(),
filters: yup.array().of(yup.string()).min(1).required(),
groups: yup.array().of(
yup.object().shape({
name: yup.string().required(),
type: yup.string().required(),
}),
),
}),
};
function convertFilters(filters) {
return filters.reduce((obj, { name, ...value }) => {
obj[name] = value;
@ -31,6 +63,9 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
if (req.method === 'POST') {
const {
websiteId,

View File

@ -1,33 +1,43 @@
import { canViewWebsite } from 'lib/auth';
import { useCors, useAuth } from 'lib/middleware';
import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types';
import { NextApiResponse } from 'next';
import { ok, methodNotAllowed, unauthorized } from 'next-basics';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getRetention } from 'queries';
import * as yup from 'yup';
export interface RetentionRequestBody {
websiteId: string;
dateRange: { window; startDate: string; endDate: string };
timezone: string;
dateRange: { startDate: string; endDate: string };
}
export interface RetentionResponse {
startAt: number;
endAt: number;
}
const schema = {
POST: yup.object().shape({
websiteId: yup.string().uuid().required(),
dateRange: yup
.object()
.shape({
startDate: yup.date().required(),
endDate: yup.date().required(),
})
.required(),
}),
};
export default async (
req: NextApiRequestQueryBody<any, RetentionRequestBody>,
res: NextApiResponse<RetentionResponse>,
res: NextApiResponse,
) => {
await useCors(req, res);
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
if (req.method === 'POST') {
const {
websiteId,
dateRange: { startDate, endDate },
timezone,
} = req.body;
if (!(await canViewWebsite(req.auth, websiteId))) {
@ -37,7 +47,6 @@ export default async (
const data = await getRetention(websiteId, {
startDate: new Date(startDate),
endDate: new Date(endDate),
timezone,
});
return ok(res, data);

View File

@ -1,14 +1,15 @@
import isbot from 'isbot';
import ipaddr from 'ipaddr.js';
import { createToken, ok, send, badRequest, forbidden } from 'next-basics';
import { saveEvent, saveSessionData } from 'queries';
import { useCors, useSession } from 'lib/middleware';
import { getJsonBody, getIpAddress } from 'lib/detect';
import { secret } from 'lib/crypto';
import { NextApiRequest, NextApiResponse } from 'next';
import { Resolver } from 'dns/promises';
import { CollectionType } from 'lib/types';
import { COLLECTION_TYPE } from 'lib/constants';
import ipaddr from 'ipaddr.js';
import isbot from 'isbot';
import { COLLECTION_TYPE, HOSTNAME_REGEX } from 'lib/constants';
import { secret } from 'lib/crypto';
import { getIpAddress, getJsonBody } from 'lib/detect';
import { useCors, useSession, useValidate } from 'lib/middleware';
import { CollectionType, YupRequest } from 'lib/types';
import { NextApiRequest, NextApiResponse } from 'next';
import { badRequest, createToken, forbidden, ok, send } from 'next-basics';
import { saveEvent, saveSessionData } from 'queries';
import * as yup from 'yup';
export interface CollectRequestBody {
payload: {
@ -43,8 +44,32 @@ export interface NextApiRequestCollect extends NextApiRequest {
city: string;
};
headers: { [key: string]: any };
yup: YupRequest;
}
const schema = {
POST: yup.object().shape({
payload: yup
.object()
.shape({
data: yup.object(),
hostname: yup.string().matches(HOSTNAME_REGEX).max(100),
language: yup.string().max(35),
referrer: yup.string().max(500),
screen: yup.string().max(11),
title: yup.string().max(500),
url: yup.string().max(500),
website: yup.string().uuid().required(),
name: yup.string().max(50),
})
.required(),
type: yup
.string()
.matches(/event|identify/i)
.required(),
}),
};
export default async (req: NextApiRequestCollect, res: NextApiResponse) => {
await useCors(req, res);
@ -54,11 +79,8 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => {
const { type, payload } = getJsonBody<CollectRequestBody>(req);
const error = validateBody({ type, payload });
if (error) {
return badRequest(res, error);
}
req.yup = schema;
await useValidate(req, res);
if (await hasBlockedIp(req)) {
return forbidden(res);
@ -118,22 +140,6 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => {
return send(res, token);
};
function validateBody({ type, payload }: CollectRequestBody) {
if (!type || !payload) {
return 'Invalid payload.';
}
if (type !== COLLECTION_TYPE.event && type !== COLLECTION_TYPE.identify) {
return 'Wrong payload type.';
}
const { data } = payload;
if (data && !(typeof data === 'object' && !Array.isArray(data))) {
return 'Invalid event data.';
}
}
async function hasBlockedIp(req: NextApiRequestCollect) {
const ignoreIps = process.env.IGNORE_IP;
const ignoreHostnames = process.env.IGNORE_HOSTNAME;

View File

@ -1,8 +1,10 @@
import { NextApiRequestQueryBody } from 'lib/types';
import { secret } from 'lib/crypto';
import { useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types';
import { NextApiResponse } from 'next';
import { createToken, methodNotAllowed, notFound, ok } from 'next-basics';
import { getWebsiteByShareId } from 'queries';
import * as yup from 'yup';
export interface ShareRequestQuery {
id: string;
@ -13,10 +15,19 @@ export interface ShareResponse {
token: string;
}
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
}),
};
export default async (
req: NextApiRequestQueryBody<ShareRequestQuery>,
res: NextApiResponse<ShareResponse>,
) => {
req.yup = schema;
await useValidate(req, res);
const { id: shareId } = req.query;
if (req.method === 'GET') {

View File

@ -1,10 +1,11 @@
import { Team } from '@prisma/client';
import { NextApiRequestQueryBody } from 'lib/types';
import { canDeleteTeam, canUpdateTeam, canViewTeam } from 'lib/auth';
import { useAuth } from 'lib/middleware';
import { useAuth, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { deleteTeam, getTeamById, updateTeam } from 'queries';
import * as yup from 'yup';
export interface TeamRequestQuery {
id: string;
@ -15,12 +16,29 @@ export interface TeamRequestBody {
accessCode: string;
}
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
}),
POST: yup.object().shape({
id: yup.string().uuid().required(),
name: yup.string().max(50).required(),
accessCode: yup.string().max(50).required(),
}),
DELETE: yup.object().shape({
id: yup.string().uuid().required(),
}),
};
export default async (
req: NextApiRequestQueryBody<TeamRequestQuery, TeamRequestBody>,
res: NextApiResponse<Team>,
) => {
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { id: teamId } = req.query;
if (req.method === 'GET') {

View File

@ -1,18 +1,28 @@
import { canDeleteTeamUser } from 'lib/auth';
import { useAuth } from 'lib/middleware';
import { useAuth, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { deleteTeamUser } from 'queries';
import * as yup from 'yup';
export interface TeamUserRequestQuery {
id: string;
userId: string;
}
const schema = {
DELETE: yup.object().shape({
id: yup.string().uuid().required(),
userId: yup.string().uuid().required(),
}),
};
export default async (req: NextApiRequestQueryBody<TeamUserRequestQuery>, res: NextApiResponse) => {
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
if (req.method === 'DELETE') {
const { id: teamId, userId } = req.query;

View File

@ -1,9 +1,9 @@
import { canUpdateTeam, canViewTeam } from 'lib/auth';
import { canViewTeam } from 'lib/auth';
import { useAuth } from 'lib/middleware';
import { NextApiRequestQueryBody, SearchFilter, TeamSearchFilterType } from 'lib/types';
import { NextApiResponse } from 'next';
import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics';
import { createTeamUser, getUserByUsername, getUsersByTeamId } from 'queries';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getUsersByTeamId } from 'queries';
export interface TeamUserRequestQuery extends SearchFilter<TeamSearchFilterType> {
id: string;
@ -38,24 +38,5 @@ export default async (
return ok(res, users);
}
if (req.method === 'POST') {
if (!(await canUpdateTeam(req.auth, teamId))) {
return unauthorized(res, 'You must be the owner of this team.');
}
const { email, roleId: roleId } = req.body;
// Check for User
const user = await getUserByUsername(email);
if (!user) {
return badRequest(res, 'The User does not exists.');
}
const updated = await createTeamUser(user.id, teamId, roleId);
return ok(res, updated);
}
return methodNotAllowed(res);
};

View File

@ -1,21 +1,32 @@
import { canDeleteTeamWebsite } from 'lib/auth';
import { useAuth } from 'lib/middleware';
import { useAuth, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { deleteTeamWebsite } from 'queries/admin/teamWebsite';
import * as yup from 'yup';
export interface TeamWebsitesRequestQuery {
id: string;
websiteId: string;
}
const schema = {
DELETE: yup.object().shape({
id: yup.string().uuid().required(),
websiteId: yup.string().uuid().required(),
}),
};
export default async (
req: NextApiRequestQueryBody<TeamWebsitesRequestQuery>,
res: NextApiResponse,
) => {
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { id: teamId, websiteId } = req.query;
if (req.method === 'DELETE') {

View File

@ -1,9 +1,10 @@
import { canViewTeam } from 'lib/auth';
import { useAuth } from 'lib/middleware';
import { useAuth, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, SearchFilter, WebsiteSearchFilterType } from 'lib/types';
import { getFilterValidation } from 'lib/yup';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getWebsites, getWebsitesByTeamId } from 'queries';
import { getWebsitesByTeamId } from 'queries';
import { createTeamWebsites } from 'queries/admin/teamWebsite';
export interface TeamWebsiteRequestQuery extends SearchFilter<WebsiteSearchFilterType> {
@ -14,12 +15,28 @@ export interface TeamWebsiteRequestBody {
websiteIds?: string[];
}
import * as yup from 'yup';
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
...getFilterValidation(/All|Name|Domain/i),
}),
POST: yup.object().shape({
id: yup.string().uuid().required(),
websiteIds: yup.array().of(yup.string()).min(1).required(),
}),
};
export default async (
req: NextApiRequestQueryBody<TeamWebsiteRequestQuery, TeamWebsiteRequestBody>,
res: NextApiResponse,
) => {
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { id: teamId } = req.query;
if (req.method === 'GET') {

View File

@ -1,23 +1,39 @@
import { Team } from '@prisma/client';
import { canCreateTeam } from 'lib/auth';
import { uuid } from 'lib/crypto';
import { useAuth } from 'lib/middleware';
import { useAuth, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, SearchFilter, TeamSearchFilterType } from 'lib/types';
import { getFilterValidation } from 'lib/yup';
import { NextApiResponse } from 'next';
import { getRandomChars, methodNotAllowed, ok, unauthorized } from 'next-basics';
import { createTeam, getTeamsByUserId } from 'queries';
import * as yup from 'yup';
export interface TeamsRequestQuery extends SearchFilter<TeamSearchFilterType> {}
export interface TeamsRequestBody extends SearchFilter<TeamSearchFilterType> {
export interface TeamsRequestBody {
name: string;
}
export interface MyTeamsRequestQuery extends SearchFilter<TeamSearchFilterType> {}
const schema = {
GET: yup.object().shape({
...getFilterValidation(/All|Name|Owner/i),
}),
POST: yup.object().shape({
name: yup.string().max(50).required(),
}),
};
export default async (
req: NextApiRequestQueryBody<TeamsRequestQuery, TeamsRequestBody>,
res: NextApiResponse<Team[] | Team>,
) => {
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const {
user: { id: userId },
} = req.auth;

View File

@ -1,21 +1,30 @@
import { Team } from '@prisma/client';
import { NextApiRequestQueryBody } from 'lib/types';
import { useAuth } from 'lib/middleware';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, notFound } from 'next-basics';
import { createTeamUser, getTeamByAccessCode, getTeamUser } from 'queries';
import { ROLES } from 'lib/constants';
import { useAuth, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types';
import { NextApiResponse } from 'next';
import { methodNotAllowed, notFound, ok } from 'next-basics';
import { createTeamUser, getTeamByAccessCode, getTeamUser } from 'queries';
import * as yup from 'yup';
export interface TeamsJoinRequestBody {
accessCode: string;
}
const schema = {
POST: yup.object().shape({
accessCode: yup.string().max(50).required(),
}),
};
export default async (
req: NextApiRequestQueryBody<any, TeamsJoinRequestBody>,
res: NextApiResponse<Team>,
) => {
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
if (req.method === 'POST') {
const { accessCode } = req.body;

View File

@ -1,9 +1,10 @@
import { NextApiRequestQueryBody, Role, User } from 'lib/types';
import { canDeleteUser, canUpdateUser, canViewUser } from 'lib/auth';
import { useAuth } from 'lib/middleware';
import { useAuth, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, Role, User } from 'lib/types';
import { NextApiResponse } from 'next';
import { badRequest, hashPassword, methodNotAllowed, ok, unauthorized } from 'next-basics';
import { deleteUser, getUserById, getUserByUsername, updateUser } from 'queries';
import * as yup from 'yup';
export interface UserRequestQuery {
id: string;
@ -15,12 +16,27 @@ export interface UserRequestBody {
role: Role;
}
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
}),
POST: yup.object().shape({
id: yup.string().uuid().required(),
username: yup.string().max(255),
password: yup.string(),
role: yup.string().matches(/admin|user|view-only/i),
}),
};
export default async (
req: NextApiRequestQueryBody<UserRequestQuery, UserRequestBody>,
res: NextApiResponse<User>,
) => {
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const {
user: { id: userId, isAdmin },
} = req.auth;

View File

@ -1,9 +1,10 @@
import { useAuth, useCors } from 'lib/middleware';
import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, SearchFilter, TeamSearchFilterType } from 'lib/types';
import { getFilterValidation } from 'lib/yup';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getTeamsByUserId } from 'queries';
import * as yup from 'yup';
export interface UserTeamsRequestQuery extends SearchFilter<TeamSearchFilterType> {
id: string;
}
@ -14,6 +15,13 @@ export interface UserTeamsRequestBody {
shareId: string;
}
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
...getFilterValidation('/All|Name|Owner/i'),
}),
};
export default async (
req: NextApiRequestQueryBody<any, UserTeamsRequestBody>,
res: NextApiResponse,
@ -21,6 +29,9 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { user } = req.auth;
const { id: userId } = req.query;

View File

@ -1,8 +1,9 @@
import { useAuth, useCors } from 'lib/middleware';
import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getEventDataUsage, getEventUsage, getUserWebsites } from 'queries';
import * as yup from 'yup';
export interface UserUsageRequestQuery {
id: string;
@ -21,6 +22,14 @@ export interface UserUsageRequestResponse {
}[];
}
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
startAt: yup.number().integer().required(),
endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(),
}),
};
export default async (
req: NextApiRequestQueryBody<UserUsageRequestQuery>,
res: NextApiResponse<UserUsageRequestResponse>,
@ -28,6 +37,9 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { user } = req.auth;
if (req.method === 'GET') {

View File

@ -1,25 +1,36 @@
import { useAuth, useCors } from 'lib/middleware';
import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, SearchFilter, WebsiteSearchFilterType } from 'lib/types';
import { getFilterValidation } from 'lib/yup';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getWebsitesByUserId } from 'queries';
import * as yup from 'yup';
export interface UserWebsitesRequestQuery extends SearchFilter<WebsiteSearchFilterType> {
id: string;
}
export interface UserWebsitesRequestBody {
name: string;
domain: string;
shareId: string;
includeTeams?: boolean;
onlyTeams?: boolean;
}
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
includeTeams: yup.boolean(),
onlyTeams: yup.boolean(),
...getFilterValidation(/All|Name|Domain/i),
}),
};
export default async (
req: NextApiRequestQueryBody<any, UserWebsitesRequestBody>,
req: NextApiRequestQueryBody<UserWebsitesRequestQuery>,
res: NextApiResponse,
) => {
await useCors(req, res);
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { user } = req.auth;
const { id: userId, page, filter, pageSize, includeTeams, onlyTeams } = req.query;

View File

@ -1,8 +1,9 @@
import { canCreateUser, canViewUsers } from 'lib/auth';
import { ROLES } from 'lib/constants';
import { uuid } from 'lib/crypto';
import { useAuth } from 'lib/middleware';
import { useAuth, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, Role, SearchFilter, User, UserSearchFilterType } from 'lib/types';
import { getFilterValidation } from 'lib/yup';
import { NextApiResponse } from 'next';
import { badRequest, hashPassword, methodNotAllowed, ok, unauthorized } from 'next-basics';
import { createUser, getUserByUsername, getUsers } from 'queries';
@ -15,12 +16,31 @@ export interface UsersRequestBody {
role?: Role;
}
import * as yup from 'yup';
const schema = {
GET: yup.object().shape({
...getFilterValidation(/All|Username/i),
}),
POST: yup.object().shape({
username: yup.string().max(255).required(),
password: yup.string().required(),
id: yup.string().uuid(),
role: yup
.string()
.matches(/admin|user|view-only/i)
.required(),
}),
};
export default async (
req: NextApiRequestQueryBody<UsersRequestQuery, UsersRequestBody>,
res: NextApiResponse<User[] | User>,
) => {
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
if (req.method === 'GET') {
if (!(await canViewUsers(req.auth))) {
return unauthorized(res);
@ -28,7 +48,7 @@ export default async (
const { page, filter, pageSize } = req.query;
const users = await getUsers({ page, filter, pageSize: +pageSize || null });
const users = await getUsers({ page, filter, pageSize: pageSize ? +pageSize : null });
return ok(res, users);
}

View File

@ -1,14 +1,21 @@
import { WebsiteActive, NextApiRequestQueryBody } from 'lib/types';
import { canViewWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware';
import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getActiveVisitors } from 'queries';
import * as yup from 'yup';
export interface WebsiteActiveRequestQuery {
id: string;
}
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
}),
};
export default async (
req: NextApiRequestQueryBody<WebsiteActiveRequestQuery>,
res: NextApiResponse<WebsiteActive>,
@ -16,6 +23,9 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { id: websiteId } = req.query;
if (req.method === 'GET') {

View File

@ -1,14 +1,21 @@
import { WebsiteActive, NextApiRequestQueryBody } from 'lib/types';
import { canViewWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware';
import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getWebsiteDateRange } from 'queries';
import * as yup from 'yup';
export interface WebsiteDateRangeRequestQuery {
id: string;
}
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
}),
};
export default async (
req: NextApiRequestQueryBody<WebsiteDateRangeRequestQuery>,
res: NextApiResponse<WebsiteActive>,
@ -16,6 +23,9 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { id: websiteId } = req.query;
if (req.method === 'GET') {

View File

@ -1,6 +1,6 @@
import { WebsiteMetric, NextApiRequestQueryBody } from 'lib/types';
import { canViewWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware';
import { useAuth, useCors, useValidate } from 'lib/middleware';
import moment from 'moment-timezone';
import { NextApiResponse } from 'next';
import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics';
@ -16,9 +16,21 @@ export interface WebsiteEventsRequestQuery {
unit: string;
timezone: string;
url: string;
eventName: string;
}
import * as yup from 'yup';
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
startAt: yup.number().integer().required(),
endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(),
unit: yup.string().required(),
timezone: yup.string().required(),
url: yup.string(),
}),
};
export default async (
req: NextApiRequestQueryBody<WebsiteEventsRequestQuery>,
res: NextApiResponse<WebsiteMetric>,
@ -26,7 +38,10 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
const { id: websiteId, timezone, url, eventName } = req.query;
req.yup = schema;
await useValidate(req, res);
const { id: websiteId, timezone, url } = req.query;
const { startDate, endDate, unit } = await parseDateRangeQuery(req);
if (req.method === 'GET') {
@ -44,7 +59,6 @@ export default async (
timezone,
unit,
url,
eventName,
});
return ok(res, events);

View File

@ -2,7 +2,7 @@ import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, serverError, unauthorized } from 'next-basics';
import { Website, NextApiRequestQueryBody } from 'lib/types';
import { canViewWebsite, canUpdateWebsite, canDeleteWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware';
import { useAuth, useCors, useValidate } from 'lib/middleware';
import { deleteWebsite, getWebsiteById, updateWebsite } from 'queries';
import { SHARE_ID_REGEX } from 'lib/constants';
@ -16,6 +16,13 @@ export interface WebsiteRequestBody {
shareId: string;
}
import * as yup from 'yup';
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
}),
};
export default async (
req: NextApiRequestQueryBody<WebsiteRequestQuery, WebsiteRequestBody>,
res: NextApiResponse<Website>,
@ -23,6 +30,9 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { id: websiteId } = req.query;
if (req.method === 'GET') {

View File

@ -2,10 +2,11 @@ import { NextApiResponse } from 'next';
import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics';
import { WebsiteMetric, NextApiRequestQueryBody } from 'lib/types';
import { canViewWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware';
import { useAuth, useCors, useValidate } from 'lib/middleware';
import { SESSION_COLUMNS, EVENT_COLUMNS, FILTER_COLUMNS } from 'lib/constants';
import { getPageviewMetrics, getSessionMetrics } from 'queries';
import { parseDateRangeQuery } from 'lib/query';
import * as yup from 'yup';
export interface WebsiteMetricsRequestQuery {
id: string;
@ -26,6 +27,12 @@ export interface WebsiteMetricsRequestQuery {
language: string;
}
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
}),
};
export default async (
req: NextApiRequestQueryBody<WebsiteMetricsRequestQuery>,
res: NextApiResponse<WebsiteMetric[]>,
@ -33,6 +40,9 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const {
id: websiteId,
type,

View File

@ -3,7 +3,7 @@ import { NextApiResponse } from 'next';
import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics';
import { NextApiRequestQueryBody, WebsitePageviews } from 'lib/types';
import { canViewWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware';
import { useAuth, useCors, useValidate } from 'lib/middleware';
import { getPageviewStats, getSessionStats } from 'queries';
import { parseDateRangeQuery } from 'lib/query';
@ -24,6 +24,13 @@ export interface WebsitePageviewRequestQuery {
city?: string;
}
import * as yup from 'yup';
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
}),
};
export default async (
req: NextApiRequestQueryBody<WebsitePageviewRequestQuery>,
res: NextApiResponse<WebsitePageviews>,
@ -31,6 +38,9 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const {
id: websiteId,
timezone,

View File

@ -1,5 +1,5 @@
import { canViewWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware';
import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, ReportSearchFilterType, SearchFilter } from 'lib/types';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
@ -9,6 +9,13 @@ export interface ReportsRequestQuery extends SearchFilter<ReportSearchFilterType
id: string;
}
import * as yup from 'yup';
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
}),
};
export default async (
req: NextApiRequestQueryBody<ReportsRequestQuery, any>,
res: NextApiResponse,
@ -16,6 +23,9 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { id: websiteId } = req.query;
if (req.method === 'GET') {

View File

@ -1,6 +1,6 @@
import { NextApiRequestQueryBody } from 'lib/types';
import { canUpdateWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware';
import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { resetWebsite } from 'queries';
@ -9,6 +9,13 @@ export interface WebsiteResetRequestQuery {
id: string;
}
import * as yup from 'yup';
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
}),
};
export default async (
req: NextApiRequestQueryBody<WebsiteResetRequestQuery>,
res: NextApiResponse,
@ -16,6 +23,9 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { id: websiteId } = req.query;
if (req.method === 'POST') {

View File

@ -2,7 +2,7 @@ import { subMinutes, differenceInMinutes } from 'date-fns';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { canViewWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware';
import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, WebsiteStats } from 'lib/types';
import { parseDateRangeQuery } from 'lib/query';
import { getWebsiteStats } from 'queries';
@ -24,6 +24,13 @@ export interface WebsiteStatsRequestQuery {
city: string;
}
import * as yup from 'yup';
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
}),
};
export default async (
req: NextApiRequestQueryBody<WebsiteStatsRequestQuery>,
res: NextApiResponse<WebsiteStats>,
@ -31,6 +38,9 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const {
id: websiteId,
url,

View File

@ -1,6 +1,6 @@
import { NextApiRequestQueryBody } from 'lib/types';
import { canViewWebsite } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware';
import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiResponse } from 'next';
import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics';
import { EVENT_COLUMNS, FILTER_COLUMNS, SESSION_COLUMNS } from 'lib/constants';
@ -10,6 +10,13 @@ export interface WebsiteResetRequestQuery {
id: string;
}
import * as yup from 'yup';
const schema = {
GET: yup.object().shape({
id: yup.string().uuid().required(),
}),
};
export default async (
req: NextApiRequestQueryBody<WebsiteResetRequestQuery>,
res: NextApiResponse,
@ -17,6 +24,9 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const { id: websiteId, type } = req.query;
if (req.method === 'GET') {

View File

@ -1,11 +1,13 @@
import { canCreateWebsite } from 'lib/auth';
import { uuid } from 'lib/crypto';
import { useAuth, useCors } from 'lib/middleware';
import { useAuth, useCors, useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, SearchFilter, WebsiteSearchFilterType } from 'lib/types';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { createWebsite } from 'queries';
import userWebsites from 'pages/api/users/[id]/websites';
import * as yup from 'yup';
import { getFilterValidation } from 'lib/yup';
export interface WebsitesRequestQuery extends SearchFilter<WebsiteSearchFilterType> {}
@ -15,12 +17,25 @@ export interface WebsitesRequestBody {
shareId: string;
}
const schema = {
GET: yup.object().shape({
...getFilterValidation(/All|Name|Domain/i),
}),
POST: yup.object().shape({
name: yup.string().max(100).required(),
domain: yup.string().max(500).required(),
shareId: yup.string().max(50),
}),
};
export default async (
req: NextApiRequestQueryBody<WebsitesRequestQuery, WebsitesRequestBody>,
res: NextApiResponse,
) => {
await useCors(req, res);
await useAuth(req, res);
req.yup = schema;
await useValidate(req, res);
const {
user: { id: userId },
@ -30,7 +45,7 @@ export default async (
req.query.id = userId;
req.query.pageSize = 100;
return userWebsites(req, res);
return userWebsites(req as any, res);
}
if (req.method === 'POST') {