refactor: api

This commit is contained in:
diced 2022-11-03 20:40:47 -07:00
parent 3c616f4f6f
commit 69d10ef429
No known key found for this signature in database
GPG key ID: 370BD1BA142842D1
19 changed files with 285 additions and 230 deletions

View file

@ -6,6 +6,7 @@ import { sign64, unsign64 } from 'lib/utils/crypto';
import config from 'lib/config';
import prisma from 'lib/prisma';
import { OAuth, User } from '@prisma/client';
import { HTTPMethod } from 'find-my-way';
export interface NextApiFile {
fieldname: string;
@ -16,7 +17,7 @@ export interface NextApiFile {
size: number;
}
interface UserExtended extends User {
export interface UserExtended extends User {
oauth: OAuth[];
}
@ -27,55 +28,98 @@ export type NextApiReq = NextApiRequest & {
files?: NextApiFile[];
};
export type NextApiRes = NextApiResponse & {
error: (message: string) => void;
forbid: (message: string, extra?: any) => void;
bad: (message: string) => void;
json: (json: Record<string, any>, status?: number) => void;
ratelimited: (remaining: number) => void;
setCookie: (name: string, value: unknown, options: CookieSerializeOptions) => void;
setUserCookie: (id: number) => void;
export type NextApiResExtra =
| 'badRequest'
| 'unauthorized'
| 'forbidden'
| 'ratelimited'
| 'notFound'
| 'error';
export type NextApiResExtraObj = {
[key in NextApiResExtra]: (message: any, extra?: Record<string, any>) => void;
};
export type NextApiRes = NextApiResponse &
NextApiResExtraObj & {
json: (json: Record<string, any>, status?: number) => void;
setCookie: (name: string, value: unknown, options: CookieSerializeOptions) => void;
setUserCookie: (id: number) => void;
};
export type ZiplineApiConfig = {
methods: HTTPMethod[];
user?: boolean;
administrator?: boolean;
middleware?: any[];
};
export const withZipline =
(handler: (req: NextApiRequest, res: NextApiResponse) => unknown) => (req: NextApiReq, res: NextApiRes) => {
(
handler: (req: NextApiRequest, res: NextApiResponse, user?: UserExtended) => unknown,
api_config: ZiplineApiConfig = { methods: ['GET'] }
) =>
(req: NextApiReq, res: NextApiRes) => {
res.setHeader('Content-Type', 'application/json');
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Content-Allow-Methods', 'GET,HEAD,POST,OPTIONS');
res.setHeader('Access-Control-Max-Age', '86400');
res.error = (message: string) => {
// Used when the client sends wrong information, etc.
res.badRequest = (message: string, extra: Record<string, any> = {}) => {
res.json(
{
error: message,
code: 400,
...extra,
},
500
400
);
};
res.forbid = (message: string, extra: any = {}) => {
// If the user is not logged in
res.unauthorized = (message: string, extra: Record<string, any> = {}) => {
res.json(
{
error: '403: ' + message,
error: message,
code: 401,
...extra,
},
401
);
};
// If the user is logged in but doesn't have permission to do something
res.forbidden = (message: string, extra: Record<string, any> = {}) => {
res.json(
{
error: message,
code: 403,
...extra,
},
403
);
};
res.bad = (message: string) => {
res.notFound = (message: string, extra: Record<string, any> = {}) => {
res.json(
{
error: '401: ' + message,
error: message,
code: 404,
...extra,
},
401
404
);
};
res.ratelimited = (remaining: number) => {
res.setHeader('X-Ratelimit-Remaining', Math.floor(remaining / 1000)).json(
res.ratelimited = (message: number, extra: Record<string, any> = {}) => {
const retry = Math.floor(message / 1000);
res.setHeader('X-Ratelimit-Remaining', retry);
res.json(
{
error: '429: ratelimited',
error: `ratelimited - try again in ${retry} seconds`,
code: 429,
...extra,
},
429
);
@ -129,8 +173,16 @@ export const withZipline =
}
};
res.setCookie = (name: string, value: unknown, options?: CookieSerializeOptions) =>
setCookie(res, name, value, options || {});
res.setCookie = (name: string, value: unknown, options: CookieSerializeOptions = {}) => {
if ('maxAge' in options) {
options.expires = new Date(Date.now() + options.maxAge * 1000);
options.maxAge /= 1000;
}
const signed = sign64(String(value), config.core.secret);
res.setHeader('Set-Cookie', serialize(name, signed, options));
};
res.setUserCookie = (id: number) => {
req.cleanCookie('user');
@ -141,21 +193,32 @@ export const withZipline =
});
};
if (!api_config.methods.includes(req.method as HTTPMethod)) {
return res.json(
{
error: 'method not allowed',
code: 405,
},
405
);
}
if (api_config.middleware) {
for (let i = 0; i !== api_config.middleware.length; ++i) {
api_config.middleware[i](req, res, (result) => {
if (result instanceof Error) return res.error(result.message);
});
}
}
if (api_config.user) {
return req.user().then((user) => {
if (!user) return res.unauthorized('not logged in');
if (api_config.administrator && !user.administrator) return res.forbidden('not an administrator');
return handler(req, res, user);
});
}
return handler(req, res);
};
export const setCookie = (
res: NextApiResponse,
name: string,
value: unknown,
options: CookieSerializeOptions = {}
) => {
if ('maxAge' in options) {
options.expires = new Date(Date.now() + options.maxAge * 1000);
options.maxAge /= 1000;
}
const signed = sign64(String(value), config.core.secret);
res.setHeader('Set-Cookie', serialize(name, signed, options));
};

View file

@ -5,10 +5,11 @@ import Logger from 'lib/logger';
import config from 'lib/config';
async function handler(req: NextApiReq, res: NextApiRes) {
// handle invites
if (req.method === 'POST' && req.body) {
if (!config.features.invites && req.body.code) return res.forbid('invites are disabled');
if (!config.features.invites && req.body.code) return res.badRequest('invites are disabled');
if (!config.features.user_registration && !req.body.code)
return res.forbid('user registration is disabled');
return res.badRequest('user registration is disabled');
const { code, username, password } = req.body as {
code?: string;
@ -18,13 +19,13 @@ async function handler(req: NextApiReq, res: NextApiRes) {
const invite = await prisma.invite.findUnique({
where: { code: code ?? '' },
});
if (!invite && code) return res.bad('invalid invite code');
if (!invite && code) return res.badRequest('invalid invite code');
const user = await prisma.user.findFirst({
where: { username },
});
if (user) return res.bad('username already exists');
if (user) return res.badRequest('username already exists');
const hashed = await hashPassword(password);
const newUser = await prisma.user.create({
data: {
@ -56,8 +57,8 @@ async function handler(req: NextApiReq, res: NextApiRes) {
}
const user = await req.user();
if (!user) return res.forbid('not logged in');
if (!user.administrator) return res.forbid('you arent an administrator');
if (!user) return res.unauthorized('not logged in');
if (!user.administrator) return res.forbidden('you arent an administrator');
if (req.method !== 'POST') return res.status(405).end();
@ -67,15 +68,15 @@ async function handler(req: NextApiReq, res: NextApiRes) {
administrator: boolean;
};
if (!username) return res.bad('no username');
if (!password) return res.bad('no auth');
if (!username) return res.badRequest('no username');
if (!password) return res.badRequest('no password');
const existing = await prisma.user.findFirst({
where: {
username,
},
});
if (existing) return res.forbid('user exists');
if (existing) return res.badRequest('user exists');
const hashed = await hashPassword(password);
@ -95,4 +96,6 @@ async function handler(req: NextApiReq, res: NextApiRes) {
return res.json(newUser);
}
export default withZipline(handler);
export default withZipline(handler, {
methods: ['POST'],
});

View file

@ -15,13 +15,13 @@ async function handler(req: NextApiReq, res: NextApiRes) {
});
if (!image) return res.status(404).end(JSON.stringify({ error: 'Image not found' }));
if (!password) return res.forbid('No password provided');
if (!password) return res.badRequest('No password provided');
const valid = await checkPassword(password as string, image.password);
if (!valid) return res.forbid('Wrong password');
if (!valid) return res.badRequest('Wrong password');
const data = await datasource.get(image.file);
if (!data) return res.error('Image not found');
if (!data) return res.notFound('Image not found');
const size = await datasource.size(image.file);

View file

@ -1,15 +1,11 @@
import prisma from 'lib/prisma';
import { NextApiReq, NextApiRes, withZipline } from 'lib/middleware/withZipline';
import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'lib/middleware/withZipline';
import { randomChars } from 'lib/util';
import Logger from 'lib/logger';
import config from 'lib/config';
async function handler(req: NextApiReq, res: NextApiRes) {
if (!config.features.invites) return res.forbid('invites are disabled');
const user = await req.user();
if (!user) return res.forbid('not logged in');
if (!user.administrator) return res.forbid('you arent an administrator');
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
if (!config.features.invites) return res.badRequest('invites are disabled');
if (req.method === 'POST') {
const { expires_at, count } = req.body as {
@ -19,8 +15,8 @@ async function handler(req: NextApiReq, res: NextApiRes) {
const expiry = expires_at ? new Date(expires_at) : null;
if (expiry) {
if (!expiry.getTime()) return res.bad('invalid date');
if (expiry.getTime() < Date.now()) return res.bad('date is in the past');
if (!expiry.getTime()) return res.badRequest('invalid date');
if (expiry.getTime() < Date.now()) return res.badRequest('date is in the past');
}
const counts = count ? count : 1;
@ -58,14 +54,6 @@ async function handler(req: NextApiReq, res: NextApiRes) {
return res.json(invite);
}
} else if (req.method === 'GET') {
const invites = await prisma.invite.findMany({
orderBy: {
created_at: 'desc',
},
});
return res.json(invites);
} else if (req.method === 'DELETE') {
const { code } = req.query as { code: string };
@ -78,7 +66,19 @@ async function handler(req: NextApiReq, res: NextApiRes) {
Logger.get('invite').info(`${user.username} (${user.id}) deleted invite ${invite.code}`);
return res.json(invite);
} else {
const invites = await prisma.invite.findMany({
orderBy: {
created_at: 'desc',
},
});
return res.json(invites);
}
}
export default withZipline(handler);
export default withZipline(handler, {
methods: ['GET', 'POST', 'DELETE'],
user: true,
administrator: true,
});

View file

@ -4,7 +4,6 @@ import { checkPassword, createToken, hashPassword } from 'lib/util';
import Logger from 'lib/logger';
async function handler(req: NextApiReq, res: NextApiRes) {
if (req.method !== 'POST') return res.status(405).end();
const { username, password } = req.body as {
username: string;
password: string;
@ -30,10 +29,10 @@ async function handler(req: NextApiReq, res: NextApiRes) {
},
});
if (!user) return res.status(404).end(JSON.stringify({ error: 'User not found' }));
if (!user) return res.notFound('user not found');
const valid = await checkPassword(password, user.password);
if (!valid) return res.forbid('Wrong password');
if (!valid) return res.unauthorized('Wrong password');
res.setUserCookie(user.id);
Logger.get('user').info(`User ${user.username} (${user.id}) logged in`);
@ -41,4 +40,6 @@ async function handler(req: NextApiReq, res: NextApiRes) {
return res.json({ success: true });
}
export default withZipline(handler);
export default withZipline(handler, {
methods: ['POST'],
});

View file

@ -1,10 +1,7 @@
import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
import Logger from 'lib/logger';
async function handler(req: NextApiReq, res: NextApiRes) {
const user = await req.user();
if (!user) return res.forbid('not logged in');
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
req.cleanCookie('user');
Logger.get('user').info(`User ${user.username} (${user.id}) logged out`);
@ -12,4 +9,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
return res.json({ success: true });
}
export default withZipline(handler);
export default withZipline(handler, {
methods: ['GET'],
user: true,
});

View file

@ -1,14 +1,11 @@
import prisma from 'lib/prisma';
import { NextApiReq, NextApiRes, withZipline } from 'lib/middleware/withZipline';
import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'lib/middleware/withZipline';
import { OauthProviders } from '@prisma/client';
async function handler(req: NextApiReq, res: NextApiRes) {
const user = await req.user();
if (!user) return res.error('not logged in');
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
if (req.method === 'DELETE') {
if (!user.password && user.oauth.length === 1)
return res.forbid("can't unlink account without a password, please set one then unlink.");
if (!user.password && user.oauth?.length === 1)
return res.badRequest("can't unlink account without a password, please set one then unlink.");
const { provider } = req.body as { provider: OauthProviders };
@ -44,8 +41,11 @@ async function handler(req: NextApiReq, res: NextApiRes) {
return res.json(nuser);
} else {
return res.json(user.oauth);
return res.json(user.oauth ?? []);
}
}
export default withZipline(handler);
export default withZipline(handler, {
methods: ['DELETE', 'GET'],
user: true,
});

View file

@ -7,8 +7,7 @@ import config from 'lib/config';
import { sendShorten } from 'lib/discord';
async function handler(req: NextApiReq, res: NextApiRes) {
if (req.method !== 'POST') return res.forbid('no allow');
if (!req.headers.authorization) return res.forbid('no authorization');
if (!req.headers.authorization) return res.badRequest('no authorization');
const user = await prisma.user.findFirst({
where: {
@ -16,13 +15,13 @@ async function handler(req: NextApiReq, res: NextApiRes) {
},
});
if (!user) return res.forbid('authorization incorect');
if (!req.body) return res.error('no body');
if (!req.body.url) return res.error('no url');
if (!user) return res.unauthorized('authorization incorect');
if (!req.body) return res.badRequest('no body');
if (!req.body.url) return res.badRequest('no url');
const maxUrlViews = req.headers['max-views'] ? Number(req.headers['max-views']) : null;
if (isNaN(maxUrlViews)) return res.error('invalid max views (invalid number)');
if (maxUrlViews < 0) return res.error('invalid max views (max views < 0)');
if (isNaN(maxUrlViews)) return res.badRequest('invalid max views (invalid number)');
if (maxUrlViews < 0) return res.badRequest('invalid max views (max views < 0)');
const rand = randomChars(zconfig.urls.length);
@ -35,7 +34,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
},
});
if (existing) return res.error('vanity already exists');
if (existing) return res.badRequest('vanity already exists');
}
const url = await prisma.url.create({
@ -71,4 +70,6 @@ async function handler(req: NextApiReq, res: NextApiRes) {
});
}
export default withZipline(handler);
export default withZipline(handler, {
methods: ['POST'],
});

View file

@ -1,17 +1,25 @@
import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
import prisma from 'lib/prisma';
import config from 'lib/config';
import { Stats } from '@prisma/client';
import { getStats } from 'server/util';
import datasource from 'lib/datasource';
async function handler(req: NextApiReq, res: NextApiRes) {
const user = await req.user();
if (!user) return res.forbid('not logged in');
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
if (req.method === 'POST') {
if (!user.administrator) return res.forbidden('not an administrator');
if (req.method === 'GET') {
const stats = await getStats(prisma, datasource);
const stats_data = await prisma.stats.create({
data: {
data: stats,
},
});
return res.json(stats_data);
} else {
let amount = typeof req.query.amount === 'string' ? Number(req.query.amount) : 2;
if (isNaN(amount)) return res.bad('invalid amount');
if (isNaN(amount)) return res.badRequest('invalid amount');
// get stats per day
@ -35,18 +43,10 @@ async function handler(req: NextApiReq, res: NextApiRes) {
}
return res.json(stats);
} else if (req.method === 'POST') {
if (!user.administrator) return res.forbid('unable to force update stats as a non-admin');
const stats = await getStats(prisma, datasource);
const stats_data = await prisma.stats.create({
data: {
data: stats,
},
});
return res.json(stats_data);
}
}
export default withZipline(handler);
export default withZipline(handler, {
methods: ['GET', 'POST'],
user: true,
});

View file

@ -18,8 +18,7 @@ import sharp from 'sharp';
const uploader = multer();
async function handler(req: NextApiReq, res: NextApiRes) {
if (req.method !== 'POST') return res.forbid('invalid method');
if (!req.headers.authorization) return res.forbid('no authorization');
if (!req.headers.authorization) return res.forbidden('no authorization');
const user = await prisma.user.findFirst({
where: {
@ -27,9 +26,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
},
});
if (!user) return res.forbid('authorization incorrect');
await run(uploader.array('file'))(req, res);
if (!user) return res.forbidden('authorization incorrect');
const response: { files: string[]; expires_at?: Date } = { files: [] };
const expires_at = req.headers['expires-at'] as string;
@ -37,7 +34,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
if (expires_at) {
expiry = parseExpiry(expires_at);
if (!expiry) return res.error('invalid date');
if (!expiry) return res.badRequest('invalid date');
else {
response.expires_at = expiry;
}
@ -49,13 +46,14 @@ async function handler(req: NextApiReq, res: NextApiRes) {
const imageCompressionPercent = req.headers['image-compression-percent']
? Number(req.headers['image-compression-percent'])
: null;
if (isNaN(imageCompressionPercent)) return res.error('invalid image compression percent (invalid number)');
if (isNaN(imageCompressionPercent))
return res.badRequest('invalid image compression percent (invalid number)');
if (imageCompressionPercent < 0 || imageCompressionPercent > 100)
return res.error('invalid image compression percent (% < 0 || % > 100)');
return res.badRequest('invalid image compression percent (% < 0 || % > 100)');
const fileMaxViews = req.headers['max-views'] ? Number(req.headers['max-views']) : null;
if (isNaN(fileMaxViews)) return res.error('invalid max views (invalid number)');
if (fileMaxViews < 0) return res.error('invalid max views (max views < 0)');
if (isNaN(fileMaxViews)) return res.badRequest('invalid max views (invalid number)');
if (fileMaxViews < 0) return res.badRequest('invalid max views (max views < 0)');
// handle partial uploads before ratelimits
if (req.headers['content-range']) {
@ -174,17 +172,17 @@ async function handler(req: NextApiReq, res: NextApiRes) {
}
}
if (!req.files) return res.error('no files');
if (req.files && req.files.length === 0) return res.error('no files');
if (!req.files) return res.badRequest('no files');
if (req.files && req.files.length === 0) return res.badRequest('no files');
for (let i = 0; i !== req.files.length; ++i) {
const file = req.files[i];
if (file.size > zconfig.uploader[user.administrator ? 'admin_limit' : 'user_limit'])
return res.error(`file[${i}]: size too big`);
return res.badRequest(`file[${i}]: size too big`);
const ext = file.originalname.split('.').pop();
if (zconfig.uploader.disabled_extensions.includes(ext))
return res.error(`file[${i}]: disabled extension recieved: ${ext}`);
return res.badRequest(`file[${i}]: disabled extension recieved: ${ext}`);
let fileName: string;
switch (format) {
@ -289,19 +287,10 @@ async function handler(req: NextApiReq, res: NextApiRes) {
return res.json(response);
}
function run(middleware: any) {
return (req, res) =>
new Promise((resolve, reject) => {
middleware(req, res, (result) => {
if (result instanceof Error) reject(result);
resolve(result);
});
});
}
export default async function handlers(req, res) {
return withZipline(handler)(req, res);
}
export default withZipline(handler, {
methods: ['POST'],
middleware: [uploader.array('file')],
});
export const config = {
api: {

View file

@ -1,14 +1,9 @@
import prisma from 'lib/prisma';
import { hashPassword } from 'lib/util';
import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
import Logger from 'lib/logger';
async function handler(req: NextApiReq, res: NextApiRes) {
const user = await req.user();
if (!user) return res.forbid('not logged in');
if (!user.administrator) return res.forbid('not an administrator');
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
const { id } = req.query as { id: string };
const target = await prisma.user.findFirst({
@ -17,23 +12,19 @@ async function handler(req: NextApiReq, res: NextApiRes) {
},
});
if (!target) return res.error('user not found');
if (!target) return res.notFound('user not found');
if (req.method === 'GET') {
delete target.password;
return res.json(target);
} else if (req.method === 'DELETE') {
if (req.method === 'DELETE') {
const newTarget = await prisma.user.delete({
where: { id: target.id },
});
if (newTarget.administrator && !user.superAdmin) return res.error('cannot delete administrator');
if (newTarget.administrator && !user.superAdmin) return res.forbidden('cannot delete administrator');
delete newTarget.password;
return res.json(newTarget);
} else if (req.method === 'PATCH') {
if (target.administrator && !user.superAdmin) return res.forbid('cannot modify administrator');
if (target.administrator && !user.superAdmin) return res.forbidden('cannot modify administrator');
if (req.body.password) {
const hashed = await hashPassword(req.body.password);
@ -57,7 +48,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
},
});
if (existing && user.username !== req.body.username) {
return res.forbid('username is already taken');
return res.badRequest('username is already taken');
}
await prisma.user.update({
where: { id: target.id },
@ -114,7 +105,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
}
}
if (invalidDomains.length) return res.forbid('invalid domains', { invalidDomains });
if (invalidDomains.length) return res.badRequest('invalid domains', { invalidDomains });
await prisma.user.update({
where: { id: target.id },
@ -150,7 +141,15 @@ async function handler(req: NextApiReq, res: NextApiRes) {
);
return res.json(newUser);
} else {
delete target.password;
return res.json(target);
}
}
export default withZipline(handler);
export default withZipline(handler, {
methods: ['GET', 'DELETE', 'PATCH'],
user: true,
administrator: true,
});

View file

@ -1,4 +1,4 @@
import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
import prisma from 'lib/prisma';
import Logger from 'lib/logger';
import { Zip, ZipPassThrough } from 'fflate';
@ -7,10 +7,7 @@ import { readdir, stat } from 'fs/promises';
import { createReadStream, createWriteStream } from 'fs';
import { tmpdir } from 'os';
async function handler(req: NextApiReq, res: NextApiRes) {
const user = await req.user();
if (!user) return res.forbid('not logged in');
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
if (req.method === 'POST') {
const files = await prisma.image.findMany({
where: {
@ -18,7 +15,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
},
});
if (!files.length) return res.error('no files found');
if (!files.length) return res.notFound('no files found');
const zip = new Zip();
const export_name = `zipline_export_${user.id}_${Date.now()}.zip`;
@ -116,7 +113,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
const export_name = req.query.name as string;
if (export_name) {
const parts = export_name.split('_');
if (Number(parts[2]) !== user.id) return res.forbid('cannot access export');
if (Number(parts[2]) !== user.id) return res.unauthorized('cannot access export owned by another user');
const stream = createReadStream(tmpdir() + `/${export_name}`);
@ -142,4 +139,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
}
}
export default withZipline(handler);
export default withZipline(handler, {
methods: ['GET', 'POST'],
user: true,
});

View file

@ -1,14 +1,11 @@
import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
import prisma from 'lib/prisma';
import { chunk } from 'lib/util';
import Logger from 'lib/logger';
import datasource from 'lib/datasource';
import config from 'lib/config';
async function handler(req: NextApiReq, res: NextApiRes) {
const user = await req.user();
if (!user) return res.forbid('not logged in');
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
if (req.method === 'DELETE') {
if (req.body.all) {
const files = await prisma.image.findMany({
@ -30,7 +27,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
return res.json({ count });
} else {
if (!req.body.id) return res.error('no file id');
if (!req.body.id) return res.badRequest('no file id');
const image = await prisma.image.delete({
where: {
@ -48,7 +45,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
return res.json(image);
}
} else if (req.method === 'PATCH') {
if (!req.body.id) return res.error('no file id');
if (!req.body.id) return res.badRequest('no file id');
let image;
@ -103,4 +100,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
}
}
export default withZipline(handler);
export default withZipline(handler, {
methods: ['GET', 'DELETE', 'PATCH'],
user: true,
});

View file

@ -1,14 +1,11 @@
import prisma from 'lib/prisma';
import { hashPassword } from 'lib/util';
import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
import Logger from 'lib/logger';
import config from 'lib/config';
import { discord_auth, github_auth, google_auth } from 'lib/oauth';
async function handler(req: NextApiReq, res: NextApiRes) {
const user = await req.user();
if (!user) return res.forbid('not logged in');
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
if (user.oauth) {
// this will probably change before the stable release
if (user.oauth.find((o) => o.provider === 'GITHUB')) {
@ -137,9 +134,8 @@ async function handler(req: NextApiReq, res: NextApiRes) {
username: req.body.username,
},
});
if (existing && user.username !== req.body.username) {
return res.forbid('username is already taken');
}
if (existing && user.username !== req.body.username) return res.badRequest('username is already taken');
await prisma.user.update({
where: { id: user.id },
data: { username: req.body.username },
@ -195,7 +191,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
}
}
if (invalidDomains.length) return res.forbid('invalid domains', { invalidDomains });
if (invalidDomains.length) return res.badRequest('invalid domains', { invalidDomains });
await prisma.user.update({
where: { id: user.id },
@ -234,4 +230,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
}
}
export default withZipline(handler);
export default withZipline(handler, {
methods: ['GET', 'PATCH'],
user: true,
});

View file

@ -1,14 +1,11 @@
import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
import prisma from 'lib/prisma';
import config from 'lib/config';
async function handler(req: NextApiReq, res: NextApiRes) {
const user = await req.user();
if (!user) return res.forbid('not logged in');
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
const take = Number(req.query.take ?? 4);
if (take > 50) return res.error("take can't be more than 50");
if (take > 50) return res.badRequest("take can't be more than 50");
let images = await prisma.image.findMany({
take,
@ -39,4 +36,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
return res.json(images);
}
export default withZipline(handler);
export default withZipline(handler, {
methods: ['GET'],
user: true,
});

View file

@ -1,26 +1,24 @@
import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
import prisma from 'lib/prisma';
import { createToken } from 'lib/util';
import Logger from 'lib/logger';
async function handler(req: NextApiReq, res: NextApiRes) {
const user = await req.user();
if (!user) return res.forbid('not logged in');
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
const updated = await prisma.user.update({
where: {
id: user.id,
},
data: {
token: createToken(),
},
});
if (req.method === 'PATCH') {
const updated = await prisma.user.update({
where: {
id: user.id,
},
data: {
token: createToken(),
},
});
Logger.get('user').info(`User ${user.username} (${user.id}) reset their token`);
Logger.get('user').info(`User ${user.username} (${user.id}) reset their token`);
return res.json({ success: updated.token });
}
return res.json({ success: updated.token });
}
export default withZipline(handler);
export default withZipline(handler, {
methods: ['PATCH'],
user: true,
});

View file

@ -1,14 +1,11 @@
import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
import prisma from 'lib/prisma';
import config from 'lib/config';
import Logger from 'lib/logger';
async function handler(req: NextApiReq, res: NextApiRes) {
const user = await req.user();
if (!user) return res.forbid('not logged in');
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
if (req.method === 'DELETE') {
if (!req.body.id) return res.bad('no url id');
if (!req.body.id) return res.badRequest('no url id');
const url = await prisma.url.delete({
where: {
@ -40,4 +37,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
}
}
export default withZipline(handler);
export default withZipline(handler, {
methods: ['GET', 'DELETE'],
user: true,
});

View file

@ -9,29 +9,29 @@ async function handler(req: NextApiReq, res: NextApiRes) {
const invite = await prisma.invite.findUnique({
where: { code },
});
if (!invite) return res.bad('invalid invite code');
if (!invite) return res.badRequest('invalid invite code');
const user = await prisma.user.findFirst({
where: { username },
});
if (user) return res.bad('username already exists');
if (user) return res.badRequest('username already exists');
return res.json({ success: true });
}
const user = await req.user();
if (!user) return res.forbid('not logged in');
if (!user.administrator) return res.forbid("you aren't an administrator");
if (!user) return res.unauthorized('not logged in');
if (!user.administrator) return res.forbidden('not an administrator');
if (req.method === 'DELETE') {
if (req.body.id === user.id) return res.forbid("you can't delete your own account");
if (req.body.id === user.id) return res.badRequest("you can't delete your own account");
const deleteUser = await prisma.user.findFirst({
where: {
id: req.body.id,
},
});
if (!deleteUser) return res.forbid("user doesn't exist");
if (!deleteUser) return res.notFound("user doesn't exist");
if (req.body.delete_images) {
const files = await prisma.image.findMany({
@ -82,4 +82,6 @@ async function handler(req: NextApiReq, res: NextApiRes) {
}
}
export default withZipline(handler);
export default withZipline(handler, {
methods: ['GET', 'POST', 'DELETE'],
});

View file

@ -3,10 +3,7 @@ import config from 'lib/config';
import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
async function handler(req: NextApiReq, res: NextApiRes) {
const user = await req.user();
if (!user) return res.forbid('not logged in');
if (!config.website.show_version) return res.bad('version hidden');
if (!config.website.show_version) return res.forbidden('version hidden');
const pkg = JSON.parse(await readFile('package.json', 'utf8'));
@ -19,4 +16,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
});
}
export default withZipline(handler);
export default withZipline(handler, {
methods: ['GET'],
user: true,
});