Skip to content

Commit

Permalink
better code: split files
Browse files Browse the repository at this point in the history
  • Loading branch information
rejetto committed Dec 6, 2023
1 parent dbeb416 commit 59acc36
Show file tree
Hide file tree
Showing 9 changed files with 76 additions and 81 deletions.
4 changes: 2 additions & 2 deletions admin/src/LoginRequired.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { createElement as h, Fragment, useEffect, useRef, useState } from 'react
import { Center, getHFS, makeSessionRefresher } from './misc'
import { Form } from '@hfs/mui-grid-form'
import { apiCall } from './api'
import { srpSequence } from '@hfs/shared'
import { srpClientSequence } from '@hfs/shared'
import { Alert, Box } from '@mui/material'

export function LoginRequired({ children }: any) {
Expand Down Expand Up @@ -57,7 +57,7 @@ function LoginForm() {
}

async function login(username: string, password: string) {
const res = await srpSequence(username, password, apiCall).catch(err => {
const res = await srpClientSequence(username, password, apiCall).catch(err => {
throw err?.code === 401 ? "Wrong username or password"
: err === 'trust' ? "Login aborted: server identity cannot be trusted"
: err?.name === 'AbortError' ? "Server didn't respond"
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { apiCall } from '@hfs/shared/api'
import { state, useSnapState } from './state'
import { alertDialog, newDialog } from './dialog'
import { getHFS, getPrefixUrl, hIcon, makeSessionRefresher, srpSequence, working } from './misc'
import { getHFS, getPrefixUrl, hIcon, makeSessionRefresher, srpClientSequence, working } from './misc'
import { useNavigate } from 'react-router-dom'
import { createElement as h, Fragment, useEffect, useRef } from 'react'
import { t, useI18N } from './i18n'
Expand All @@ -12,7 +12,7 @@ import { CustomCode } from './components'

async function login(username:string, password:string) {
const stopWorking = working()
return srpSequence(username, password, apiCall).then(res => {
return srpClientSequence(username, password, apiCall).then(res => {
stopWorking()
sessionRefresher(res)
state.loginRequired = false
Expand Down
2 changes: 1 addition & 1 deletion shared/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import _ from 'lodash'
import { apiCall } from './api'
export * from './react'
export * from './dialogs'
export * from './srp'
export * from '../src/srp'
export * from '../src/cross'

(window as any)._ = _
Expand Down
16 changes: 0 additions & 16 deletions shared/srp.ts

This file was deleted.

39 changes: 6 additions & 33 deletions src/api.auth.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,20 @@
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt

import { Account, accountCanLogin, getAccount, getCurrentUsername, getFromAccount, normalizeUsername } from './perm'
import { Account, accountCanLogin, getAccount, getCurrentUsername, getFromAccount } from './perm'
import { verifyPassword } from './crypt'
import { ApiError, ApiHandler } from './apiMiddleware'
import { SRPParameters, SRPRoutines, SRPServerSession, SRPServerSessionStep1 } from 'tssrp6a'
import {
ADMIN_URI,
HTTP_UNAUTHORIZED, HTTP_BAD_REQUEST, HTTP_SERVER_ERROR, HTTP_NOT_ACCEPTABLE, HTTP_CONFLICT, HTTP_NOT_FOUND
} from './const'
import Koa from 'koa'
import { SRPServerSessionStep1 } from 'tssrp6a'
import { ADMIN_URI, HTTP_UNAUTHORIZED, HTTP_BAD_REQUEST, HTTP_SERVER_ERROR, HTTP_NOT_ACCEPTABLE, HTTP_CONFLICT,
HTTP_NOT_FOUND } from './const'
import { changeSrpHelper, changePasswordHelper } from './api.helpers'
import { ctxAdminAccess } from './adminApis'
import { prepareState, sessionDuration } from './middlewares'
import { sessionDuration } from './middlewares'
import { loggedIn, srpStep1 } from './auth'
import { defineConfig } from './config'

const srp6aNimbusRoutines = new SRPRoutines(new SRPParameters())
const ongoingLogins:Record<string,SRPServerSessionStep1> = {} // store data that doesn't fit session object
const keepSessionAlive = defineConfig('keep_session_alive', true)

// centralized log-in state
async function loggedIn(ctx:Koa.Context, username: string | false) {
const s = ctx.session
if (!s)
return ctx.throw(HTTP_SERVER_ERROR,'session')
if (username === false) {
delete s.username
return
}
s.username = normalizeUsername(username)
await prepareState(ctx, async ()=>{}) // updating the state is necessary to send complete session data so that frontend shows admin button
}

function makeExp() {
return !keepSessionAlive.get() ? undefined
: { exp: new Date(Date.now() + sessionDuration.compiled()) }
Expand Down Expand Up @@ -73,17 +57,6 @@ export const loginSrp1: ApiHandler = async ({ username }, ctx) => {
}
}

export async function srpStep1(account: Account) {
if (!account.srp)
throw HTTP_NOT_ACCEPTABLE
const [salt, verifier] = account.srp.split('|')
if (!salt || !verifier)
throw Error("malformed account")
const srpSession = new SRPServerSession(srp6aNimbusRoutines)
const step1 = await srpSession.step1(account.username, BigInt(salt), BigInt(verifier))
return { step1, salt, pubKey: String(step1.B) } // cast to string cause bigint can't be jsonized
}

export const loginSrp2: ApiHandler = async ({ pubKey, proof }, ctx) => {
if (!ctx.session)
return new ApiError(HTTP_SERVER_ERROR)
Expand Down
40 changes: 40 additions & 0 deletions src/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Account, getAccount, normalizeUsername } from './perm'
import { HTTP_NOT_ACCEPTABLE, HTTP_SERVER_ERROR } from './cross-const'
import { SRPParameters, SRPRoutines, SRPServerSession } from 'tssrp6a'
import { Context } from 'koa'
import { prepareState } from './middlewares'
import { srpClientPart } from './srp'

const srp6aNimbusRoutines = new SRPRoutines(new SRPParameters())

export async function srpStep1(account: Account) {
if (!account.srp)
throw HTTP_NOT_ACCEPTABLE
const [salt, verifier] = account.srp.split('|')
if (!salt || !verifier)
throw Error("malformed account")
const srpSession = new SRPServerSession(srp6aNimbusRoutines)
const step1 = await srpSession.step1(account.username, BigInt(salt), BigInt(verifier))
return { step1, salt, pubKey: String(step1.B) } // cast to string cause bigint can't be jsonized
}

export async function srpCheck(username: string, password: string) {
const account = getAccount(username)
if (!account?.srp || !password) return false
const { step1, salt, pubKey } = await srpStep1(account)
const client = await srpClientPart(username, password, salt, pubKey)
return await step1.step2(client.A, client.M1).then(() => true, () => false)
}

// centralized log-in state
export async function loggedIn(ctx: Context, username: string | false) {
const s = ctx.session
if (!s)
return ctx.throw(HTTP_SERVER_ERROR,'session')
if (username === false) {
delete s.username
return
}
s.username = normalizeUsername(username)
await prepareState(ctx, async ()=>{}) // updating the state is necessary to send complete session data so that frontend shows admin button
}
29 changes: 4 additions & 25 deletions src/middlewares.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,12 @@

import compress from 'koa-compress'
import Koa from 'koa'
import {
ADMIN_URI, API_URI, BUILD_TIMESTAMP, DEV,
import { ADMIN_URI, API_URI, BUILD_TIMESTAMP, DEV,
HTTP_FORBIDDEN, HTTP_NOT_FOUND, HTTP_FOOL, HTTP_UNAUTHORIZED, HTTP_BAD_REQUEST, HTTP_METHOD_NOT_ALLOWED,
} from './const'
import { FRONTEND_URI } from './const'
import { statusCodeForMissingPerm, nodeIsDirectory, urlToNode, vfs, walkNode, VfsNode, getNodeName } from './vfs'
import {
DAY,
asyncGeneratorToReadable,
dirTraversal,
filterMapGenerator,
isLocalHost,
stream2string,
tryJson, Dict
} from './misc'
import { DAY, asyncGeneratorToReadable, dirTraversal, filterMapGenerator, isLocalHost, stream2string, tryJson } from './misc'
import { zipStreamFromFolder } from './zip'
import { serveFile, serveFileNode } from './serveFile'
import { serveGuiFiles } from './serveGuiFiles'
Expand All @@ -26,8 +17,7 @@ import { applyBlock } from './block'
import { accountCanLogin, getAccount } from './perm'
import { socket2connection, updateConnection, normalizeIp } from './connections'
import basicAuth from 'basic-auth'
import { SRPClientSession, SRPParameters, SRPRoutines } from 'tssrp6a'
import { srpStep1 } from './api.auth'
import { loggedIn, srpCheck } from './auth'
import { basename, dirname } from 'path'
import { pipeline } from 'stream/promises'
import formidable from 'formidable'
Expand All @@ -36,8 +26,6 @@ import { allowAdmin, favicon } from './adminApis'
import { constants } from 'zlib'
import { baseUrl, getHttpsWorkingPort } from './listen'
import { defineConfig } from './config'
import { getLangData } from './lang'
import { getSection } from './customHtml'
import { sendErrorPage } from './errorPages'

const forceHttps = defineConfig('force_https', true)
Expand Down Expand Up @@ -215,6 +203,7 @@ export function getProxyDetected() {
return !ignoreProxies.get() && proxyDetected
&& { from: proxyDetected.ip, for: proxyDetected.get('X-Forwarded-For') }
}

export const prepareState: Koa.Middleware = async (ctx, next) => {
if (ctx.session)
ctx.session.maxAge = sessionDuration.compiled()
Expand All @@ -236,16 +225,6 @@ async function getHttpAccount(ctx: Koa.Context) {
return account
}

async function srpCheck(username: string, password: string) {
const account = getAccount(username)
if (!account?.srp || !password) return false
const { step1, salt, pubKey } = await srpStep1(account)
const client = new SRPClientSession(new SRPRoutines(new SRPParameters()))
const clientRes1 = await client.step1(username, password)
const clientRes2 = await clientRes1.step2(BigInt(salt), BigInt(pubKey))
return await step1.step2(clientRes2.A, clientRes2.M1).then(() => true, () => false)
}

export const paramsDecoder: Koa.Middleware = async (ctx, next) => {
ctx.params = ctx.method === 'POST' && ctx.originalUrl.startsWith(API_URI)
&& (tryJson(await stream2string(ctx.req)) || {})
Expand Down
19 changes: 19 additions & 0 deletions src/srp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt

import { SRPClientSession, SRPParameters, SRPRoutines } from 'tssrp6a'

export async function srpClientSequence(username:string, password:string, apiCall: (cmd:string, params:any) => any) {
const { pubKey, salt } = await apiCall('loginSrp1', { username })
if (!salt) throw Error('salt')
const client = await srpClientPart(username, password, salt, pubKey)
const res = await apiCall('loginSrp2', { pubKey: String(client.A), proof: String(client.M1) }) // bigint-s must be cast to string to be json-ed
await client.step3(BigInt(res.proof)).catch(() => Promise.reject('trust'))
return res
}

export async function srpClientPart(username: string, password: string, salt: string, pubKey: string) {
const srp6aNimbusRoutines = new SRPRoutines(new SRPParameters())
const srp = new SRPClientSession(srp6aNimbusRoutines);
const res = await srp.step1(username, password)
return await res.step2(BigInt(salt), BigInt(pubKey))
}
4 changes: 2 additions & 2 deletions tests/test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import axios, { AxiosRequestConfig } from 'axios'
import { wrapper } from 'axios-cookiejar-support'
import { CookieJar } from 'tough-cookie'
import { srpSequence } from '@hfs/shared/srp'
import { srpClientSequence } from '../src/srp'
import { createReadStream, rmSync } from 'fs'
import { dirname, join } from 'path'
import _ from 'lodash'
Expand Down Expand Up @@ -129,7 +129,7 @@ describe('after-login', () => {
})

function login(usr: string, pwd=password) {
return srpSequence(usr, pwd, (cmd: string, params: any) =>
return srpClientSequence(usr, pwd, (cmd: string, params: any) =>
reqApi(cmd, params, (x,res)=> !res.isAxiosError)())
}

Expand Down

0 comments on commit 59acc36

Please sign in to comment.