feat: headless mode

This commit is contained in:
diced 2022-12-01 09:27:14 -08:00
parent 231f734fd5
commit 6f3081cb8e
No known key found for this signature in database
GPG key ID: 370BD1BA142842D1
7 changed files with 1749 additions and 848 deletions

View file

@ -102,6 +102,8 @@ export interface ConfigFeatures {
oauth_registration: boolean;
user_registration: boolean;
headless: boolean;
}
export interface ConfigOAuth {

View file

@ -138,6 +138,8 @@ export default function readConfig() {
map('FEATURES_OAUTH_REGISTRATION', 'boolean', 'features.oauth_registration'),
map('FEATURES_USER_REGISTRATION', 'boolean', 'features.user_registration'),
map('FEATURES_HEADLESS', 'boolean', 'features.headless'),
map('CHUNKS_MAX_SIZE', 'human-to-byte', 'chunks.max_size'),
map('CHUNKS_CHUNKS_SIZE', 'human-to-byte', 'chunks.chunks_size'),

View file

@ -166,12 +166,14 @@ const validator = s.object({
invites_length: s.number.default(6),
oauth_registration: s.boolean.default(false),
user_registration: s.boolean.default(false),
headless: s.boolean.default(false),
})
.default({
invites: false,
invites_length: 6,
oauth_registration: false,
user_registration: false,
headless: false,
}),
chunks: s
.object({

View file

@ -1,4 +1,5 @@
import { OauthProviders } from '@prisma/client';
import config from 'lib/config';
import Logger from 'lib/logger';
import prisma from 'lib/prisma';
import { createToken } from 'lib/util';
@ -30,6 +31,12 @@ export const withOAuth =
const logger = Logger.get(`oauth::${provider}`);
function oauthError(error: string) {
if (config.features.headless)
return res.json({
error,
provider,
});
return res.redirect(`/oauth_error?error=${error}&provider=${provider}`);
}

View file

@ -157,6 +157,18 @@ export const withZipline =
req.user = async () => {
try {
const authHeader = req.headers.authorization;
if (authHeader) {
const user = await prisma.user.findFirst({
where: {
token: authHeader,
},
include: { oauth: true },
});
if (user) return user;
}
const userId = req.getCookie('user');
if (!userId) return null;

View file

@ -81,12 +81,17 @@ async function start() {
const handle = nextServer.getRequestHandler();
const router = Router({
defaultRoute: (req, res) => {
if (config.features.headless) {
const url = req.url.toLowerCase();
if (!url.startsWith('/api') || url === '/api') return notFound(req, res, nextServer);
}
handle(req, res);
},
});
router.on('GET', '/favicon.ico', async (req, res) => {
if (!existsSync('./public/favicon.ico')) return nextServer.render404(req, res);
if (!existsSync('./public/favicon.ico')) return notFound(req, res, nextServer);
const favicon = createReadStream('./public/favicon.ico');
res.setHeader('Content-Type', 'image/x-icon');
@ -95,14 +100,14 @@ async function start() {
});
router.on('GET', `${config.urls.route}/:id`, async (req, res, params) => {
if (params.id === '') return nextServer.render404(req, res as ServerResponse);
if (params.id === '') return notFound(req, res, nextServer);
const url = await prisma.url.findFirst({
where: {
OR: [{ id: params.id }, { vanity: params.id }, { invisible: { invis: decodeURI(params.id) } }],
},
});
if (!url) return nextServer.render404(req, res as ServerResponse);
if (!url) return notFound(req, res, nextServer);
const nUrl = await prisma.url.update({
where: {
@ -122,7 +127,7 @@ async function start() {
Logger.get('url').debug(`url deleted due to max views ${JSON.stringify(nUrl)}`);
return nextServer.render404(req, res as ServerResponse);
return notFound(req, res, nextServer);
}
return redirect(res, url.destination);
@ -132,8 +137,9 @@ async function start() {
'GET',
config.uploader.route === '/' ? '/:id' : `${config.uploader.route}/:id`,
async (req, res, params) => {
if (params.id === '') return nextServer.render404(req, res as ServerResponse);
else if (params.id === 'dashboard') return nextServer.render(req, res as ServerResponse, '/dashboard');
if (params.id === '') return notFound(req, res, nextServer);
else if (params.id === 'dashboard' && !config.features.headless)
return nextServer.render(req, res as ServerResponse, '/dashboard');
const image = await prisma.image.findFirst({
where: {
@ -144,7 +150,7 @@ async function start() {
if (!image) return rawFile(req, res, nextServer, params.id);
else {
const failed = await preFile(image, prisma);
if (failed) return nextServer.render404(req, res as ServerResponse);
if (failed) return notFound(req, res, nextServer);
if (image.password || image.embed || image.mimetype.startsWith('text/'))
redirect(res, `/view/${image.file}`);
@ -156,7 +162,7 @@ async function start() {
);
router.on('GET', '/r/:id', async (req, res, params) => {
if (params.id === '') return nextServer.render404(req, res as ServerResponse);
if (params.id === '') return notFound(req, res, nextServer);
const image = await prisma.image.findFirst({
where: {
@ -167,13 +173,17 @@ async function start() {
if (!image) await rawFile(req, res, nextServer, params.id);
else {
const failed = await preFile(image, prisma);
if (failed) return nextServer.render404(req, res as ServerResponse);
if (failed) return notFound(req, res, nextServer);
if (image.password) {
res.setHeader('Content-Type', 'application/json');
res.statusCode = 403;
return res.end(
JSON.stringify({ error: "can't view a raw file that has a password", url: `/view/${image.file}` })
JSON.stringify({
error: "can't view a raw file that has a password",
url: `/view/${image.file}`,
code: 403,
})
);
} else await rawFile(req, res, nextServer, params.id);
}
@ -202,7 +212,11 @@ async function start() {
http.listen(config.core.port, config.core.host ?? '0.0.0.0');
logger.info(`started ${dev ? 'development' : 'production'} zipline@${version} server`);
logger.info(
`started ${dev ? 'development' : 'production'} zipline@${version} server${
config.features.headless ? ' (headless)' : ''
}`
);
stats(prisma);
clearInvites(prisma);
@ -211,6 +225,16 @@ async function start() {
setInterval(() => stats(prisma), config.core.stats_interval * 1000);
}
async function notFound(req: IncomingMessage, res: ServerResponse, nextServer: NextServer) {
if (config.features.headless) {
res.statusCode = 404;
res.setHeader('Content-Type', 'application/json');
return res.end(JSON.stringify({ error: 'not found', url: req.url, code: 404 }));
} else {
return notFound(req, res, nextServer);
}
}
async function preFile(file: Image, prisma: PrismaClient) {
if (file.expires_at && file.expires_at < new Date()) {
await datasource.delete(file.file);
@ -242,7 +266,7 @@ async function postFile(file: Image, prisma: PrismaClient) {
async function rawFile(req: IncomingMessage, res: OutgoingMessage, nextServer: NextServer, id: string) {
const data = await datasource.get(id);
if (!data) return nextServer.render404(req, res as ServerResponse);
if (!data) return notFound(req, res as ServerResponse, nextServer);
const mimetype = await guess(extname(id));
const size = await datasource.size(id);
@ -253,7 +277,7 @@ async function rawFile(req: IncomingMessage, res: OutgoingMessage, nextServer: N
data.pipe(res);
data.on('error', (e) => {
logger.debug(`error while serving raw file ${id}: ${e}`);
nextServer.render404(req, res as ServerResponse);
notFound(req, res as ServerResponse, nextServer);
});
data.on('end', () => res.end());
}
@ -269,7 +293,7 @@ async function fileDb(
if (Object.keys(exts).includes(ext)) return handle(req, res as ServerResponse);
const data = await datasource.get(image.file);
if (!data) return nextServer.render404(req, res as ServerResponse);
if (!data) return notFound(req, res as ServerResponse, nextServer);
const size = await datasource.size(image.file);
@ -279,7 +303,7 @@ async function fileDb(
data.pipe(res);
data.on('error', (e) => {
logger.debug(`error while serving raw file ${image.file}: ${e}`);
nextServer.render404(req, res as ServerResponse);
notFound(req, res as ServerResponse, nextServer);
});
data.on('end', () => res.end());
}

2518
yarn.lock

File diff suppressed because it is too large Load diff