support ?login
Massimo Melina committed
Oct 30, 2023 at 00:08 UTC
aaf825d577a3d214475e9bda66eb0b2f7c346b33
4 files changed
+30
-11
README.md
+1
@@ -182,6 +182,7 @@ Some actions you can take for improved security:
182
- Appending `#LOGIN` to address will bring up the login dialog
183
- Appending ?lang=CODE to address will force a specific language
184
- right/ctrl/command click on toggle-all checkbox will invert each checkbox state
185
+- Appending `?login=USER:PASSWORD` will automatically log in the browser
186
187
## Contribute
188
src/auth.ts
+2
-2
@@ -20,10 +20,10 @@ export async function srpStep1(account: Account) {
20
21
export async function srpCheck(username: string, password: string) {
22
const account = getAccount(username)
23
- if (!account?.srp || !password) return false
23
+ if (!account?.srp || !password) return
24
const { step1, salt, pubKey } = await srpStep1(account)
25
const client = await srpClientPart(username, password, salt, pubKey)
26
- return await step1.step2(client.A, client.M1).then(() => true, () => false)
26
+ return await step1.step2(client.A, client.M1).then(() => account, () => {})
27
}
28
29
// centralized log-in state
src/cross.ts
+7
@@ -116,6 +116,13 @@ export function enforceFinal(sub:string, s:string, evenEmpty=false) {
116
return !evenEmpty && !s || s.endsWith(sub) ? s : s+sub
117
}
118
119
+export function splitAt(sub: string | number, all: string): [string, string] {
120
+ if (typeof sub === 'number')
121
+ return [all.slice(0, sub), all.slice(sub + 1)]
122
+ const i = all.indexOf(sub)
123
+ return i < 0 ? [all,''] : [all.slice(0, i), all.slice(i + sub.length)]
124
+}
125
+
126
export function truthy<T>(value: T): value is Truthy<T> {
127
return Boolean(value)
128
}
src/middlewares.ts
+20
-9
@@ -7,7 +7,8 @@ import { ADMIN_URI, API_URI, BUILD_TIMESTAMP, DEV,
7
} from './const'
8
import { FRONTEND_URI } from './const'
9
import { statusCodeForMissingPerm, nodeIsDirectory, urlToNode, vfs, walkNode, VfsNode, getNodeName } from './vfs'
10
-import { DAY, asyncGeneratorToReadable, dirTraversal, filterMapGenerator, isLocalHost, stream2string, tryJson } from './misc'
10
+import { DAY, asyncGeneratorToReadable, dirTraversal, filterMapGenerator, isLocalHost, stream2string, tryJson,
11
+ splitAt } from './misc'
12
import { zipStreamFromFolder } from './zip'
13
import { serveFile, serveFileNode } from './serveFile'
14
import { serveGuiFiles } from './serveGuiFiles'
@@ -17,7 +18,7 @@ import { applyBlock } from './block'
18
import { accountCanLogin, getAccount } from './perm'
19
import { socket2connection, updateConnection, normalizeIp } from './connections'
20
import basicAuth from 'basic-auth'
20
-import { loggedIn, srpCheck } from './auth'
21
+import { srpCheck } from './auth'
22
import { basename, dirname } from 'path'
23
import { pipeline } from 'stream/promises'
24
import formidable from 'formidable'
@@ -208,7 +209,7 @@ export const prepareState: Koa.Middleware = async (ctx, next) => {
209
if (ctx.session)
210
ctx.session.maxAge = sessionDuration.compiled()
211
// calculate these once and for all
211
- const a = ctx.state.account = await getHttpAccount(ctx) ?? getAccount(ctx.session?.username, false)
212
+ const a = ctx.state.account = await urlLogin() || await getHttpAccount() || getAccount(ctx.session?.username, false)
213
if (a && !accountCanLogin(a))
214
ctx.state.account = undefined
215
const conn = ctx.state.connection = socket2connection(ctx.socket)
@@ -216,13 +217,23 @@ export const prepareState: Koa.Middleware = async (ctx, next) => {
217
if (conn)
218
updateConnection(conn, { ctx, op: undefined })
219
await next()
219
-}
220
221
-async function getHttpAccount(ctx: Koa.Context) {
222
- const credentials = basicAuth(ctx.req)
223
- const account = getAccount(credentials?.name||'')
224
- if (account && await srpCheck(account.username, credentials!.pass))
225
- return account
221
+ async function urlLogin() {
222
+ const { login } = ctx.query
223
+ if (!login) return
224
+ const [u,p] = splitAt(':', String(login))
225
+ const a = await srpCheck(u, p)
226
+ if (a) {
227
+ ctx.session!.username = a.username
228
+ ctx.redirect(ctx.originalUrl.slice(0, -ctx.querystring.length-1))
229
+ }
230
+ return a
231
+ }
232
+
233
+ async function getHttpAccount() {
234
+ const credentials = basicAuth(ctx.req)
235
+ return srpCheck(credentials?.name||'', credentials?.pass||'')
236
+ }
237
}
238
239
export const paramsDecoder: Koa.Middleware = async (ctx, next) => {