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
|
||||
|
||||
CORE_HTTPS=true
|
||||
CORE_RETURN_HTTPS=true
|
||||
CORE_SECRET="changethis"
|
||||
CORE_HOST=0.0.0.0
|
||||
CORE_PORT=3000
|
||||
|
@ -44,3 +44,5 @@ URLS_LENGTH=6
|
|||
|
||||
RATELIMIT_USER=5
|
||||
RATELIMIT_ADMIN=3
|
||||
|
||||
# for more variables checkout the docs
|
|
@ -114,7 +114,7 @@ model OAuth {
|
|||
id Int @id @default(autoincrement())
|
||||
provider OauthProviders
|
||||
user User @relation(fields: [userId], references: [uuid], onDelete: Cascade)
|
||||
userId String
|
||||
userId String @db.Uuid
|
||||
username String
|
||||
oauthId String?
|
||||
token String
|
||||
|
|
|
@ -95,18 +95,12 @@ export default function FileModal({
|
|||
const handleCopy = () => {
|
||||
clipboard.copy(`${window.location.protocol}//${window.location.host}${file.url}`);
|
||||
setOpen(false);
|
||||
if (!navigator.clipboard)
|
||||
showNotification({
|
||||
title: 'Unable to copy to clipboard',
|
||||
message: 'Zipline is unable to copy to clipboard due to security reasons.',
|
||||
color: 'red',
|
||||
});
|
||||
else
|
||||
showNotification({
|
||||
title: 'Copied to clipboard',
|
||||
message: '',
|
||||
icon: <IconClipboardCopy size='1rem' />,
|
||||
});
|
||||
|
||||
showNotification({
|
||||
title: 'Copied to clipboard',
|
||||
message: '',
|
||||
icon: <IconClipboardCopy size='1rem' />,
|
||||
});
|
||||
};
|
||||
|
||||
const handleFavorite = async () => {
|
||||
|
|
|
@ -4,10 +4,8 @@ import {
|
|||
Box,
|
||||
Burger,
|
||||
Button,
|
||||
Group,
|
||||
Header,
|
||||
Image,
|
||||
Input,
|
||||
MediaQuery,
|
||||
Menu,
|
||||
Navbar,
|
||||
|
@ -222,21 +220,14 @@ export default function Layout({ children, props }) {
|
|||
labels: { confirm: 'Copy', cancel: 'Cancel' },
|
||||
onConfirm: async () => {
|
||||
clipboard.copy(token);
|
||||
|
||||
if (!navigator.clipboard)
|
||||
showNotification({
|
||||
title: 'Unable to copy to clipboard',
|
||||
message: (
|
||||
<Text size='sm'>
|
||||
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>
|
||||
),
|
||||
title: 'Unable to copy token',
|
||||
message:
|
||||
"Zipline couldn't copy to your clipboard. Please copy the token manually from the settings page.",
|
||||
color: 'red',
|
||||
icon: <IconClipboardCopy size='1rem' />,
|
||||
});
|
||||
else
|
||||
showNotification({
|
||||
|
|
|
@ -106,22 +106,16 @@ export default function Dashboard({ disableMediaPreview, exifEnabled, compress }
|
|||
|
||||
const copyFile = async (file) => {
|
||||
clipboard.copy(`${window.location.protocol}//${window.location.host}${file.url}`);
|
||||
if (!navigator.clipboard)
|
||||
showNotification({
|
||||
title: 'Unable to copy to clipboard',
|
||||
message: 'Zipline is unable to copy to clipboard due to security reasons.',
|
||||
color: 'red',
|
||||
});
|
||||
else
|
||||
showNotification({
|
||||
title: 'Copied to clipboard',
|
||||
message: (
|
||||
<a
|
||||
href={`${window.location.protocol}//${window.location.host}${file.url}`}
|
||||
>{`${window.location.protocol}//${window.location.host}${file.url}`}</a>
|
||||
),
|
||||
icon: <IconClipboardCopy size='1rem' />,
|
||||
});
|
||||
|
||||
showNotification({
|
||||
title: 'Copied to clipboard',
|
||||
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) => {
|
||||
|
|
|
@ -40,6 +40,12 @@ export default function FilePagation({ disableMediaPreview, exifEnabled, queryPa
|
|||
const pages = usePaginatedFiles(page, !checked ? 'media' : null);
|
||||
|
||||
if (pages.isSuccess && pages.data.length === 0) {
|
||||
if (page > 1 && numPages > 0) {
|
||||
setPage(page - 1);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Center sx={{ flexDirection: 'column' }}>
|
||||
<Group>
|
||||
|
|
|
@ -112,7 +112,7 @@ export default function Folders({ disableMediaPreview, exifEnabled, compress })
|
|||
|
||||
const makePublic = async (folder) => {
|
||||
const res = await useFetch(`/api/user/folders/${folder.id}`, 'PATCH', {
|
||||
public: folder.public ? false : true,
|
||||
public: !folder.public,
|
||||
});
|
||||
|
||||
if (!res.error) {
|
||||
|
@ -363,25 +363,18 @@ export default function Folders({ disableMediaPreview, exifEnabled, compress })
|
|||
aria-label='copy link'
|
||||
onClick={() => {
|
||||
clipboard.copy(`${window.location.origin}/folder/${folder.id}`);
|
||||
if (!navigator.clipboard)
|
||||
showNotification({
|
||||
title: 'Unable to copy to clipboard',
|
||||
message: 'Zipline is unable to copy to clipboard due to security reasons.',
|
||||
color: 'red',
|
||||
});
|
||||
else
|
||||
showNotification({
|
||||
title: 'Copied folder link',
|
||||
message: (
|
||||
<>
|
||||
Copied{' '}
|
||||
<AnchorNext href={`/folder/${folder.id}`}>folder link</AnchorNext> to
|
||||
clipboard
|
||||
</>
|
||||
),
|
||||
color: 'green',
|
||||
icon: <IconClipboardCopy size='1rem' />,
|
||||
});
|
||||
|
||||
showNotification({
|
||||
title: 'Copied folder link',
|
||||
message: (
|
||||
<>
|
||||
Copied <AnchorNext href={`/folder/${folder.id}`}>folder link</AnchorNext>{' '}
|
||||
to clipboard
|
||||
</>
|
||||
),
|
||||
color: 'green',
|
||||
icon: <IconClipboardCopy size='1rem' />,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<IconClipboardCopy size='1rem' />
|
||||
|
|
|
@ -184,18 +184,12 @@ export default function Invites() {
|
|||
|
||||
const handleCopy = async (invite) => {
|
||||
clipboard.copy(`${window.location.protocol}//${window.location.host}/auth/register?code=${invite.code}`);
|
||||
if (!navigator.clipboard)
|
||||
showNotification({
|
||||
title: 'Unable to copy to clipboard',
|
||||
message: 'Zipline is unable to copy to clipboard due to security reasons.',
|
||||
color: 'red',
|
||||
});
|
||||
else
|
||||
showNotification({
|
||||
title: 'Copied to clipboard',
|
||||
message: '',
|
||||
icon: <IconClipboardCopy size='1rem' />,
|
||||
});
|
||||
|
||||
showNotification({
|
||||
title: 'Copied to clipboard',
|
||||
message: '',
|
||||
icon: <IconClipboardCopy size='1rem' />,
|
||||
});
|
||||
};
|
||||
|
||||
const updateInvites = async () => {
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import {
|
||||
ActionIcon,
|
||||
Anchor,
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
ColorInput,
|
||||
CopyButton,
|
||||
FileInput,
|
||||
Group,
|
||||
Image,
|
||||
|
@ -23,6 +25,8 @@ import {
|
|||
IconBrandDiscordFilled,
|
||||
IconBrandGithubFilled,
|
||||
IconBrandGoogle,
|
||||
IconCheck,
|
||||
IconClipboardCopy,
|
||||
IconFileExport,
|
||||
IconFiles,
|
||||
IconFilesOff,
|
||||
|
@ -91,6 +95,7 @@ export default function Manage({ oauth_registration, oauth_providers: raw_oauth_
|
|||
const [file, setFile] = useState<File | null>(null);
|
||||
const [fileDataURL, setFileDataURL] = useState(user.avatar ?? null);
|
||||
const [totpEnabled, setTotpEnabled] = useState(!!user.totpSecret);
|
||||
const [tokenShown, setTokenShown] = useState(false);
|
||||
|
||||
const getDataURL = (f: File): Promise<string> => {
|
||||
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
|
||||
variables
|
||||
</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))}>
|
||||
<TextInput id='username' label='Username' my='sm' {...form.getInputProps('username')} />
|
||||
<PasswordInput
|
||||
|
|
|
@ -27,18 +27,12 @@ export default function MetadataView({ fileId }) {
|
|||
|
||||
const copy = (value) => {
|
||||
clipboard.copy(value);
|
||||
if (!navigator.clipboard)
|
||||
showNotification({
|
||||
title: 'Unable to copy to clipboard',
|
||||
message: 'Zipline is unable to copy to clipboard due to security reasons.',
|
||||
color: 'red',
|
||||
});
|
||||
else
|
||||
showNotification({
|
||||
title: 'Copied to clipboard',
|
||||
message: value,
|
||||
icon: <IconClipboardCopy size='1rem' />,
|
||||
});
|
||||
|
||||
showNotification({
|
||||
title: 'Copied to clipboard',
|
||||
message: value,
|
||||
icon: <IconClipboardCopy size='1rem' />,
|
||||
});
|
||||
};
|
||||
|
||||
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 copy = (idx: number) => {
|
||||
clipboard.copy(files[idx]);
|
||||
if (!navigator.clipboard)
|
||||
showNotification({
|
||||
title: 'Unable to copy to clipboard',
|
||||
message: 'Zipline is unable to copy to clipboard due to security reasons.',
|
||||
color: 'red',
|
||||
});
|
||||
else
|
||||
showNotification({
|
||||
title: 'Copied to clipboard',
|
||||
message: <AnchorNext href={files[idx]}>{files[idx]}</AnchorNext>,
|
||||
icon: <IconClipboardCopy size='1rem' />,
|
||||
});
|
||||
|
||||
showNotification({
|
||||
title: 'Copied to clipboard',
|
||||
message: <AnchorNext href={files[idx]}>{files[idx]}</AnchorNext>,
|
||||
icon: <IconClipboardCopy size='1rem' />,
|
||||
});
|
||||
};
|
||||
|
||||
modals.openModal({
|
||||
|
|
|
@ -169,18 +169,12 @@ export default function Urls() {
|
|||
|
||||
const copyURL = (u) => {
|
||||
clipboard.copy(`${window.location.protocol}//${window.location.host}${u.url}`);
|
||||
if (!navigator.clipboard)
|
||||
showNotification({
|
||||
title: 'Unable to copy to clipboard',
|
||||
message: 'Zipline is unable to copy to clipboard due to security reasons.',
|
||||
color: 'red',
|
||||
});
|
||||
else
|
||||
showNotification({
|
||||
title: 'Copied to clipboard',
|
||||
message: '',
|
||||
icon: <IconClipboardCopy size='1rem' />,
|
||||
});
|
||||
|
||||
showNotification({
|
||||
title: 'Copied to clipboard',
|
||||
message: '',
|
||||
icon: <IconClipboardCopy size='1rem' />,
|
||||
});
|
||||
};
|
||||
|
||||
const urlDelete = useURLDelete();
|
||||
|
|
|
@ -123,6 +123,8 @@ export interface ConfigFeatures {
|
|||
}
|
||||
|
||||
export interface ConfigOAuth {
|
||||
bypass_local_login: boolean;
|
||||
|
||||
github_client_id?: 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_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_SECRET', 'string', 'oauth.github_client_secret'),
|
||||
|
||||
|
|
|
@ -168,6 +168,8 @@ const validator = s.object({
|
|||
.nullish.default(null),
|
||||
oauth: s
|
||||
.object({
|
||||
bypass_local_login: s.boolean.default(false),
|
||||
|
||||
github_client_id: 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> {
|
||||
return new Promise((res, rej) => {
|
||||
return new Promise((res) => {
|
||||
this.s3.statObject(this.config.bucket, file, (err, stat) => {
|
||||
if (err) rej(err);
|
||||
if (err) res(0);
|
||||
else res(stat.size);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public async fullSize(): Promise<number> {
|
||||
return new Promise((res, rej) => {
|
||||
return new Promise((res) => {
|
||||
const objects = this.s3.listObjectsV2(this.config.bucket, '', true);
|
||||
let size = 0;
|
||||
|
||||
objects.on('data', (item) => (size += item.size));
|
||||
objects.on('end', (err) => {
|
||||
if (err) rej(err);
|
||||
if (err) res(0);
|
||||
else res(size);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,6 +2,7 @@ import date from './date';
|
|||
import gfycat from './gfycat';
|
||||
import random from './random';
|
||||
import uuid from './uuid';
|
||||
import { parse } from 'path';
|
||||
|
||||
export type 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':
|
||||
return uuid();
|
||||
case 'name':
|
||||
return originalName.split('.')[0];
|
||||
const { name } = parse(originalName);
|
||||
|
||||
return name;
|
||||
case 'gfycat':
|
||||
return gfycat();
|
||||
default:
|
||||
|
|
|
@ -16,6 +16,7 @@ export type ServerSideProps = {
|
|||
user_registration: boolean;
|
||||
oauth_registration: boolean;
|
||||
oauth_providers: string;
|
||||
bypass_local_login: boolean;
|
||||
chunks_size: number;
|
||||
max_size: number;
|
||||
totp_enabled: boolean;
|
||||
|
@ -74,6 +75,7 @@ export const getServerSideProps: GetServerSideProps<ServerSideProps> = async (ct
|
|||
user_registration: config.features.user_registration,
|
||||
oauth_registration: config.features.oauth_registration,
|
||||
oauth_providers: JSON.stringify(oauth_providers),
|
||||
bypass_local_login: config.oauth.bypass_local_login,
|
||||
chunks_size: config.chunks.chunks_size,
|
||||
max_size: config.chunks.max_size,
|
||||
totp_enabled: config.mfa.totp_enabled,
|
||||
|
|
|
@ -7,6 +7,7 @@ import { extname } from 'path';
|
|||
|
||||
async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
const { id, password } = req.query;
|
||||
if (isNaN(Number(id))) return res.badRequest('invalid id');
|
||||
|
||||
const file = await prisma.file.findFirst({
|
||||
where: {
|
||||
|
|
|
@ -12,7 +12,7 @@ import { createInvisImage, hashPassword } from 'lib/util';
|
|||
import { parseExpiry } from 'lib/utils/client';
|
||||
import { removeGPSData } from 'lib/utils/exif';
|
||||
import multer from 'multer';
|
||||
import { join } from 'path';
|
||||
import { join, parse } from 'path';
|
||||
import sharp from 'sharp';
|
||||
import { Worker } from 'worker_threads';
|
||||
|
||||
|
@ -200,7 +200,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
let mimetype = file.mimetype;
|
||||
|
||||
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);
|
||||
|
||||
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 { authentik_auth, discord_auth, github_auth, google_auth } from 'lib/oauth';
|
||||
import prisma from 'lib/prisma';
|
||||
|
@ -18,7 +18,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
|||
|
||||
return res.json({
|
||||
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')) {
|
||||
|
@ -35,8 +35,8 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
|||
return res.json({
|
||||
error: 'oauth token expired',
|
||||
redirect_uri: discord_auth.oauth_url(
|
||||
config.oauth.discord_client_id,
|
||||
`${config.core.return_https ? 'https' : 'http'}://${req.headers.host}`
|
||||
zconfig.oauth.discord_client_id,
|
||||
`${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',
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
client_id: config.oauth.discord_client_id,
|
||||
client_secret: config.oauth.discord_client_secret,
|
||||
client_id: zconfig.oauth.discord_client_id,
|
||||
client_secret: zconfig.oauth.discord_client_secret,
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token: provider.refresh,
|
||||
}),
|
||||
|
@ -59,8 +59,8 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
|||
return res.json({
|
||||
error: 'oauth token expired',
|
||||
redirect_uri: discord_auth.oauth_url(
|
||||
config.oauth.discord_client_id,
|
||||
`${config.core.return_https ? 'https' : 'http'}://${req.headers.host}`
|
||||
zconfig.oauth.discord_client_id,
|
||||
`${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({
|
||||
error: 'oauth token expired',
|
||||
redirect_uri: google_auth.oauth_url(
|
||||
config.oauth.google_client_id,
|
||||
`${config.core.return_https ? 'https' : 'http'}://${req.headers.host}`
|
||||
zconfig.oauth.google_client_id,
|
||||
`${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',
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
client_id: config.oauth.google_client_id,
|
||||
client_secret: config.oauth.google_client_secret,
|
||||
client_id: zconfig.oauth.google_client_id,
|
||||
client_secret: zconfig.oauth.google_client_secret,
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token: provider.refresh,
|
||||
}),
|
||||
|
@ -113,8 +113,8 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
|||
return res.json({
|
||||
error: 'oauth token expired',
|
||||
redirect_uri: google_auth.oauth_url(
|
||||
config.oauth.google_client_id,
|
||||
`${config.core.return_https ? 'https' : 'http'}://${req.headers.host}`
|
||||
zconfig.oauth.google_client_id,
|
||||
`${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, {
|
||||
methods: ['GET', 'PATCH'],
|
||||
user: true,
|
||||
|
|
|
@ -22,7 +22,13 @@ import { useRouter } from 'next/router';
|
|||
import { useEffect, useState } from 'react';
|
||||
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();
|
||||
|
||||
// totp modal
|
||||
|
@ -34,6 +40,9 @@ export default function Login({ title, user_registration, oauth_registration, oa
|
|||
|
||||
const oauth_providers = JSON.parse(unparsed);
|
||||
|
||||
const show_local_login =
|
||||
router.query.local === 'true' || !(bypass_local_login && oauth_providers?.length > 0);
|
||||
|
||||
const icons = {
|
||||
GitHub: IconBrandGithub,
|
||||
Discord: IconBrandDiscordFilled,
|
||||
|
@ -100,6 +109,12 @@ export default function Login({ title, user_registration, oauth_registration, oa
|
|||
|
||||
useEffect(() => {
|
||||
(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');
|
||||
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' }}>
|
||||
<Card radius='md'>
|
||||
<Title size={30} align='left'>
|
||||
{title}
|
||||
{bypass_local_login ? ' Login to Zipline with' : 'Zipline'}
|
||||
</Title>
|
||||
|
||||
{oauth_registration && (
|
||||
|
@ -166,7 +181,7 @@ export default function Login({ title, user_registration, oauth_registration, oa
|
|||
variant='outline'
|
||||
radius='md'
|
||||
fullWidth
|
||||
leftIcon={<Icon height={'15'} width={'15'} />}
|
||||
leftIcon={<Icon size='1rem' />}
|
||||
my='xs'
|
||||
component={Link}
|
||||
href={url}
|
||||
|
@ -175,41 +190,42 @@ export default function Login({ title, user_registration, oauth_registration, oa
|
|||
</Button>
|
||||
))}
|
||||
</Group>
|
||||
|
||||
<Divider my='xs' label='or' labelPosition='center' />
|
||||
{show_local_login && <Divider my='xs' label='or' labelPosition='center' />}
|
||||
</>
|
||||
)}
|
||||
|
||||
<form onSubmit={form.onSubmit((v) => onSubmit(v))}>
|
||||
<TextInput
|
||||
my='xs'
|
||||
radius='md'
|
||||
size='md'
|
||||
id='username'
|
||||
label='Username'
|
||||
{...form.getInputProps('username')}
|
||||
/>
|
||||
<PasswordInput
|
||||
my='xs'
|
||||
radius='md'
|
||||
size='md'
|
||||
id='password'
|
||||
label='Password'
|
||||
{...form.getInputProps('password')}
|
||||
/>
|
||||
{show_local_login && (
|
||||
<form onSubmit={form.onSubmit((v) => onSubmit(v))}>
|
||||
<TextInput
|
||||
my='xs'
|
||||
radius='md'
|
||||
size='md'
|
||||
id='username'
|
||||
label='Username'
|
||||
{...form.getInputProps('username')}
|
||||
/>
|
||||
<PasswordInput
|
||||
my='xs'
|
||||
radius='md'
|
||||
size='md'
|
||||
id='password'
|
||||
label='Password'
|
||||
{...form.getInputProps('password')}
|
||||
/>
|
||||
|
||||
<Group position='apart'>
|
||||
{user_registration && (
|
||||
<Anchor size='xs' href='/auth/register' component={Link}>
|
||||
Don't have an account? Register
|
||||
</Anchor>
|
||||
)}
|
||||
<Group position='apart'>
|
||||
{user_registration && (
|
||||
<Anchor size='xs' href='/auth/register' component={Link}>
|
||||
Don't have an account? Register
|
||||
</Anchor>
|
||||
)}
|
||||
|
||||
<Button size='sm' p='xs' radius='md' my='xs' type='submit' loading={loading}>
|
||||
Login
|
||||
</Button>
|
||||
</Group>
|
||||
</form>
|
||||
<Button size='sm' p='xs' radius='md' my='xs' type='submit' loading={loading}>
|
||||
Login
|
||||
</Button>
|
||||
</Group>
|
||||
</form>
|
||||
)}
|
||||
</Card>
|
||||
</Center>
|
||||
</>
|
||||
|
|
|
@ -231,7 +231,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
|
|||
if (file.password) file.password = true;
|
||||
return {
|
||||
props: {
|
||||
image: file,
|
||||
file,
|
||||
user,
|
||||
pass,
|
||||
prismRender: true,
|
||||
|
|
|
@ -29,7 +29,7 @@ async function main() {
|
|||
|
||||
for (let i = 0; i !== files.length; ++i) {
|
||||
const file = files[i];
|
||||
if (!datasource.get(file.name)) {
|
||||
if (!(await datasource.get(file.name))) {
|
||||
if (process.argv.includes('--force-delete')) {
|
||||
console.log(`File ${file.name} does not exist. Deleting...`);
|
||||
await prisma.file.delete({
|
||||
|
|
Loading…
Reference in a new issue