Skip to content

Commit

Permalink
feat(v3.4.2): random domain selection #129
Browse files Browse the repository at this point in the history
  • Loading branch information
diced committed Mar 4, 2022
1 parent 99e92e4 commit 083040e
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 15 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "zip3",
"version": "3.4.1",
"version": "3.4.2",
"license": "MIT",
"scripts": {
"dev": "node esbuild.config.js && REACT_EDITOR=code-insiders NODE_ENV=development node dist/server",
Expand Down
2 changes: 2 additions & 0 deletions prisma/migrations/20220304004623_domains/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "domains" TEXT[];
13 changes: 7 additions & 6 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@ generator client {
}

model User {
id Int @id @default(autoincrement())
id Int @id @default(autoincrement())
username String
password String
token String
administrator Boolean @default(false)
systemTheme String @default("system")
administrator Boolean @default(false)
systemTheme String @default("system")
embedTitle String?
embedColor String @default("#2f3136")
embedSiteName String? @default("{image.file} • {user.name}")
ratelimited Boolean @default(false)
embedColor String @default("#2f3136")
embedSiteName String? @default("{image.file} • {user.name}")
ratelimited Boolean @default(false)
domains String[]
images Image[]
urls Url[]
}
Expand Down
60 changes: 55 additions & 5 deletions src/components/pages/Manage.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React from 'react';
import React, { useState } from 'react';

import useFetch from 'hooks/useFetch';
import Link from 'components/Link';
import { useStoreDispatch, useStoreSelector } from 'lib/redux/store';
import { updateUser } from 'lib/redux/reducers/user';
import { useForm } from '@mantine/hooks';
import { Tooltip, TextInput, Button, Text, Title, Group, ColorInput } from '@mantine/core';
import { DownloadIcon } from '@modulz/radix-icons';
import { randomId, useForm } from '@mantine/hooks';
import { Tooltip, TextInput, Button, Text, Title, Group, ColorInput, MultiSelect, Space } from '@mantine/core';
import { DownloadIcon, Cross1Icon } from '@modulz/radix-icons';
import { useNotifications } from '@mantine/notifications';

function VarsTooltip({ children }) {
return (
Expand All @@ -27,6 +28,9 @@ function VarsTooltip({ children }) {
export default function Manage() {
const user = useStoreSelector(state => state.user);
const dispatch = useStoreDispatch();
const notif = useNotifications();

const [domains, setDomains] = useState(user.domains ?? []);

const genShareX = (withEmbed: boolean = false, withZws: boolean = false) => {
const config = {
Expand Down Expand Up @@ -61,6 +65,7 @@ export default function Manage() {
embedTitle: user.embedTitle ?? '',
embedColor: user.embedColor,
embedSiteName: user.embedSiteName ?? '',
domains: user.domains ?? [],
},
});

Expand All @@ -73,19 +78,51 @@ export default function Manage() {

if (cleanUsername === '') return form.setFieldError('username', 'Username can\'t be nothing');

const id = notif.showNotification({
title: 'Updating user...',
message: '',
loading: true,
});

const data = {
username: cleanUsername,
password: cleanPassword === '' ? null : cleanPassword,
embedTitle: cleanEmbedTitle === '' ? null : cleanEmbedTitle,
embedColor: cleanEmbedColor === '' ? null : cleanEmbedColor,
embedSiteName: cleanEmbedSiteName === '' ? null : cleanEmbedSiteName,
domains,
};

const newUser = await useFetch('/api/user', 'PATCH', data);

if (newUser.error) {
if (newUser.invalidDomains) {
notif.updateNotification(id, {
message: <>
<Text mt='xs'>The following domains are invalid:</Text>
{newUser.invalidDomains.map(err => (
<>
<Text color='gray' key={randomId()}>{err.domain}: {err.reason}</Text>
<Space h='md' />
</>
))}
</>,
color: 'red',
icon: <Cross1Icon />,
});
}
notif.updateNotification(id, {
title: 'Couldn\'t save user',
message: newUser.error,
color: 'red',
icon: <Cross1Icon />,
});
} else {
dispatch(updateUser(newUser));
notif.updateNotification(id, {
title: 'Saved User',
message: '',
});
}
};

Expand All @@ -97,10 +134,23 @@ export default function Manage() {
</VarsTooltip>
<form onSubmit={form.onSubmit((v) => onSubmit(v))}>
<TextInput id='username' label='Username' {...form.getInputProps('username')} />
<TextInput id='password' label='Password'type='password' {...form.getInputProps('password')} />
<TextInput id='password' label='Password' type='password' {...form.getInputProps('password')} />
<TextInput id='embedTitle' label='Embed Title' {...form.getInputProps('embedTitle')} />
<ColorInput id='embedColor' label='Embed Color' {...form.getInputProps('embedColor')} />
<TextInput id='embedSiteName' label='Embed Site Name' {...form.getInputProps('embedSiteName')} />
<MultiSelect
id='domains'
label='Domains'
data={domains}
placeholder='Leave blank if you dont want random domain selection.'
creatable
searchable
clearable
getCreateLabel={query => `Add ${query}`}
onCreate={query => setDomains((current) => [...current, query])}
{...form.getInputProps('domains')}
/>

<Group position='right' sx={{ paddingTop: 12 }}>
<Button
type='submit'
Expand Down
8 changes: 6 additions & 2 deletions src/lib/middleware/withZipline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export type NextApiReq = NextApiRequest & {
administrator: boolean;
id: number;
password: string;
domains: string[];
} | null | void>;
getCookie: (name: string) => string | null;
cleanCookie: (name: string) => void;
Expand All @@ -33,7 +34,7 @@ export type NextApiReq = NextApiRequest & {

export type NextApiRes = NextApiResponse & {
error: (message: string) => void;
forbid: (message: string) => void;
forbid: (message: string, extra?: any) => void;
bad: (message: string) => void;
json: (json: any) => void;
ratelimited: () => void;
Expand All @@ -52,11 +53,12 @@ export const withZipline = (handler: (req: NextApiRequest, res: NextApiResponse)
});
};

res.forbid = (message: string) => {
res.forbid = (message: string, extra: any = {}) => {
res.setHeader('Content-Type', 'application/json');
res.status(403);
res.json({
error: '403: ' + message,
...extra,
});
};

Expand Down Expand Up @@ -93,6 +95,7 @@ export const withZipline = (handler: (req: NextApiRequest, res: NextApiResponse)
maxAge: undefined,
}));
};

req.user = async () => {
try {
const userId = req.getCookie('user');
Expand All @@ -111,6 +114,7 @@ export const withZipline = (handler: (req: NextApiRequest, res: NextApiResponse)
systemTheme: true,
token: true,
username: true,
domains: true,
},
});

Expand Down
1 change: 1 addition & 0 deletions src/lib/redux/reducers/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface User {
embedColor: string;
embedSiteName: string;
systemTheme: string;
domains: string[];
}

const initialState: User = null;
Expand Down
7 changes: 6 additions & 1 deletion src/pages/api/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,12 @@ async function handler(req: NextApiReq, res: NextApiRes) {

await datasource.save(image.file, file.buffer);
Logger.get('image').info(`User ${user.username} (${user.id}) uploaded an image ${image.file} (${image.id})`);
files.push(`${zconfig.core.secure ? 'https' : 'http'}://${req.headers.host}${zconfig.uploader.route}/${invis ? invis.invis : image.file}`);
if (user.domains.length) {
const domain = user.domains[Math.floor(Math.random() * user.domains.length)];
files.push(`${domain}${zconfig.uploader.route}/${invis ? invis.invis : image.file}`);
} else {
files.push(`${zconfig.core.secure ? 'https' : 'http'}://${req.headers.host}${zconfig.uploader.route}/${invis ? invis.invis : image.file}`);
}
}

if (user.administrator && zconfig.ratelimit.admin !== 0) {
Expand Down
32 changes: 32 additions & 0 deletions src/pages/api/user/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import prisma from 'lib/prisma';
import { hashPassword } from 'lib/util';
import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
import Logger from 'lib/logger';
import pkg from '../../../../package.json';

async function handler(req: NextApiReq, res: NextApiRes) {
const user = await req.user();
Expand Down Expand Up @@ -51,6 +52,36 @@ async function handler(req: NextApiReq, res: NextApiRes) {
data: { systemTheme: req.body.systemTheme },
});

if (req.body.domains) {
if (!req.body.domains) await prisma.user.update({
where: { id: user.id },
data: { domains: [] },
});

const invalidDomains = [];

for (const domain of req.body.domains) {
try {
const url = new URL(domain);
url.pathname = '/api/version';
const res = await fetch(url.toString());
if (!res.ok) invalidDomains.push({ domain, reason: 'Got a non OK response' });
else {
const body = await res.json();
if (body?.local !== pkg.version) invalidDomains.push({ domain, reason: 'Version mismatch' });
else await prisma.user.update({
where: { id: user.id },
data: { domains: { push: url.origin } },
});
}
} catch (e) {
invalidDomains.push({ domain, reason: e.message });
}
}

if (invalidDomains.length) return res.forbid('Invalid domains', { invalidDomains });
}

const newUser = await prisma.user.findFirst({
where: {
id: Number(user.id),
Expand All @@ -66,6 +97,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
systemTheme: true,
token: true,
username: true,
domains: true,
},
});

Expand Down

0 comments on commit 083040e

Please sign in to comment.