support login with http-basic-authentication
Massimo Melina committed
Jan 25, 2024 at 16:31 UTC
0b8e159dcd9d87aa8c80e53a322e24cbb51dd95f
3 files changed
+20
-16
src/api.auth.ts
+3
-3
@@ -6,7 +6,7 @@ import { SRPServerSessionStep1 } from 'tssrp6a'
6
import { ADMIN_URI, HTTP_UNAUTHORIZED, HTTP_BAD_REQUEST, HTTP_SERVER_ERROR, HTTP_CONFLICT, HTTP_NOT_FOUND } from './const'
7
import { ctxAdminAccess } from './adminApis'
8
import { sessionDuration } from './middlewares'
9
-import { getCurrentUsername, loggedIn, srpStep1 } from './auth'
9
+import { getCurrentUsername, setLoggedIn, srpStep1 } from './auth'
10
import { defineConfig } from './config'
11
12
const ongoingLogins:Record<string,SRPServerSessionStep1> = {} // store data that doesn't fit session object
@@ -52,7 +52,7 @@ export const loginSrp2: ApiHandler = async ({ pubKey, proof }, ctx) => {
52
return new ApiError(HTTP_NOT_FOUND)
53
try {
54
const M2 = await step1.step2(BigInt(pubKey), BigInt(proof))
55
- await loggedIn(ctx, username)
55
+ await setLoggedIn(ctx, username)
56
delete ctx.session.loggingIn
57
return {
58
proof: String(M2),
@@ -73,7 +73,7 @@ export const loginSrp2: ApiHandler = async ({ pubKey, proof }, ctx) => {
73
export const logout: ApiHandler = async ({}, ctx) => {
74
if (!ctx.session)
75
return new ApiError(HTTP_SERVER_ERROR)
76
- await loggedIn(ctx, false)
76
+ await setLoggedIn(ctx, false)
77
// 401 is a convenient code for OK: the browser clears a possible http authentication (hopefully), and Admin automatically triggers login dialog
78
return new ApiError(HTTP_UNAUTHORIZED)
79
}
src/auth.ts
+2
-3
@@ -2,7 +2,6 @@ import { Account, getAccount, normalizeUsername } from './perm'
2
import { HTTP_NOT_ACCEPTABLE, HTTP_SERVER_ERROR } from './cross-const'
3
import { SRPParameters, SRPRoutines, SRPServerSession } from 'tssrp6a'
4
import { Context } from 'koa'
5
-import { prepareState } from './middlewares'
5
import { srpClientPart } from './srp'
6
import { getOrSet } from './cross'
7
import { createHash } from 'node:crypto'
@@ -39,7 +38,7 @@ export function getCurrentUsername(ctx: Context): string {
38
}
39
40
// centralized log-in state
42
-export async function loggedIn(ctx: Context, username: string | false) {
41
+export async function setLoggedIn(ctx: Context, username: string | false) {
42
const s = ctx.session
43
if (!s)
44
return ctx.throw(HTTP_SERVER_ERROR,'session')
@@ -49,7 +48,7 @@ export async function loggedIn(ctx: Context, username: string | false) {
48
}
49
invalidSessions.delete(username)
50
s.username = normalizeUsername(username)
52
- await prepareState(ctx, async ()=>{}) // updating the state is necessary to send complete session data so that frontend shows admin button
51
+ ctx.state.account = getAccount(username)
52
}
53
54
export const invalidSessions = new Set<string>() // since session are currently stored in cookies, we need to memorize this until we meet again
src/middlewares.ts
+15
-10
@@ -9,7 +9,7 @@ import { applyBlock } from './block'
9
import { Account, accountCanLogin, getAccount } from './perm'
10
import { Connection, disconnect, normalizeIp, socket2connection, updateConnectionForCtx } from './connections'
11
import basicAuth from 'basic-auth'
12
-import { invalidSessions, srpCheck } from './auth'
12
+import { invalidSessions, setLoggedIn, srpCheck } from './auth'
13
import { constants } from 'zlib'
14
import { baseUrl, getHttpsWorkingPort } from './listen'
15
import { defineConfig } from './config'
@@ -97,7 +97,7 @@ export function getProxyDetected() {
97
}
98
99
export const prepareState: Koa.Middleware = async (ctx, next) => {
100
- if (ctx.session) {
100
+ if (ctx.session?.username) {
101
if (invalidSessions.delete(ctx.session.username))
102
delete ctx.session.username
103
ctx.session.maxAge = sessionDuration.compiled()
@@ -111,22 +111,27 @@ export const prepareState: Koa.Middleware = async (ctx, next) => {
111
updateConnectionForCtx(ctx)
112
await next()
113
114
- async function urlLogin() {
114
+ function urlLogin() {
115
const { login } = ctx.query
116
if (!login) return
117
const [u,p] = splitAt(':', String(login))
118
+ ctx.redirect(ctx.originalUrl.slice(0, -ctx.querystring.length-1)) // redirect to hide credentials
119
+ return doLogin(u, p)
120
+ }
121
+
122
+ function getHttpAccount() {
123
+ const credentials = basicAuth(ctx.req)
124
+ return doLogin(credentials?.name||'', credentials?.pass||'')
125
+ }
126
+
127
+ async function doLogin(u: string, p: string) {
128
const a = await srpCheck(u, p)
129
if (a) {
120
- ctx.session!.username = a.username
121
- ctx.redirect(ctx.originalUrl.slice(0, -ctx.querystring.length-1)) // redirect to hide credentials
130
+ setLoggedIn(ctx, a.username)
131
+ ctx.headers['x-username'] = a.username // give an easier way to determine if the login was successful
132
}
133
return a
134
}
125
-
126
- async function getHttpAccount() {
127
- const credentials = basicAuth(ctx.req)
128
- return srpCheck(credentials?.name||'', credentials?.pass||'')
129
- }
135
}
136
137
declare module "koa" {