feat: built-in ssl support

- CORE_HTTPS is now CORE_RETURN_HTTPS
- SSL_(KEY/CERT/ALLOW_HTTP1)
This commit is contained in:
diced 2022-12-07 19:40:54 -08:00
parent eadfa09570
commit c21d8f837e
No known key found for this signature in database
GPG key ID: 370BD1BA142842D1
9 changed files with 61 additions and 21 deletions

View file

@ -1,5 +1,5 @@
export interface ConfigCore {
https: boolean;
return_https: boolean;
secret: string;
host: string;
port: number;
@ -134,6 +134,12 @@ export interface ConfigExif {
remove_gps: boolean;
}
export interface ConfigSsl {
allow_http1: boolean;
key: string;
cert: string;
}
export interface Config {
core: ConfigCore;
uploader: ConfigUploader;
@ -147,4 +153,5 @@ export interface Config {
chunks: ConfigChunks;
mfa: ConfigMfa;
exif: ConfigExif;
ssl: ConfigSsl;
}

View file

@ -1,10 +1,11 @@
import { parse } from 'dotenv';
import { expand } from 'dotenv-expand';
import { existsSync, readFileSync } from 'fs';
import { resolve } from 'path';
import Logger from '../logger';
import { humanToBytes } from '../utils/bytes';
export type ValueType = 'string' | 'number' | 'boolean' | 'array' | 'json-array' | 'human-to-byte';
export type ValueType = 'string' | 'number' | 'boolean' | 'array' | 'json-array' | 'human-to-byte' | 'path';
function isObject(value: any): value is Record<string, any> {
return typeof value === 'object' && value !== null;
@ -55,7 +56,7 @@ export default function readConfig() {
}
const maps = [
map('CORE_HTTPS', 'boolean', 'core.https'),
map('CORE_RETURN_HTTPS', 'boolean', 'core.return_https'),
map('CORE_SECRET', 'string', 'core.secret'),
map('CORE_HOST', 'string', 'core.host'),
map('CORE_PORT', 'number', 'core.port'),
@ -150,6 +151,10 @@ export default function readConfig() {
map('EXIF_ENABLED', 'boolean', 'exif.enabled'),
map('EXIF_REMOVE_GPS', 'boolean', 'exif.remove_gps'),
map('SSL_KEY', 'path', 'ssl.key'),
map('SSL_CERT', 'path', 'ssl.cert'),
map('SSL_ALLOW_HTTP1', 'boolean', 'ssl.allow_http1'),
];
const config = {};
@ -186,6 +191,10 @@ export default function readConfig() {
parsed = humanToBytes(value) ?? undefined;
if (!parsed) logger.debug(`Unable to parse ${map.env}=${value}`);
break;
case 'path':
parsed = resolve(value);
if (!existsSync(parsed)) logger.debug(`Unable to find ${map.env}=${value} (path does not exist)`);
break;
default:
parsed = value;

View file

@ -23,7 +23,7 @@ const discord_content = s
const validator = s.object({
core: s.object({
https: s.boolean.default(false),
return_https: s.boolean.default(false),
secret: s.string.lengthGreaterThanOrEqual(8),
host: s.string.default('0.0.0.0'),
port: s.number.default(3000),
@ -206,6 +206,13 @@ const validator = s.object({
enabled: false,
remove_gps: false,
}),
ssl: s
.object({
key: s.string,
cert: s.string,
allow_http1: s.boolean.default(false),
})
.optional.nullish.default(null),
});
export default function validate(config): Config {

View file

@ -25,7 +25,7 @@ async function handler({ code, state, host }: OAuthQuery, logger: Logger): Promi
return {
redirect: discord_auth.oauth_url(
config.oauth.discord_client_id,
`${config.core.https ? 'https' : 'http'}://${host}`,
`${config.core.return_https ? 'https' : 'http'}://${host}`,
state
),
};
@ -35,7 +35,7 @@ async function handler({ code, state, host }: OAuthQuery, logger: Logger): Promi
client_secret: config.oauth.discord_client_secret,
code,
grant_type: 'authorization_code',
redirect_uri: `${config.core.https ? 'https' : 'http'}://${host}/api/auth/oauth/discord`,
redirect_uri: `${config.core.return_https ? 'https' : 'http'}://${host}/api/auth/oauth/discord`,
scope: 'identify',
});

View file

@ -24,7 +24,7 @@ async function handler({ code, state, host }: OAuthQuery, logger: Logger): Promi
return {
redirect: google_auth.oauth_url(
config.oauth.google_client_id,
`${config.core.https ? 'https' : 'http'}://${host}`,
`${config.core.return_https ? 'https' : 'http'}://${host}`,
state
),
};
@ -33,7 +33,7 @@ async function handler({ code, state, host }: OAuthQuery, logger: Logger): Promi
code,
client_id: config.oauth.google_client_id,
client_secret: config.oauth.google_client_secret,
redirect_uri: `${config.core.https ? 'https' : 'http'}://${host}/api/auth/oauth/google`,
redirect_uri: `${config.core.return_https ? 'https' : 'http'}://${host}/api/auth/oauth/google`,
grant_type: 'authorization_code',
});

View file

@ -58,14 +58,14 @@ async function handler(req: NextApiReq, res: NextApiRes) {
await sendShorten(
user,
url,
`${zconfig.core.https ? 'https' : 'http'}://${req.headers.host}${zconfig.urls.route}/${
`${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}${zconfig.urls.route}/${
req.body.vanity ? req.body.vanity : invis ? invis.invis : url.id
}`
);
}
return res.json({
url: `${zconfig.core.https ? 'https' : 'http'}://${req.headers.host}${zconfig.urls.route}/${
url: `${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}${zconfig.urls.route}/${
req.body.vanity ? req.body.vanity : invis ? invis.invis : url.id
}`,
});

View file

@ -179,7 +179,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
);
} else {
response.files.push(
`${zconfig.core.https ? 'https' : 'http'}://${req.headers.host}${
`${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}${
zconfig.uploader.route === '/' ? '' : zconfig.uploader.route
}/${invis ? invis.invis : file.file}`
);
@ -189,7 +189,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
await sendUpload(
user,
file,
`${zconfig.core.https ? 'https' : 'http'}://${req.headers.host}/r/${
`${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}/r/${
invis ? invis.invis : file.file
}`
);
@ -311,7 +311,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
);
} else {
response.files.push(
`${zconfig.core.https ? 'https' : 'http'}://${req.headers.host}${
`${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}${
zconfig.uploader.route === '/' ? '' : zconfig.uploader.route
}/${invis ? invis.invis : image.file}`
);
@ -323,7 +323,9 @@ async function handler(req: NextApiReq, res: NextApiRes) {
await sendUpload(
user,
image,
`${zconfig.core.https ? 'https' : 'http'}://${req.headers.host}/r/${invis ? invis.invis : image.file}`
`${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}/r/${
invis ? invis.invis : image.file
}`
);
}

View file

@ -35,7 +35,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
error: 'oauth token expired',
redirect_uri: discord_auth.oauth_url(
config.oauth.discord_client_id,
`${config.core.https ? 'https' : 'http'}://${req.headers.host}`
`${config.core.return_https ? 'https' : 'http'}://${req.headers.host}`
),
});
}
@ -59,7 +59,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
error: 'oauth token expired',
redirect_uri: discord_auth.oauth_url(
config.oauth.discord_client_id,
`${config.core.https ? 'https' : 'http'}://${req.headers.host}`
`${config.core.return_https ? 'https' : 'http'}://${req.headers.host}`
),
});
}
@ -90,7 +90,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
error: 'oauth token expired',
redirect_uri: google_auth.oauth_url(
config.oauth.google_client_id,
`${config.core.https ? 'https' : 'http'}://${req.headers.host}`
`${config.core.return_https ? 'https' : 'http'}://${req.headers.host}`
),
});
}
@ -113,7 +113,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
error: 'oauth token expired',
redirect_uri: google_auth.oauth_url(
config.oauth.google_client_id,
`${config.core.https ? 'https' : 'http'}://${req.headers.host}`
`${config.core.return_https ? 'https' : 'http'}://${req.headers.host}`
),
});
}

View file

@ -4,8 +4,8 @@ import datasource from '../lib/datasource';
import Logger from '../lib/logger';
import { getStats } from './util';
import fastify, { FastifyInstance } from 'fastify';
import { createReadStream, existsSync } from 'fs';
import fastify, { FastifyInstance, FastifyServerOptions } from 'fastify';
import { createReadStream, existsSync, readFileSync } from 'fs';
import dbFileDecorator from './decorators/dbFile';
import notFound from './decorators/notFound';
import postFileDecorator from './decorators/postFile';
@ -24,7 +24,7 @@ import urlsRoute, { urlsRouteOnResponse } from './routes/urls';
const dev = process.env.NODE_ENV === 'development';
const logger = Logger.get('server');
const server = fastify();
const server = fastify(genFastifyOpts());
if (dev) {
server.addHook('onRoute', (opts) => {
@ -197,3 +197,18 @@ async function clearInvites(this: FastifyInstance) {
logger.child('invites').debug(`deleted ${count} used invites`);
}
function genFastifyOpts(): FastifyServerOptions {
const opts = {};
if (config.ssl?.cert && config.ssl?.key) {
opts['https'] = {
key: readFileSync(config.ssl.key),
cert: readFileSync(config.ssl.cert),
};
if (config.ssl?.allow_http1) opts['https']['allowHTTP1'] = true;
}
return opts;
}