Merge branch 'trunk' into feature/oauth-authentik

This commit is contained in:
dicedtomato 2023-05-06 08:05:44 -10:00 committed by GitHub
commit 61b65700a1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 184 additions and 168 deletions

View file

@ -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

View file

@ -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

View file

@ -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 () => {

View file

@ -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({

View file

@ -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) => {

View 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>

View file

@ -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' />

View file

@ -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 () => {

View file

@ -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

View file

@ -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) => {

View file

@ -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({

View file

@ -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();

View file

@ -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;

View file

@ -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'),

View file

@ -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),

View file

@ -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);
}); });
}); });

View file

@ -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:

View file

@ -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,

View file

@ -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: {

View file

@ -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;

View file

@ -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,

View file

@ -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&apos;t have an account? Register Don&apos;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>
</> </>

View file

@ -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,

View file

@ -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({