plugins: api.ctxBelongsTo

Massimo Melina committed Dec 16, 2024 at 15:19 UTC 65efd864aa6079119392f7581b5062d8692e1afc
4 files changed +19 -12
dev-plugins.md
+4 -1
@@ -239,7 +239,9 @@ The `api` object you get as parameter of the `init` contains the following:
239
240 - `i18n(ctx: Context): Promise<{ t }>` if you need to translate messages inside http body, without the GUI, use this function
241 to instantiate translation for the language of the browser. You can then use the `t` function as documented in [dedicated section](Internationalization-i18n).
242 -
242 +
243 +- `ctxBelongsTo(ctx: Context, accounts: strings[]): boolean` check if the current username, or any group it belongs to, matches the provided accounts list.
244 +
245 - `misc` many functions and constants available in [misc.ts](https://github.com/rejetto/hfs/blob/main/src/misc.ts).
246 These are not documented, probably never will, and are subject to change without notifications,
247 but you can study the sources if you are interested in using them. It's just a shorter version of `api.require('./misc')`
@@ -708,6 +710,7 @@ If you want to override a text regardless of the language, use the special langu
710 - api.i18n
711 - frontend event: newListEntries
712 - HFS.fileShowComponents
713 + - api.ctxBelongsTo
714 - 9.6 (v0.54.0)
715 - frontend event: showPlay
716 - api.addBlock
src/perm.ts
+9 -2
@@ -1,11 +1,13 @@
1 // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3 import _ from 'lodash'
4 -import { HTTP_BAD_REQUEST, objRenameKey, objSameKeys, setHidden, typedEntries, wantArray } from './misc'
4 +import { getOrSet, HTTP_BAD_REQUEST, objRenameKey, objSameKeys, setHidden, typedEntries, wantArray } from './misc'
5 import { defineConfig, saveConfigAsap } from './config'
6 import { createVerifierAndSalt, SRPParameters, SRPRoutines } from 'tssrp6a'
7 import events from './events'
8 import { ApiError } from './apiMiddleware'
9 +import { getCurrentUsername } from './auth'
10 +import Koa from 'koa'
11
12 export interface Account {
13 // we consider all the following fields, when falsy, as equivalent to be missing. If this changes in the future, please adjust addAccount and setAccount
@@ -39,6 +41,12 @@ export function expandUsername(who: string): string[] {
41 return ret
42 }
43
44 +// check if current username or any ancestor match the provided usernames
45 +export function ctxBelongsTo(ctx: Koa.Context, usernames: string[]) {
46 + return getOrSet(ctx.state, 'usernames', () => expandUsername(getCurrentUsername(ctx))) // cache ancestors' usernames inside context state
47 + .some((u: string) => usernames.includes(u))
48 +}
49 +
50 export function getAccount(username:string, normalize=true) : Account | undefined {
51 if (normalize)
52 username = normalizeUsername(username)
@@ -198,7 +206,6 @@ export function accountCanLoginAdmin(account: Account) {
206 return accountCanLogin(account) && Boolean(getFromAccount(account, a => a.admin))
207 }
208
201 -
209 export async function changeSrpHelper(account: Account, salt: string, verifier: string) {
210 if (!salt || !verifier)
211 return new ApiError(HTTP_BAD_REQUEST, 'missing parameters')
src/plugins.ts
+2
@@ -29,6 +29,7 @@ import { app } from './index'
29 import { addBlock } from './block'
30 import { getLangData } from './lang'
31 import { i18nFromTranslations } from './i18n'
32 +import { ctxBelongsTo } from './perm'
33
34 export const PATH = 'plugins'
35 export const DISABLING_SUFFIX = '-disabled'
@@ -121,6 +122,7 @@ async function initPlugin<T>(pl: any, morePassedToInit?: T) {
122 notifyClient,
123 addBlock,
124 misc,
125 + ctxBelongsTo,
126 ...morePassedToInit
127 })
128 return Object.assign(pl, typeof res === 'function' ? { unload: res } : res)
src/vfs.ts
+4 -9
@@ -3,7 +3,7 @@
3 import fs from 'fs/promises'
4 import { basename, dirname, join, resolve } from 'path'
5 import {
6 - dirStream, getOrSet, makeMatcher, setHidden, onlyTruthy, isValidFileName, throw_, VfsPerms, Who,
6 + dirStream, makeMatcher, setHidden, onlyTruthy, isValidFileName, throw_, VfsPerms, Who,
7 isWhoObject, WHO_ANY_ACCOUNT, defaultPerms, PERM_KEYS, removeStarting, HTTP_SERVER_ERROR, try_
8 } from './misc'
9 import Koa from 'koa'
@@ -11,7 +11,7 @@ import _ from 'lodash'
11 import { defineConfig, setConfig } from './config'
12 import { HTTP_FORBIDDEN, HTTP_UNAUTHORIZED, IS_MAC, IS_WINDOWS } from './const'
13 import events from './events'
14 -import { expandUsername } from './perm'
14 +import { ctxBelongsTo } from './perm'
15 import { getCurrentUsername } from './auth'
16 import { Stats } from 'node:fs'
17 import fswin from 'fswin'
@@ -249,13 +249,8 @@ export function statusCodeForMissingPerm(node: VfsNode, perm: keyof VfsPerms, ct
249 cur = who
250 } while (1)
251
252 - if (Array.isArray(who)) {
253 - const arr = who // shut up ts
254 - // check if I or any ancestor match `who`, but cache ancestors' usernames inside context state
255 - const some = getOrSet(ctx.state, 'usernames', () => expandUsername(getCurrentUsername(ctx)))
256 - .some((u: string) => arr.includes(u))
257 - return some ? 0 : HTTP_UNAUTHORIZED
258 - }
252 + if (Array.isArray(who))
253 + return ctxBelongsTo(ctx, who) ? 0 : HTTP_UNAUTHORIZED
254 return typeof who === 'boolean' ? (who ? 0 : HTTP_FORBIDDEN)
255 : who === WHO_ANY_ACCOUNT ? (getCurrentUsername(ctx) ? 0 : HTTP_UNAUTHORIZED)
256 : throw_(Error(`invalid permission: ${perm}=${try_(() => JSON.stringify(who))}`))