admin/options: force https
Massimo Melina committed
Apr 1, 2023 at 22:04 UTC
48a9d23dd9b63dbcc6a0d23ee65ea181e33ef114
3 files changed
+27
-7
admin/src/OptionsPage.ts
+10
-6
@@ -64,6 +64,7 @@ export default function OptionsPage() {
64
placeholder: "no limit",
65
md: 3,
66
}
67
+ const httpsEnabled = values.https_port >= 0
68
return h(Form, {
69
sx: { maxWidth: '60em' },
70
values,
@@ -91,30 +92,33 @@ export default function OptionsPage() {
92
{ k: 'port', comp: ServerPort, md: 3, label:"HTTP port", status: status?.http||true, suggestedPort: 80 },
93
{ k: 'https_port', comp: ServerPort, md: 3, label: "HTTPS port", status: status?.https||true, suggestedPort: 443,
94
onChange(v: number) {
94
- if (v >= 0 && values.https_port < 0 && !values.cert)
95
+ if (v >= 0 && !httpsEnabled && !values.cert)
96
suggestMakingCert()
97
return v
98
}
99
},
100
{ k: 'max_kbps', ...maxSpeedDefaults, label: "Limit output KB/s", helperText: "Doesn't apply to localhost" },
101
{ k: 'max_kbps_per_ip', ...maxSpeedDefaults, label: "Limit output KB/s per-ip" },
101
- values.https_port >= 0 && { k: 'cert', comp: FileField, label: "HTTPS certificate file",
102
+ httpsEnabled && values.port >= 0 && { k: 'force_https', comp: BoolField, md: 4, label: "Force HTTPS",
103
+ helperText: "Not applied to localhost"
104
+ },
105
+ httpsEnabled && { k: 'cert', comp: FileField, md: 4, label: "HTTPS certificate file",
106
...with_(status?.https.error, e => isCertError(e) ? {
107
error: true,
108
helperText: [e, ' - ', h(Link, { key: 'fix', sx: { cursor: 'pointer' }, onClick: makeCertAndSave }, "make one")]
109
} : null)
110
},
107
- values.https_port >= 0 && { k: 'private_key', comp: FileField, label: "HTTPS private key file",
111
+ httpsEnabled && { k: 'private_key', comp: FileField, md: 4, label: "HTTPS private key file",
112
...with_(status?.https.error, e => isKeyError(e) ? { error: true, helperText: e } : null)
113
},
114
{ k: 'open_browser_at_start', comp: BoolField, label: "Open Admin-panel at start", md: 4,
115
helperText: "Browser is automatically launched with HFS"
116
},
113
- { k: 'localhost_admin', comp: BoolField, label: "Admin access for localhost connections", md: 5,
117
+ { k: 'localhost_admin', comp: BoolField, label: "Unprotected admin on localhost", md: 4,
118
getError: x => !x && admins?.length===0 && "First create at least one admin account",
115
- helperText: "To access Admin without entering credentials"
119
+ helperText: "Access Admin-panel without entering credentials"
120
},
117
- { k: 'file_menu_on_link', comp: SelectField, label: "Access file menu", sm: 12, md: 3,
121
+ { k: 'file_menu_on_link', comp: SelectField, label: "Access file menu", sm: 12, md: 4,
122
options: { "by clicking on file name": true, "by dedicated button": false }
123
},
124
{ k: 'title', helperText: "You can see this in the tab of your browser" },
src/listen.ts
+4
@@ -20,6 +20,10 @@ let httpsSrv: http.Server & ServerExtra
20
21
const openBrowserAtStart = defineConfig('open_browser_at_start', !DEV)
22
23
+export function getHttpsWorkingPort() {
24
+ return httpsSrv.listening && (httpsSrv.address() as any)?.port
25
+}
26
+
27
export const portCfg = defineConfig<number>('port', 80)
28
portCfg.sub(async port => {
29
while (!app)
src/middlewares.ts
+13
-1
@@ -12,7 +12,7 @@ import {
12
} from './const'
13
import { FRONTEND_URI } from './const'
14
import { statusCodeForMissingPerm, nodeIsDirectory, urlToNode, vfs } from './vfs'
15
-import { dirTraversal, newObj, stream2string, tryJson } from './misc'
15
+import { dirTraversal, isLocalHost, newObj, stream2string, tryJson } from './misc'
16
import { zipStreamFromFolder } from './zip'
17
import { serveFile, serveFileNode } from './serveFile'
18
import { serveGuiFiles } from './serveGuiFiles'
@@ -30,6 +30,10 @@ import formidable from 'formidable'
30
import { uploadWriter } from './upload'
31
import { allowAdmin, favicon } from './adminApis'
32
import { constants } from 'zlib'
33
+import { getHttpsWorkingPort } from './listen'
34
+import { defineConfig } from './config'
35
+
36
+const forceHttps = defineConfig('force_https', true)
37
38
export const gzipper = compress({
39
threshold: 2048,
@@ -78,6 +82,14 @@ export const serveGuiAndSharedFiles: Koa.Middleware = async (ctx, next) => {
82
}
83
if (ctx.body)
84
return next()
85
+ if (!ctx.secure && forceHttps.get() && getHttpsWorkingPort() && !isLocalHost(ctx)) {
86
+ const { URL } = ctx
87
+ URL.protocol = 'https'
88
+ URL.port = getHttpsWorkingPort()
89
+ ctx.status = 307 // this ensures the client doesn't switch to a simpler GET request
90
+ return ctx.redirect(URL.href)
91
+ }
92
+
93
if (path.startsWith(FRONTEND_URI))
94
return serveFrontendPrefixed(ctx,next)
95
if (path.length === ADMIN_URI.length - 1 && ADMIN_URI.startsWith(path))