Merge branch 'trunk' into feature/oauth-authentik
This commit is contained in:
commit
61b65700a1
24 changed files with 184 additions and 168 deletions
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
# if using s3/supabase make sure to comment out the other datasources
|
# if using s3/supabase make sure to comment out the other datasources
|
||||||
|
|
||||||
CORE_HTTPS=true
|
CORE_RETURN_HTTPS=true
|
||||||
CORE_SECRET="changethis"
|
CORE_SECRET="changethis"
|
||||||
CORE_HOST=0.0.0.0
|
CORE_HOST=0.0.0.0
|
||||||
CORE_PORT=3000
|
CORE_PORT=3000
|
||||||
|
@ -44,3 +44,5 @@ URLS_LENGTH=6
|
||||||
|
|
||||||
RATELIMIT_USER=5
|
RATELIMIT_USER=5
|
||||||
RATELIMIT_ADMIN=3
|
RATELIMIT_ADMIN=3
|
||||||
|
|
||||||
|
# for more variables checkout the docs
|
|
@ -114,7 +114,7 @@ model OAuth {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
provider OauthProviders
|
provider OauthProviders
|
||||||
user User @relation(fields: [userId], references: [uuid], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [uuid], onDelete: Cascade)
|
||||||
userId String
|
userId String @db.Uuid
|
||||||
username String
|
username String
|
||||||
oauthId String?
|
oauthId String?
|
||||||
token String
|
token String
|
||||||
|
|
|
@ -95,18 +95,12 @@ export default function FileModal({
|
||||||
const handleCopy = () => {
|
const handleCopy = () => {
|
||||||
clipboard.copy(`${window.location.protocol}//${window.location.host}${file.url}`);
|
clipboard.copy(`${window.location.protocol}//${window.location.host}${file.url}`);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
if (!navigator.clipboard)
|
|
||||||
showNotification({
|
showNotification({
|
||||||
title: 'Unable to copy to clipboard',
|
title: 'Copied to clipboard',
|
||||||
message: 'Zipline is unable to copy to clipboard due to security reasons.',
|
message: '',
|
||||||
color: 'red',
|
icon: <IconClipboardCopy size='1rem' />,
|
||||||
});
|
});
|
||||||
else
|
|
||||||
showNotification({
|
|
||||||
title: 'Copied to clipboard',
|
|
||||||
message: '',
|
|
||||||
icon: <IconClipboardCopy size='1rem' />,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFavorite = async () => {
|
const handleFavorite = async () => {
|
||||||
|
|
|
@ -4,10 +4,8 @@ import {
|
||||||
Box,
|
Box,
|
||||||
Burger,
|
Burger,
|
||||||
Button,
|
Button,
|
||||||
Group,
|
|
||||||
Header,
|
Header,
|
||||||
Image,
|
Image,
|
||||||
Input,
|
|
||||||
MediaQuery,
|
MediaQuery,
|
||||||
Menu,
|
Menu,
|
||||||
Navbar,
|
Navbar,
|
||||||
|
@ -222,21 +220,14 @@ export default function Layout({ children, props }) {
|
||||||
labels: { confirm: 'Copy', cancel: 'Cancel' },
|
labels: { confirm: 'Copy', cancel: 'Cancel' },
|
||||||
onConfirm: async () => {
|
onConfirm: async () => {
|
||||||
clipboard.copy(token);
|
clipboard.copy(token);
|
||||||
|
|
||||||
if (!navigator.clipboard)
|
if (!navigator.clipboard)
|
||||||
showNotification({
|
showNotification({
|
||||||
title: 'Unable to copy to clipboard',
|
title: 'Unable to copy token',
|
||||||
message: (
|
message:
|
||||||
<Text size='sm'>
|
"Zipline couldn't copy to your clipboard. Please copy the token manually from the settings page.",
|
||||||
Zipline is unable to copy to clipboard due to security reasons. However, you can still copy
|
|
||||||
the token manually.
|
|
||||||
<br />
|
|
||||||
<Group position='left' spacing='sm'>
|
|
||||||
<Text>Your token is:</Text>
|
|
||||||
<Input size='sm' onFocus={(e) => e.target.select()} type='text' value={token} />
|
|
||||||
</Group>
|
|
||||||
</Text>
|
|
||||||
),
|
|
||||||
color: 'red',
|
color: 'red',
|
||||||
|
icon: <IconClipboardCopy size='1rem' />,
|
||||||
});
|
});
|
||||||
else
|
else
|
||||||
showNotification({
|
showNotification({
|
||||||
|
|
|
@ -106,22 +106,16 @@ export default function Dashboard({ disableMediaPreview, exifEnabled, compress }
|
||||||
|
|
||||||
const copyFile = async (file) => {
|
const copyFile = async (file) => {
|
||||||
clipboard.copy(`${window.location.protocol}//${window.location.host}${file.url}`);
|
clipboard.copy(`${window.location.protocol}//${window.location.host}${file.url}`);
|
||||||
if (!navigator.clipboard)
|
|
||||||
showNotification({
|
showNotification({
|
||||||
title: 'Unable to copy to clipboard',
|
title: 'Copied to clipboard',
|
||||||
message: 'Zipline is unable to copy to clipboard due to security reasons.',
|
message: (
|
||||||
color: 'red',
|
<a
|
||||||
});
|
href={`${window.location.protocol}//${window.location.host}${file.url}`}
|
||||||
else
|
>{`${window.location.protocol}//${window.location.host}${file.url}`}</a>
|
||||||
showNotification({
|
),
|
||||||
title: 'Copied to clipboard',
|
icon: <IconClipboardCopy size='1rem' />,
|
||||||
message: (
|
});
|
||||||
<a
|
|
||||||
href={`${window.location.protocol}//${window.location.host}${file.url}`}
|
|
||||||
>{`${window.location.protocol}//${window.location.host}${file.url}`}</a>
|
|
||||||
),
|
|
||||||
icon: <IconClipboardCopy size='1rem' />,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const viewFile = async (file) => {
|
const viewFile = async (file) => {
|
||||||
|
|
|
@ -40,6 +40,12 @@ export default function FilePagation({ disableMediaPreview, exifEnabled, queryPa
|
||||||
const pages = usePaginatedFiles(page, !checked ? 'media' : null);
|
const pages = usePaginatedFiles(page, !checked ? 'media' : null);
|
||||||
|
|
||||||
if (pages.isSuccess && pages.data.length === 0) {
|
if (pages.isSuccess && pages.data.length === 0) {
|
||||||
|
if (page > 1 && numPages > 0) {
|
||||||
|
setPage(page - 1);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Center sx={{ flexDirection: 'column' }}>
|
<Center sx={{ flexDirection: 'column' }}>
|
||||||
<Group>
|
<Group>
|
||||||
|
|
|
@ -112,7 +112,7 @@ export default function Folders({ disableMediaPreview, exifEnabled, compress })
|
||||||
|
|
||||||
const makePublic = async (folder) => {
|
const makePublic = async (folder) => {
|
||||||
const res = await useFetch(`/api/user/folders/${folder.id}`, 'PATCH', {
|
const res = await useFetch(`/api/user/folders/${folder.id}`, 'PATCH', {
|
||||||
public: folder.public ? false : true,
|
public: !folder.public,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.error) {
|
if (!res.error) {
|
||||||
|
@ -363,25 +363,18 @@ export default function Folders({ disableMediaPreview, exifEnabled, compress })
|
||||||
aria-label='copy link'
|
aria-label='copy link'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
clipboard.copy(`${window.location.origin}/folder/${folder.id}`);
|
clipboard.copy(`${window.location.origin}/folder/${folder.id}`);
|
||||||
if (!navigator.clipboard)
|
|
||||||
showNotification({
|
showNotification({
|
||||||
title: 'Unable to copy to clipboard',
|
title: 'Copied folder link',
|
||||||
message: 'Zipline is unable to copy to clipboard due to security reasons.',
|
message: (
|
||||||
color: 'red',
|
<>
|
||||||
});
|
Copied <AnchorNext href={`/folder/${folder.id}`}>folder link</AnchorNext>{' '}
|
||||||
else
|
to clipboard
|
||||||
showNotification({
|
</>
|
||||||
title: 'Copied folder link',
|
),
|
||||||
message: (
|
color: 'green',
|
||||||
<>
|
icon: <IconClipboardCopy size='1rem' />,
|
||||||
Copied{' '}
|
});
|
||||||
<AnchorNext href={`/folder/${folder.id}`}>folder link</AnchorNext> to
|
|
||||||
clipboard
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
color: 'green',
|
|
||||||
icon: <IconClipboardCopy size='1rem' />,
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconClipboardCopy size='1rem' />
|
<IconClipboardCopy size='1rem' />
|
||||||
|
|
|
@ -184,18 +184,12 @@ export default function Invites() {
|
||||||
|
|
||||||
const handleCopy = async (invite) => {
|
const handleCopy = async (invite) => {
|
||||||
clipboard.copy(`${window.location.protocol}//${window.location.host}/auth/register?code=${invite.code}`);
|
clipboard.copy(`${window.location.protocol}//${window.location.host}/auth/register?code=${invite.code}`);
|
||||||
if (!navigator.clipboard)
|
|
||||||
showNotification({
|
showNotification({
|
||||||
title: 'Unable to copy to clipboard',
|
title: 'Copied to clipboard',
|
||||||
message: 'Zipline is unable to copy to clipboard due to security reasons.',
|
message: '',
|
||||||
color: 'red',
|
icon: <IconClipboardCopy size='1rem' />,
|
||||||
});
|
});
|
||||||
else
|
|
||||||
showNotification({
|
|
||||||
title: 'Copied to clipboard',
|
|
||||||
message: '',
|
|
||||||
icon: <IconClipboardCopy size='1rem' />,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateInvites = async () => {
|
const updateInvites = async () => {
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import {
|
import {
|
||||||
|
ActionIcon,
|
||||||
Anchor,
|
Anchor,
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
ColorInput,
|
ColorInput,
|
||||||
|
CopyButton,
|
||||||
FileInput,
|
FileInput,
|
||||||
Group,
|
Group,
|
||||||
Image,
|
Image,
|
||||||
|
@ -23,6 +25,8 @@ import {
|
||||||
IconBrandDiscordFilled,
|
IconBrandDiscordFilled,
|
||||||
IconBrandGithubFilled,
|
IconBrandGithubFilled,
|
||||||
IconBrandGoogle,
|
IconBrandGoogle,
|
||||||
|
IconCheck,
|
||||||
|
IconClipboardCopy,
|
||||||
IconFileExport,
|
IconFileExport,
|
||||||
IconFiles,
|
IconFiles,
|
||||||
IconFilesOff,
|
IconFilesOff,
|
||||||
|
@ -91,6 +95,7 @@ export default function Manage({ oauth_registration, oauth_providers: raw_oauth_
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
const [fileDataURL, setFileDataURL] = useState(user.avatar ?? null);
|
const [fileDataURL, setFileDataURL] = useState(user.avatar ?? null);
|
||||||
const [totpEnabled, setTotpEnabled] = useState(!!user.totpSecret);
|
const [totpEnabled, setTotpEnabled] = useState(!!user.totpSecret);
|
||||||
|
const [tokenShown, setTokenShown] = useState(false);
|
||||||
|
|
||||||
const getDataURL = (f: File): Promise<string> => {
|
const getDataURL = (f: File): Promise<string> => {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
|
@ -367,6 +372,25 @@ export default function Manage({ oauth_registration, oauth_providers: raw_oauth_
|
||||||
<AnchorNext href='https://zipline.diced.tech/docs/guides/variables'>the docs</AnchorNext> for
|
<AnchorNext href='https://zipline.diced.tech/docs/guides/variables'>the docs</AnchorNext> for
|
||||||
variables
|
variables
|
||||||
</MutedText>
|
</MutedText>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
rightSection={
|
||||||
|
<CopyButton value={user.token} timeout={1000}>
|
||||||
|
{({ copied, copy }) => (
|
||||||
|
<ActionIcon onClick={copy}>
|
||||||
|
{copied ? <IconCheck color='green' size='1rem' /> : <IconClipboardCopy size='1rem' />}
|
||||||
|
</ActionIcon>
|
||||||
|
)}
|
||||||
|
</CopyButton>
|
||||||
|
}
|
||||||
|
// @ts-ignore (this works even though ts doesn't allow for it)
|
||||||
|
component='span'
|
||||||
|
label='Token'
|
||||||
|
onClick={() => setTokenShown(true)}
|
||||||
|
>
|
||||||
|
{tokenShown ? user.token : '[click to reveal]'}
|
||||||
|
</TextInput>
|
||||||
|
|
||||||
<form onSubmit={form.onSubmit((v) => onSubmit(v))}>
|
<form onSubmit={form.onSubmit((v) => onSubmit(v))}>
|
||||||
<TextInput id='username' label='Username' my='sm' {...form.getInputProps('username')} />
|
<TextInput id='username' label='Username' my='sm' {...form.getInputProps('username')} />
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
|
|
|
@ -27,18 +27,12 @@ export default function MetadataView({ fileId }) {
|
||||||
|
|
||||||
const copy = (value) => {
|
const copy = (value) => {
|
||||||
clipboard.copy(value);
|
clipboard.copy(value);
|
||||||
if (!navigator.clipboard)
|
|
||||||
showNotification({
|
showNotification({
|
||||||
title: 'Unable to copy to clipboard',
|
title: 'Copied to clipboard',
|
||||||
message: 'Zipline is unable to copy to clipboard due to security reasons.',
|
message: value,
|
||||||
color: 'red',
|
icon: <IconClipboardCopy size='1rem' />,
|
||||||
});
|
});
|
||||||
else
|
|
||||||
showNotification({
|
|
||||||
title: 'Copied to clipboard',
|
|
||||||
message: value,
|
|
||||||
icon: <IconClipboardCopy size='1rem' />,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchValue = (value) => {
|
const searchValue = (value) => {
|
||||||
|
|
|
@ -7,18 +7,12 @@ export default function showFilesModal(clipboard, modals, files: string[]) {
|
||||||
const open = (idx: number) => window.open(files[idx], '_blank');
|
const open = (idx: number) => window.open(files[idx], '_blank');
|
||||||
const copy = (idx: number) => {
|
const copy = (idx: number) => {
|
||||||
clipboard.copy(files[idx]);
|
clipboard.copy(files[idx]);
|
||||||
if (!navigator.clipboard)
|
|
||||||
showNotification({
|
showNotification({
|
||||||
title: 'Unable to copy to clipboard',
|
title: 'Copied to clipboard',
|
||||||
message: 'Zipline is unable to copy to clipboard due to security reasons.',
|
message: <AnchorNext href={files[idx]}>{files[idx]}</AnchorNext>,
|
||||||
color: 'red',
|
icon: <IconClipboardCopy size='1rem' />,
|
||||||
});
|
});
|
||||||
else
|
|
||||||
showNotification({
|
|
||||||
title: 'Copied to clipboard',
|
|
||||||
message: <AnchorNext href={files[idx]}>{files[idx]}</AnchorNext>,
|
|
||||||
icon: <IconClipboardCopy size='1rem' />,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
modals.openModal({
|
modals.openModal({
|
||||||
|
|
|
@ -169,18 +169,12 @@ export default function Urls() {
|
||||||
|
|
||||||
const copyURL = (u) => {
|
const copyURL = (u) => {
|
||||||
clipboard.copy(`${window.location.protocol}//${window.location.host}${u.url}`);
|
clipboard.copy(`${window.location.protocol}//${window.location.host}${u.url}`);
|
||||||
if (!navigator.clipboard)
|
|
||||||
showNotification({
|
showNotification({
|
||||||
title: 'Unable to copy to clipboard',
|
title: 'Copied to clipboard',
|
||||||
message: 'Zipline is unable to copy to clipboard due to security reasons.',
|
message: '',
|
||||||
color: 'red',
|
icon: <IconClipboardCopy size='1rem' />,
|
||||||
});
|
});
|
||||||
else
|
|
||||||
showNotification({
|
|
||||||
title: 'Copied to clipboard',
|
|
||||||
message: '',
|
|
||||||
icon: <IconClipboardCopy size='1rem' />,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const urlDelete = useURLDelete();
|
const urlDelete = useURLDelete();
|
||||||
|
|
|
@ -123,6 +123,8 @@ export interface ConfigFeatures {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConfigOAuth {
|
export interface ConfigOAuth {
|
||||||
|
bypass_local_login: boolean;
|
||||||
|
|
||||||
github_client_id?: string;
|
github_client_id?: string;
|
||||||
github_client_secret?: string;
|
github_client_secret?: string;
|
||||||
|
|
||||||
|
|
|
@ -136,6 +136,8 @@ export default function readConfig() {
|
||||||
map('DISCORD_SHORTEN_EMBED_THUMBNAIL', 'boolean', 'discord.shorten.embed.thumbnail'),
|
map('DISCORD_SHORTEN_EMBED_THUMBNAIL', 'boolean', 'discord.shorten.embed.thumbnail'),
|
||||||
map('DISCORD_SHORTEN_EMBED_TIMESTAMP', 'boolean', 'discord.shorten.embed.timestamp'),
|
map('DISCORD_SHORTEN_EMBED_TIMESTAMP', 'boolean', 'discord.shorten.embed.timestamp'),
|
||||||
|
|
||||||
|
map('OAUTH_BYPASS_LOCAL_LOGIN', 'boolean', 'oauth.bypass_local_login'),
|
||||||
|
|
||||||
map('OAUTH_GITHUB_CLIENT_ID', 'string', 'oauth.github_client_id'),
|
map('OAUTH_GITHUB_CLIENT_ID', 'string', 'oauth.github_client_id'),
|
||||||
map('OAUTH_GITHUB_CLIENT_SECRET', 'string', 'oauth.github_client_secret'),
|
map('OAUTH_GITHUB_CLIENT_SECRET', 'string', 'oauth.github_client_secret'),
|
||||||
|
|
||||||
|
|
|
@ -168,6 +168,8 @@ const validator = s.object({
|
||||||
.nullish.default(null),
|
.nullish.default(null),
|
||||||
oauth: s
|
oauth: s
|
||||||
.object({
|
.object({
|
||||||
|
bypass_local_login: s.boolean.default(false),
|
||||||
|
|
||||||
github_client_id: s.string.nullable.default(null),
|
github_client_id: s.string.nullable.default(null),
|
||||||
github_client_secret: s.string.nullable.default(null),
|
github_client_secret: s.string.nullable.default(null),
|
||||||
|
|
||||||
|
|
|
@ -50,22 +50,22 @@ export class S3 extends Datasource {
|
||||||
}
|
}
|
||||||
|
|
||||||
public size(file: string): Promise<number> {
|
public size(file: string): Promise<number> {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res) => {
|
||||||
this.s3.statObject(this.config.bucket, file, (err, stat) => {
|
this.s3.statObject(this.config.bucket, file, (err, stat) => {
|
||||||
if (err) rej(err);
|
if (err) res(0);
|
||||||
else res(stat.size);
|
else res(stat.size);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async fullSize(): Promise<number> {
|
public async fullSize(): Promise<number> {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res) => {
|
||||||
const objects = this.s3.listObjectsV2(this.config.bucket, '', true);
|
const objects = this.s3.listObjectsV2(this.config.bucket, '', true);
|
||||||
let size = 0;
|
let size = 0;
|
||||||
|
|
||||||
objects.on('data', (item) => (size += item.size));
|
objects.on('data', (item) => (size += item.size));
|
||||||
objects.on('end', (err) => {
|
objects.on('end', (err) => {
|
||||||
if (err) rej(err);
|
if (err) res(0);
|
||||||
else res(size);
|
else res(size);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,6 +2,7 @@ import date from './date';
|
||||||
import gfycat from './gfycat';
|
import gfycat from './gfycat';
|
||||||
import random from './random';
|
import random from './random';
|
||||||
import uuid from './uuid';
|
import uuid from './uuid';
|
||||||
|
import { parse } from 'path';
|
||||||
|
|
||||||
export type NameFormat = 'random' | 'date' | 'uuid' | 'name' | 'gfycat';
|
export type NameFormat = 'random' | 'date' | 'uuid' | 'name' | 'gfycat';
|
||||||
export const NameFormats: NameFormat[] = ['random', 'date', 'uuid', 'name', 'gfycat'];
|
export const NameFormats: NameFormat[] = ['random', 'date', 'uuid', 'name', 'gfycat'];
|
||||||
|
@ -14,7 +15,9 @@ export default async function formatFileName(nameFormat: NameFormat, originalNam
|
||||||
case 'uuid':
|
case 'uuid':
|
||||||
return uuid();
|
return uuid();
|
||||||
case 'name':
|
case 'name':
|
||||||
return originalName.split('.')[0];
|
const { name } = parse(originalName);
|
||||||
|
|
||||||
|
return name;
|
||||||
case 'gfycat':
|
case 'gfycat':
|
||||||
return gfycat();
|
return gfycat();
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -16,6 +16,7 @@ export type ServerSideProps = {
|
||||||
user_registration: boolean;
|
user_registration: boolean;
|
||||||
oauth_registration: boolean;
|
oauth_registration: boolean;
|
||||||
oauth_providers: string;
|
oauth_providers: string;
|
||||||
|
bypass_local_login: boolean;
|
||||||
chunks_size: number;
|
chunks_size: number;
|
||||||
max_size: number;
|
max_size: number;
|
||||||
totp_enabled: boolean;
|
totp_enabled: boolean;
|
||||||
|
@ -74,6 +75,7 @@ export const getServerSideProps: GetServerSideProps<ServerSideProps> = async (ct
|
||||||
user_registration: config.features.user_registration,
|
user_registration: config.features.user_registration,
|
||||||
oauth_registration: config.features.oauth_registration,
|
oauth_registration: config.features.oauth_registration,
|
||||||
oauth_providers: JSON.stringify(oauth_providers),
|
oauth_providers: JSON.stringify(oauth_providers),
|
||||||
|
bypass_local_login: config.oauth.bypass_local_login,
|
||||||
chunks_size: config.chunks.chunks_size,
|
chunks_size: config.chunks.chunks_size,
|
||||||
max_size: config.chunks.max_size,
|
max_size: config.chunks.max_size,
|
||||||
totp_enabled: config.mfa.totp_enabled,
|
totp_enabled: config.mfa.totp_enabled,
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { extname } from 'path';
|
||||||
|
|
||||||
async function handler(req: NextApiReq, res: NextApiRes) {
|
async function handler(req: NextApiReq, res: NextApiRes) {
|
||||||
const { id, password } = req.query;
|
const { id, password } = req.query;
|
||||||
|
if (isNaN(Number(id))) return res.badRequest('invalid id');
|
||||||
|
|
||||||
const file = await prisma.file.findFirst({
|
const file = await prisma.file.findFirst({
|
||||||
where: {
|
where: {
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { createInvisImage, hashPassword } from 'lib/util';
|
||||||
import { parseExpiry } from 'lib/utils/client';
|
import { parseExpiry } from 'lib/utils/client';
|
||||||
import { removeGPSData } from 'lib/utils/exif';
|
import { removeGPSData } from 'lib/utils/exif';
|
||||||
import multer from 'multer';
|
import multer from 'multer';
|
||||||
import { join } from 'path';
|
import { join, parse } from 'path';
|
||||||
import sharp from 'sharp';
|
import sharp from 'sharp';
|
||||||
import { Worker } from 'worker_threads';
|
import { Worker } from 'worker_threads';
|
||||||
|
|
||||||
|
@ -200,7 +200,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
||||||
let mimetype = file.mimetype;
|
let mimetype = file.mimetype;
|
||||||
|
|
||||||
if (file.mimetype === 'application/octet-stream' && zconfig.uploader.assume_mimetypes) {
|
if (file.mimetype === 'application/octet-stream' && zconfig.uploader.assume_mimetypes) {
|
||||||
const ext = file.originalname.split('.').pop();
|
const ext = parse(file.originalname).ext.replace('.', '');
|
||||||
const mime = await guess(ext);
|
const mime = await guess(ext);
|
||||||
|
|
||||||
if (!mime) response.assumed_mimetype = false;
|
if (!mime) response.assumed_mimetype = false;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import config from 'lib/config';
|
import zconfig from 'lib/config';
|
||||||
import Logger from 'lib/logger';
|
import Logger from 'lib/logger';
|
||||||
import { authentik_auth, discord_auth, github_auth, google_auth } from 'lib/oauth';
|
import { authentik_auth, discord_auth, github_auth, google_auth } from 'lib/oauth';
|
||||||
import prisma from 'lib/prisma';
|
import prisma from 'lib/prisma';
|
||||||
|
@ -18,7 +18,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
error: 'oauth token expired',
|
error: 'oauth token expired',
|
||||||
redirect_uri: github_auth.oauth_url(config.oauth.github_client_id),
|
redirect_uri: github_auth.oauth_url(zconfig.oauth.github_client_id),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (user.oauth.find((o) => o.provider === 'DISCORD')) {
|
} else if (user.oauth.find((o) => o.provider === 'DISCORD')) {
|
||||||
|
@ -35,8 +35,8 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||||
return res.json({
|
return res.json({
|
||||||
error: 'oauth token expired',
|
error: 'oauth token expired',
|
||||||
redirect_uri: discord_auth.oauth_url(
|
redirect_uri: discord_auth.oauth_url(
|
||||||
config.oauth.discord_client_id,
|
zconfig.oauth.discord_client_id,
|
||||||
`${config.core.return_https ? 'https' : 'http'}://${req.headers.host}`
|
`${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}`
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -47,8 +47,8 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
},
|
},
|
||||||
body: new URLSearchParams({
|
body: new URLSearchParams({
|
||||||
client_id: config.oauth.discord_client_id,
|
client_id: zconfig.oauth.discord_client_id,
|
||||||
client_secret: config.oauth.discord_client_secret,
|
client_secret: zconfig.oauth.discord_client_secret,
|
||||||
grant_type: 'refresh_token',
|
grant_type: 'refresh_token',
|
||||||
refresh_token: provider.refresh,
|
refresh_token: provider.refresh,
|
||||||
}),
|
}),
|
||||||
|
@ -59,8 +59,8 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||||
return res.json({
|
return res.json({
|
||||||
error: 'oauth token expired',
|
error: 'oauth token expired',
|
||||||
redirect_uri: discord_auth.oauth_url(
|
redirect_uri: discord_auth.oauth_url(
|
||||||
config.oauth.discord_client_id,
|
zconfig.oauth.discord_client_id,
|
||||||
`${config.core.return_https ? 'https' : 'http'}://${req.headers.host}`
|
`${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}`
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -90,8 +90,8 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||||
return res.json({
|
return res.json({
|
||||||
error: 'oauth token expired',
|
error: 'oauth token expired',
|
||||||
redirect_uri: google_auth.oauth_url(
|
redirect_uri: google_auth.oauth_url(
|
||||||
config.oauth.google_client_id,
|
zconfig.oauth.google_client_id,
|
||||||
`${config.core.return_https ? 'https' : 'http'}://${req.headers.host}`
|
`${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}`
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -101,8 +101,8 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
},
|
},
|
||||||
body: new URLSearchParams({
|
body: new URLSearchParams({
|
||||||
client_id: config.oauth.google_client_id,
|
client_id: zconfig.oauth.google_client_id,
|
||||||
client_secret: config.oauth.google_client_secret,
|
client_secret: zconfig.oauth.google_client_secret,
|
||||||
grant_type: 'refresh_token',
|
grant_type: 'refresh_token',
|
||||||
refresh_token: provider.refresh,
|
refresh_token: provider.refresh,
|
||||||
}),
|
}),
|
||||||
|
@ -113,8 +113,8 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||||
return res.json({
|
return res.json({
|
||||||
error: 'oauth token expired',
|
error: 'oauth token expired',
|
||||||
redirect_uri: google_auth.oauth_url(
|
redirect_uri: google_auth.oauth_url(
|
||||||
config.oauth.google_client_id,
|
zconfig.oauth.google_client_id,
|
||||||
`${config.core.return_https ? 'https' : 'http'}://${req.headers.host}`
|
`${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}`
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -258,6 +258,14 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
api: {
|
||||||
|
bodyParser: {
|
||||||
|
sizeLimit: '50mb',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default withZipline(handler, {
|
export default withZipline(handler, {
|
||||||
methods: ['GET', 'PATCH'],
|
methods: ['GET', 'PATCH'],
|
||||||
user: true,
|
user: true,
|
||||||
|
|
|
@ -22,7 +22,13 @@ import { useRouter } from 'next/router';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
export { getServerSideProps } from 'middleware/getServerSideProps';
|
export { getServerSideProps } from 'middleware/getServerSideProps';
|
||||||
|
|
||||||
export default function Login({ title, user_registration, oauth_registration, oauth_providers: unparsed }) {
|
export default function Login({
|
||||||
|
title,
|
||||||
|
user_registration,
|
||||||
|
oauth_registration,
|
||||||
|
bypass_local_login,
|
||||||
|
oauth_providers: unparsed,
|
||||||
|
}) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// totp modal
|
// totp modal
|
||||||
|
@ -34,6 +40,9 @@ export default function Login({ title, user_registration, oauth_registration, oa
|
||||||
|
|
||||||
const oauth_providers = JSON.parse(unparsed);
|
const oauth_providers = JSON.parse(unparsed);
|
||||||
|
|
||||||
|
const show_local_login =
|
||||||
|
router.query.local === 'true' || !(bypass_local_login && oauth_providers?.length > 0);
|
||||||
|
|
||||||
const icons = {
|
const icons = {
|
||||||
GitHub: IconBrandGithub,
|
GitHub: IconBrandGithub,
|
||||||
Discord: IconBrandDiscordFilled,
|
Discord: IconBrandDiscordFilled,
|
||||||
|
@ -100,6 +109,12 @@ export default function Login({ title, user_registration, oauth_registration, oa
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
|
// if the user includes `local=true` as a query param, show the login form
|
||||||
|
// otherwise, redirect to the oauth login if there is only one registered provider
|
||||||
|
if (bypass_local_login && oauth_providers?.length === 1 && router.query.local !== 'true') {
|
||||||
|
await router.push(oauth_providers[0].url);
|
||||||
|
}
|
||||||
|
|
||||||
const a = await fetch('/api/user');
|
const a = await fetch('/api/user');
|
||||||
if (a.ok) await router.push('/dashboard');
|
if (a.ok) await router.push('/dashboard');
|
||||||
})();
|
})();
|
||||||
|
@ -153,7 +168,7 @@ export default function Login({ title, user_registration, oauth_registration, oa
|
||||||
<Center sx={{ height: '100vh' }}>
|
<Center sx={{ height: '100vh' }}>
|
||||||
<Card radius='md'>
|
<Card radius='md'>
|
||||||
<Title size={30} align='left'>
|
<Title size={30} align='left'>
|
||||||
{title}
|
{bypass_local_login ? ' Login to Zipline with' : 'Zipline'}
|
||||||
</Title>
|
</Title>
|
||||||
|
|
||||||
{oauth_registration && (
|
{oauth_registration && (
|
||||||
|
@ -166,7 +181,7 @@ export default function Login({ title, user_registration, oauth_registration, oa
|
||||||
variant='outline'
|
variant='outline'
|
||||||
radius='md'
|
radius='md'
|
||||||
fullWidth
|
fullWidth
|
||||||
leftIcon={<Icon height={'15'} width={'15'} />}
|
leftIcon={<Icon size='1rem' />}
|
||||||
my='xs'
|
my='xs'
|
||||||
component={Link}
|
component={Link}
|
||||||
href={url}
|
href={url}
|
||||||
|
@ -175,41 +190,42 @@ export default function Login({ title, user_registration, oauth_registration, oa
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</Group>
|
</Group>
|
||||||
|
{show_local_login && <Divider my='xs' label='or' labelPosition='center' />}
|
||||||
<Divider my='xs' label='or' labelPosition='center' />
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<form onSubmit={form.onSubmit((v) => onSubmit(v))}>
|
{show_local_login && (
|
||||||
<TextInput
|
<form onSubmit={form.onSubmit((v) => onSubmit(v))}>
|
||||||
my='xs'
|
<TextInput
|
||||||
radius='md'
|
my='xs'
|
||||||
size='md'
|
radius='md'
|
||||||
id='username'
|
size='md'
|
||||||
label='Username'
|
id='username'
|
||||||
{...form.getInputProps('username')}
|
label='Username'
|
||||||
/>
|
{...form.getInputProps('username')}
|
||||||
<PasswordInput
|
/>
|
||||||
my='xs'
|
<PasswordInput
|
||||||
radius='md'
|
my='xs'
|
||||||
size='md'
|
radius='md'
|
||||||
id='password'
|
size='md'
|
||||||
label='Password'
|
id='password'
|
||||||
{...form.getInputProps('password')}
|
label='Password'
|
||||||
/>
|
{...form.getInputProps('password')}
|
||||||
|
/>
|
||||||
|
|
||||||
<Group position='apart'>
|
<Group position='apart'>
|
||||||
{user_registration && (
|
{user_registration && (
|
||||||
<Anchor size='xs' href='/auth/register' component={Link}>
|
<Anchor size='xs' href='/auth/register' component={Link}>
|
||||||
Don't have an account? Register
|
Don't have an account? Register
|
||||||
</Anchor>
|
</Anchor>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button size='sm' p='xs' radius='md' my='xs' type='submit' loading={loading}>
|
<Button size='sm' p='xs' radius='md' my='xs' type='submit' loading={loading}>
|
||||||
Login
|
Login
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</form>
|
</form>
|
||||||
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
</Center>
|
</Center>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -231,7 +231,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
|
||||||
if (file.password) file.password = true;
|
if (file.password) file.password = true;
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
image: file,
|
file,
|
||||||
user,
|
user,
|
||||||
pass,
|
pass,
|
||||||
prismRender: true,
|
prismRender: true,
|
||||||
|
|
|
@ -29,7 +29,7 @@ async function main() {
|
||||||
|
|
||||||
for (let i = 0; i !== files.length; ++i) {
|
for (let i = 0; i !== files.length; ++i) {
|
||||||
const file = files[i];
|
const file = files[i];
|
||||||
if (!datasource.get(file.name)) {
|
if (!(await datasource.get(file.name))) {
|
||||||
if (process.argv.includes('--force-delete')) {
|
if (process.argv.includes('--force-delete')) {
|
||||||
console.log(`File ${file.name} does not exist. Deleting...`);
|
console.log(`File ${file.name} does not exist. Deleting...`);
|
||||||
await prisma.file.delete({
|
await prisma.file.delete({
|
||||||
|
|
Loading…
Reference in a new issue