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) => {