feat(v3.6.0-rc1): small fixes

This commit is contained in:
diced 2022-10-22 23:42:52 -07:00
parent 642e8796f0
commit a90130e8bf
No known key found for this signature in database
GPG key ID: 370BD1BA142842D1
14 changed files with 73 additions and 84 deletions

View file

@ -1,6 +1,6 @@
{ {
"name": "zipline", "name": "zipline",
"version": "3.5.1", "version": "3.6.0-rc1",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"dev": "npm-run-all build:server dev:run", "dev": "npm-run-all build:server dev:run",
@ -17,7 +17,7 @@
"docker:run": "docker-compose up -d", "docker:run": "docker-compose up -d",
"docker:down": "docker-compose down", "docker:down": "docker-compose down",
"docker:build-dev": "docker-compose --file docker-compose.dev.yml up --build", "docker:build-dev": "docker-compose --file docker-compose.dev.yml up --build",
"scripts:read-config": "node dist/scripts/read-config" "scripts:read-config": "npm-run-all build:server && node dist/scripts/read-config"
}, },
"dependencies": { "dependencies": {
"@dicedtomato/mantine-data-grid": "0.0.23", "@dicedtomato/mantine-data-grid": "0.0.23",
@ -82,4 +82,4 @@
"url": "https://github.com/diced/zipline.git" "url": "https://github.com/diced/zipline.git"
}, },
"packageManager": "yarn@3.2.4" "packageManager": "yarn@3.2.4"
} }

View file

@ -21,6 +21,7 @@ import {
Image, Image,
Tooltip, Tooltip,
Badge, Badge,
Menu,
} from '@mantine/core'; } from '@mantine/core';
import { useClipboard } from '@mantine/hooks'; import { useClipboard } from '@mantine/hooks';
import { useModals } from '@mantine/modals'; import { useModals } from '@mantine/modals';
@ -194,7 +195,7 @@ export default function Layout({ children, props }) {
const openResetToken = () => const openResetToken = () =>
modals.openConfirmModal({ modals.openConfirmModal({
title: 'Reset Token', title: <Title>Reset Token?</Title>,
children: ( children: (
<Text size='sm'> <Text size='sm'>
Once you reset your token, you will have to update any uploaders to use this new token. Once you reset your token, you will have to update any uploaders to use this new token.
@ -227,7 +228,7 @@ export default function Layout({ children, props }) {
const openCopyToken = () => const openCopyToken = () =>
modals.openConfirmModal({ modals.openConfirmModal({
title: 'Copy Token', title: <Title>Copy Token</Title>,
children: ( children: (
<Text size='sm'> <Text size='sm'>
Make sure you don&apos;t share this token with anyone as they will be able to upload files on your Make sure you don&apos;t share this token with anyone as they will be able to upload files on your
@ -362,17 +363,10 @@ export default function Layout({ children, props }) {
<Popover.Dropdown p={4} mr='md' sx={{ minWidth: '200px' }}> <Popover.Dropdown p={4} mr='md' sx={{ minWidth: '200px' }}>
<Stack spacing={2}> <Stack spacing={2}>
<Text <Menu.Label>
sx={{ {user.username}{' '}
color: theme.colorScheme === 'dark' ? theme.colors.dark[2] : theme.colors.gray[6], {user.administrator && user.username !== 'administrator' ? '(Administrator)' : ''}
fontWeight: 500, </Menu.Label>
fontSize: theme.fontSizes.sm,
padding: `${theme.spacing.xs / 2}px ${theme.spacing.sm}px`,
cursor: 'default',
}}
>
{user.username}
</Text>
<MenuItemLink icon={<SettingsIcon />} href='/dashboard/manage'> <MenuItemLink icon={<SettingsIcon />} href='/dashboard/manage'>
Manage Account Manage Account
</MenuItemLink> </MenuItemLink>
@ -398,20 +392,10 @@ export default function Layout({ children, props }) {
<MenuItemLink icon={<LogoutIcon />} href='/auth/logout' color='red'> <MenuItemLink icon={<LogoutIcon />} href='/auth/logout' color='red'>
Logout Logout
</MenuItemLink> </MenuItemLink>
<Divider <Menu.Divider />
variant='solid'
my={theme.spacing.xs / 2}
sx={(theme) => ({
width: '110%',
borderTopColor:
theme.colorScheme === 'dark' ? theme.colors.dark[7] : theme.colors.gray[2],
margin: `${theme.spacing.xs / 2}px -4px`,
})}
/>
{user.oauth ? ( {user.oauth ? (
<> <>
<MenuItem <MenuItem
noClick
icon={ icon={
user.oauthProvider === 'discord' ? ( user.oauthProvider === 'discord' ? (
<DiscordIcon size={18} /> <DiscordIcon size={18} />
@ -424,16 +408,7 @@ export default function Layout({ children, props }) {
<span style={{ textTransform: 'capitalize' }}>{user.oauthProvider}</span> <span style={{ textTransform: 'capitalize' }}>{user.oauthProvider}</span>
</MenuItem> </MenuItem>
<Divider <Menu.Divider />
variant='solid'
my={theme.spacing.xs / 2}
sx={(theme) => ({
width: '110%',
borderTopColor:
theme.colorScheme === 'dark' ? theme.colors.dark[7] : theme.colors.gray[2],
margin: `${theme.spacing.xs / 2}px -4px`,
})}
/>
</> </>
) : null} ) : null}
<MenuItem icon={<PencilIcon />}> <MenuItem icon={<PencilIcon />}>

View file

@ -100,6 +100,11 @@ export default function ZiplineTheming({ Component, pageProps, ...props }) {
overlayColor: theme.colorScheme === 'dark' ? theme.colors.dark[6] : 'white', overlayColor: theme.colorScheme === 'dark' ? theme.colors.dark[6] : 'white',
}, },
}, },
Loader: {
defaultProps: {
variant: 'dots',
},
},
Card: { Card: {
styles: (t) => ({ styles: (t) => ({
root: { root: {

View file

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { Table, Tooltip, Badge, useMantineTheme } from '@mantine/core'; import { Table, Tooltip, Badge, HoverCard, Text, useMantineTheme, Group } from '@mantine/core';
import Type from 'components/Type'; import Type from 'components/Type';
export function FilePreview({ file }: { file: File }) { export function FilePreview({ file }: { file: File }) {
@ -21,10 +21,12 @@ export default function FileDropzone({ file }: { file: File }) {
const theme = useMantineTheme(); const theme = useMantineTheme();
return ( return (
<Tooltip <HoverCard shadow='md'>
position='top' <HoverCard.Target>
label={ <Badge size='lg'>{file.name}</Badge>
<div style={{ display: 'flex', alignItems: 'center' }}> </HoverCard.Target>
<HoverCard.Dropdown>
<Group grow>
<FilePreview file={file} /> <FilePreview file={file} />
<Table sx={{ color: theme.colorScheme === 'dark' ? 'white' : 'white' }} ml='md'> <Table sx={{ color: theme.colorScheme === 'dark' ? 'white' : 'white' }} ml='md'>
@ -43,10 +45,8 @@ export default function FileDropzone({ file }: { file: File }) {
</tr> </tr>
</tbody> </tbody>
</Table> </Table>
</div> </Group>
} </HoverCard.Dropdown>
> </HoverCard>
<Badge size='lg'>{file.name}</Badge>
</Tooltip>
); );
} }

View file

@ -14,7 +14,7 @@ export function GeneratorModal({ opened, onClose, title, onSubmit, ...other }) {
return ( return (
<Modal opened={opened} onClose={onClose} title={<Title order={3}>{title}</Title>} size='lg'> <Modal opened={opened} onClose={onClose} title={<Title order={3}>{title}</Title>} size='lg'>
{other.desc && <Text>{other.desc}</Text>} {other.desc && <Text mb='md'>{other.desc}</Text>}
<form onSubmit={form.onSubmit((values) => onSubmit(values))}> <form onSubmit={form.onSubmit((values) => onSubmit(values))}>
<Select <Select
label='Select file name format' label='Select file name format'

View file

@ -283,21 +283,28 @@ export default function Manage() {
<Link href='https://zipline.diced.tech/docs/guides/variables'>the docs</Link> for variables <Link href='https://zipline.diced.tech/docs/guides/variables'>the docs</Link> for variables
</MutedText> </MutedText>
<form onSubmit={form.onSubmit((v) => onSubmit(v))}> <form onSubmit={form.onSubmit((v) => onSubmit(v))}>
<TextInput id='username' label='Username' {...form.getInputProps('username')} /> <TextInput id='username' label='Username' my='sm' {...form.getInputProps('username')} />
<PasswordInput <PasswordInput
id='password' id='password'
label='Password' label='Password'
description='Leave blank to keep your old password' description='Leave blank to keep your old password'
my='sm'
{...form.getInputProps('password')} {...form.getInputProps('password')}
/> />
<TextInput id='embedTitle' label='Embed Title' {...form.getInputProps('embedTitle')} /> <TextInput id='embedTitle' label='Embed Title' my='sm' {...form.getInputProps('embedTitle')} />
<ColorInput id='embedColor' label='Embed Color' {...form.getInputProps('embedColor')} /> <ColorInput id='embedColor' label='Embed Color' my='sm' {...form.getInputProps('embedColor')} />
<TextInput id='embedSiteName' label='Embed Site Name' {...form.getInputProps('embedSiteName')} /> <TextInput
id='embedSiteName'
label='Embed Site Name'
my='sm'
{...form.getInputProps('embedSiteName')}
/>
<TextInput <TextInput
id='domains' id='domains'
label='Domains' label='Domains'
description='A list of domains separated by commas. These domains will be used to randomly output a domain when uploading. This is optional.' description='A list of domains separated by commas. These domains will be used to randomly output a domain when uploading. This is optional.'
placeholder='https://example.com, https://example2.com' placeholder='https://example.com, https://example2.com'
my='sm'
{...form.getInputProps('domains')} {...form.getInputProps('domains')}
/> />

View file

@ -72,6 +72,7 @@ export default function Upload() {
dropdownPosition='top' dropdownPosition='top'
data={Object.keys(exts).map((x) => ({ value: x, label: exts[x] }))} data={Object.keys(exts).map((x) => ({ value: x, label: exts[x] }))}
icon={<TypeIcon />} icon={<TypeIcon />}
searchable
/> />
<Button <Button
leftIcon={<UploadIcon />} leftIcon={<UploadIcon />}

View file

@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import type { CookieSerializeOptions } from 'cookie'; import type { CookieSerializeOptions } from 'cookie';
import { serialize } from 'cookie'; import { serialize } from 'cookie';
import { sign64, unsign64 } from 'lib/util'; import { sign64, unsign64 } from 'lib/utils/crypto';
import config from 'lib/config'; import config from 'lib/config';
import prisma from 'lib/prisma'; import prisma from 'lib/prisma';
import { User } from '@prisma/client'; import { User } from '@prisma/client';

View file

@ -1,4 +1,4 @@
import { createHmac, randomBytes, timingSafeEqual } from 'crypto'; import { randomBytes } from 'crypto';
import { hash, verify } from 'argon2'; import { hash, verify } from 'argon2';
import { readdir, stat } from 'fs/promises'; import { readdir, stat } from 'fs/promises';
import { join } from 'path'; import { join } from 'path';
@ -25,31 +25,6 @@ export function createToken() {
return randomChars(24) + '.' + Buffer.from(Date.now().toString()).toString('base64url'); return randomChars(24) + '.' + Buffer.from(Date.now().toString()).toString('base64url');
} }
export function sign(value: string, secret: string): string {
const signed = value + ':' + createHmac('sha256', secret).update(value).digest('base64').replace(/=+$/, '');
return signed;
}
export function unsign(value: string, secret: string): string {
const str = value.slice(0, value.lastIndexOf(':'));
const mac = sign(str, secret);
const macBuffer = Buffer.from(mac);
const valBuffer = Buffer.from(value);
return timingSafeEqual(macBuffer, valBuffer) ? str : null;
}
export function sign64(value: string, secret: string): string {
return Buffer.from(sign(value, secret)).toString('base64');
}
export function unsign64(value: string, secret: string): string {
return unsign(Buffer.from(value, 'base64').toString(), secret);
}
export function chunk<T>(arr: T[], size: number): Array<T[]> { export function chunk<T>(arr: T[], size: number): Array<T[]> {
const result = []; const result = [];
const L = arr.length; const L = arr.length;

26
src/lib/utils/crypto.ts Normal file
View file

@ -0,0 +1,26 @@
import { createHmac, timingSafeEqual } from 'crypto';
export function sign(value: string, secret: string): string {
const signed = value + ':' + createHmac('sha256', secret).update(value).digest('base64').replace(/=+$/, '');
return signed;
}
export function unsign(value: string, secret: string): string {
const str = value.slice(0, value.lastIndexOf(':'));
const mac = sign(str, secret);
const macBuffer = Buffer.from(mac);
const valBuffer = Buffer.from(value);
return timingSafeEqual(macBuffer, valBuffer) ? str : null;
}
export function sign64(value: string, secret: string): string {
return Buffer.from(sign(value, secret)).toString('base64');
}
export function unsign64(value: string, secret: string): string {
return unsign(Buffer.from(value, 'base64').toString(), secret);
}

View file

@ -7,12 +7,12 @@ async function handler(req: NextApiReq, res: NextApiRes) {
const user = await req.user(); const user = await req.user();
if (!user) return res.forbid('not logged in'); if (!user) return res.forbid('not logged in');
let amount = typeof req.query.amount === 'string' ? parseInt(req.query.amount) : 2; 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.bad('invalid amount');
// get stats per day // get stats per day
var stats = await prisma.$queryRaw<Stats[]>` let stats: Stats[] = await prisma.$queryRaw`
SELECT * SELECT *
FROM "Stats" as t JOIN FROM "Stats" as t JOIN
(SELECT MAX(t2."created_at") as max_timestamp (SELECT MAX(t2."created_at") as max_timestamp

View file

@ -24,7 +24,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
}, },
}); });
if (!user) return res.forbid('authorization incorect'); if (!user) return res.forbid('authorization incorrect');
if (user.ratelimit) { if (user.ratelimit) {
const remaining = user.ratelimit.getTime() - Date.now(); const remaining = user.ratelimit.getTime() - Date.now();
if (remaining <= 0) { if (remaining <= 0) {

View file

@ -87,7 +87,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
Logger.get('user').info(`Export for ${user.username} (${user.id}) has started`); Logger.get('user').info(`Export for ${user.username} (${user.id}) has started`);
for (let i = 0; i !== files.length; ++i) { for (let i = 0; i !== files.length; ++i) {
const file = files[i]; const file = files[i];
const stream = await datasource.get(file.file); const stream = datasource.get(file.file);
if (stream) { if (stream) {
const def = new ZipPassThrough(file.file); const def = new ZipPassThrough(file.file);
zip.add(def); zip.add(def);

View file

@ -1,3 +1,3 @@
import config from 'lib/config'; import config from '../lib/config';
console.log(JSON.stringify(config, null, 2)); console.log(JSON.stringify(config, null, 2));