From 8ead7386ca7c4167162e176943534cb3ec864aaf Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Wed, 7 May 2025 08:27:42 +0200 Subject: [PATCH 1/5] update: ReadMe --- README.md | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 91f6037..4cc0e40 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ Dieses Projekt, `ff-admin-server`, ist das Backend zur Verwaltung von Mitglieder Eine Demo zusammen mit der `ff-admin` finden Sie unter [https://admin-demo.ff-admin.de](https://admin-demo.ff-admin.de). +Das Handbuch zur Anwendung finden sie unter [https://ff-admin.de/ff-admin-handbook](https://ff-admin.de/ff-admin-handbook). + ## Installation Das Image exposed nur den Port 5000. Die Env-Variable SERVER_PORT kann nur im lokal ausführenden dev-Kontext verwendet werden. @@ -25,26 +27,13 @@ services: container_name: ff_member_administration_server restart: unless-stopped environment: - - DB_TYPE= # default ist auf mysql gesetzt + - DB_TYPE= # default ist auf mysql gesetzt - DB_HOST=ff-db - DB_PORT= # default ist auf 3306 gesetzt - DB_NAME=ffadmin - DB_USERNAME=administration_backend - DB_PASSWORD= - - JWT_SECRET= - - JWT_EXPIRATION= # default ist auf 15m gesetzt - - REFRESH_EXPIRATION= # default ist auf 1d gesetzt - - PWA_REFRESH_EXPIRATION= # default ist auf 5d gesetzt - - MAIL_USERNAME= - - MAIL_PASSWORD= - - MAIL_HOST= - - MAIL_PORT= # default ist auf 587 gesetzt - - MAIL_SECURE= # default ist auf false gesetzt - - CLUB_NAME= # default ist auf FF Admin gesetzt - - CLUB_WEBSITE= - - BACKUP_INTERVAL= # alle x Tage, sonst keine - - BACKUP_COPIES= # Anzahl parallel bestehender Backups - - BACKUP_AUTO_RESTORE= # default ist auf true gesetzt + - APPLICATION_SECRET= - USE_SECURITY_STRICT_LIMIT = (true|false) # default ist true - SECURITY_STRICT_LIMIT_WINDOW = [0-9]*(y|d|h|m|s) # default ist 15 - SECURITY_STRICT_LIMIT_REQUEST_COUNT = strict_request_count # default ist 15 @@ -91,8 +80,6 @@ networks: Die Verwendung von postgres wird aufgrund des Verhaltens bei Datenbank-Update-Fehlern empfohlen. -Die Verwendung von SQLite wird nur für die Entwicklung oder lokale Tests empfohlen. - Führen Sie dann den folgenden Befehl im Verzeichnis der compose-Datei aus, um den Container zu starten: ```sh -- 2.45.3 From 9dd7686b67b924f0d394afab40fc089d9d617af6 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Wed, 7 May 2025 08:27:48 +0200 Subject: [PATCH 2/5] change: request method for account credential change --- src/routes/user.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/routes/user.ts b/src/routes/user.ts index 90ba489..68b6b7c 100644 --- a/src/routes/user.ts +++ b/src/routes/user.ts @@ -34,15 +34,15 @@ router.post("/verify", async (req, res) => { await verifyMyTotp(req, res); }); -router.post("/changepw", async (req, res) => { +router.patch("/changepw", async (req, res) => { await changeMyPassword(req, res); }); -router.post("/changeToTOTP", async (req, res) => { +router.patch("/changeToTOTP", async (req, res) => { await changeToTOTP(req, res); }); -router.post("/changeToPW", async (req, res) => { +router.patch("/changeToPW", async (req, res) => { await changeToPW(req, res); }); -- 2.45.3 From 56484020d89aff9148ff4b335d99d4f1b425c50e Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Wed, 7 May 2025 09:05:36 +0200 Subject: [PATCH 3/5] enhance: permission handling --- src/controller/authController.ts | 2 -- src/helpers/permissionHelper.ts | 37 ++++++++----------- src/routes/admin/index.ts | 62 ++++++++++++++++---------------- src/type/permissionTypes.ts | 4 ++- 4 files changed, 49 insertions(+), 56 deletions(-) diff --git a/src/controller/authController.ts b/src/controller/authController.ts index 6302a3c..ce674ab 100644 --- a/src/controller/authController.ts +++ b/src/controller/authController.ts @@ -41,8 +41,6 @@ export async function login(req: Request, res: Response): Promise { let { id } = await UserService.getByUsername(username); let { secret, routine } = await UserService.getUserSecretAndRoutine(id); - console.log(secret, passedSecret); - let valid = false; if (routine == LoginRoutineEnum.totp) { valid = speakeasy.totp.verify({ diff --git a/src/helpers/permissionHelper.ts b/src/helpers/permissionHelper.ts index 6a83f92..fd66663 100644 --- a/src/helpers/permissionHelper.ts +++ b/src/helpers/permissionHelper.ts @@ -17,33 +17,30 @@ export default class PermissionHelper { permissions: PermissionObject, type: PermissionType | "admin", section: PermissionSection, - module?: PermissionModule + module: PermissionModule ) { if (type == "admin") return permissions?.admin ?? permissions?.adminByOwner ?? false; if (permissions?.admin || permissions?.adminByOwner) return true; if ( - (!module && - permissions[section] != undefined && - (permissions[section]?.all == "*" || permissions[section]?.all?.includes(type))) || permissions[section]?.all == "*" || - permissions[section]?.all?.includes(type) + permissions[section]?.all?.includes(type) || + permissions[section]?.[module] == "*" || + permissions[section]?.[module]?.includes(type) ) return true; - if (module && (permissions[section]?.[module] == "*" || permissions[section]?.[module]?.includes(type))) - return true; return false; } static canSome( permissions: PermissionObject, checks: Array<{ - requiredPermissions: PermissionType | "admin"; + requiredPermission: PermissionType | "admin"; section: PermissionSection; - module?: PermissionModule; + module: PermissionModule; }> ) { checks.reduce((prev, curr) => { - return prev || this.can(permissions, curr.requiredPermissions, curr.section, curr.module); + return prev || this.can(permissions, curr.requiredPermission, curr.section, curr.module); }, false); } @@ -66,12 +63,12 @@ export default class PermissionHelper { static canSomeSection( permissions: PermissionObject, checks: Array<{ - requiredPermissions: PermissionType | "admin"; + requiredPermission: PermissionType | "admin"; section: PermissionSection; }> ): boolean { return checks.reduce((prev, curr) => { - return prev || this.can(permissions, curr.requiredPermissions, curr.section); + return prev || this.canSection(permissions, curr.requiredPermission, curr.section); }, false); } @@ -83,7 +80,7 @@ export default class PermissionHelper { static passCheckMiddleware( requiredPermissions: PermissionType | "admin", section: PermissionSection, - module?: PermissionModule + module: PermissionModule ): (req: Request, res: Response, next: Function) => void { return (req: Request, res: Response, next: Function) => { const permissions = req.permissions; @@ -99,9 +96,9 @@ export default class PermissionHelper { static passCheckSomeMiddleware( checks: Array<{ - requiredPermissions: PermissionType | "admin"; + requiredPermission: PermissionType | "admin"; section: PermissionSection; - module?: PermissionModule; + module: PermissionModule; }> ): (req: Request, res: Response, next: Function) => void { return (req: Request, res: Response, next: Function) => { @@ -111,9 +108,7 @@ export default class PermissionHelper { if (isOwner || this.canSome(permissions, checks)) { next(); } else { - let permissionsToPass = checks.reduce((prev, curr) => { - return prev + (prev != " or " ? "" : "") + `${curr.section}.${curr.module}.${curr.requiredPermissions}`; - }, ""); + let permissionsToPass = checks.map((c) => `${c.section}.${c.module}.${c.requiredPermission}`).join(" or "); throw new ForbiddenRequestException(`missing permission for ${permissionsToPass}`); } }; @@ -136,7 +131,7 @@ export default class PermissionHelper { } static sectionPassCheckSomeMiddleware( - checks: Array<{ requiredPermissions: PermissionType | "admin"; section: PermissionSection }> + checks: Array<{ requiredPermission: PermissionType | "admin"; section: PermissionSection }> ): (req: Request, res: Response, next: Function) => void { return (req: Request, res: Response, next: Function) => { const permissions = req.permissions; @@ -145,9 +140,7 @@ export default class PermissionHelper { if (isOwner || this.canSomeSection(permissions, checks)) { next(); } else { - let permissionsToPass = checks.reduce((prev, curr) => { - return prev + (prev != " or " ? "" : "") + `${curr.section}.${curr.requiredPermissions}`; - }, ""); + let permissionsToPass = checks.map((c) => `${c.section}.${c.requiredPermission}`).join(" or "); throw new ForbiddenRequestException(`missing permission for ${permissionsToPass}`); } }; diff --git a/src/routes/admin/index.ts b/src/routes/admin/index.ts index 5c3444b..4e74012 100644 --- a/src/routes/admin/index.ts +++ b/src/routes/admin/index.ts @@ -33,64 +33,64 @@ var router = express.Router({ mergeParams: true }); router.use( "/award", PermissionHelper.passCheckSomeMiddleware([ - { requiredPermissions: "read", section: "configuration", module: "award" }, - { requiredPermissions: "read", section: "club", module: "member" }, + { requiredPermission: "read", section: "configuration", module: "award" }, + { requiredPermission: "read", section: "club", module: "member" }, ]), award ); router.use( "/communicationtype", PermissionHelper.passCheckSomeMiddleware([ - { requiredPermissions: "read", section: "configuration", module: "communication_type" }, - { requiredPermissions: "read", section: "club", module: "member" }, + { requiredPermission: "read", section: "configuration", module: "communication_type" }, + { requiredPermission: "read", section: "club", module: "member" }, ]), communicationType ); router.use( "/executiveposition", PermissionHelper.passCheckSomeMiddleware([ - { requiredPermissions: "read", section: "configuration", module: "executive_position" }, - { requiredPermissions: "read", section: "club", module: "member" }, + { requiredPermission: "read", section: "configuration", module: "executive_position" }, + { requiredPermission: "read", section: "club", module: "member" }, ]), executivePosition ); router.use( "/membershipstatus", PermissionHelper.passCheckSomeMiddleware([ - { requiredPermissions: "read", section: "configuration", module: "membership_status" }, - { requiredPermissions: "read", section: "club", module: "member" }, + { requiredPermission: "read", section: "configuration", module: "membership_status" }, + { requiredPermission: "read", section: "club", module: "member" }, ]), membershipStatus ); router.use( "/qualification", PermissionHelper.passCheckSomeMiddleware([ - { requiredPermissions: "read", section: "configuration", module: "qualification" }, - { requiredPermissions: "read", section: "club", module: "member" }, + { requiredPermission: "read", section: "configuration", module: "qualification" }, + { requiredPermission: "read", section: "club", module: "member" }, ]), qualification ); router.use( "/salutation", PermissionHelper.passCheckSomeMiddleware([ - { requiredPermissions: "read", section: "configuration", module: "salutation" }, - { requiredPermissions: "read", section: "club", module: "member" }, + { requiredPermission: "read", section: "configuration", module: "salutation" }, + { requiredPermission: "read", section: "club", module: "member" }, ]), salutation ); router.use( "/calendartype", PermissionHelper.passCheckSomeMiddleware([ - { requiredPermissions: "read", section: "configuration", module: "calendar_type" }, - { requiredPermissions: "read", section: "club", module: "calendar" }, + { requiredPermission: "read", section: "configuration", module: "calendar_type" }, + { requiredPermission: "read", section: "club", module: "calendar" }, ]), calendarType ); router.use( "/querystore", PermissionHelper.passCheckSomeMiddleware([ - { requiredPermissions: "read", section: "configuration", module: "query_store" }, - { requiredPermissions: "read", section: "club", module: "listprint" }, + { requiredPermission: "read", section: "configuration", module: "query_store" }, + { requiredPermission: "read", section: "club", module: "listprint" }, ]), queryStore ); @@ -98,16 +98,16 @@ router.use("/template", PermissionHelper.passCheckMiddleware("read", "configurat router.use( "/templateusage", PermissionHelper.passCheckSomeMiddleware([ - { requiredPermissions: "read", section: "configuration", module: "template_usage" }, - { requiredPermissions: "read", section: "configuration", module: "template" }, + { requiredPermission: "read", section: "configuration", module: "template_usage" }, + { requiredPermission: "read", section: "configuration", module: "template" }, ]), templateUsage ); router.use( "/newsletterconfig", PermissionHelper.passCheckSomeMiddleware([ - { requiredPermissions: "read", section: "configuration", module: "newsletter_config" }, - { requiredPermissions: "read", section: "configuration", module: "communication_type" }, + { requiredPermission: "read", section: "configuration", module: "newsletter_config" }, + { requiredPermission: "read", section: "configuration", module: "communication_type" }, ]), newsletterConfig ); @@ -116,8 +116,8 @@ router.use("/member", PermissionHelper.passCheckMiddleware("read", "club", "memb router.use( "/protocol", PermissionHelper.passCheckSomeMiddleware([ - { requiredPermissions: "read", section: "club", module: "protocol" }, - { requiredPermissions: "read", section: "club", module: "member" }, + { requiredPermission: "read", section: "club", module: "protocol" }, + { requiredPermission: "read", section: "club", module: "member" }, ]), protocol ); @@ -125,19 +125,19 @@ router.use("/calendar", PermissionHelper.passCheckMiddleware("read", "club", "ca router.use( "/querybuilder", PermissionHelper.passCheckSomeMiddleware([ - { requiredPermissions: "read", section: "club", module: "query" }, - { requiredPermissions: "read", section: "configuration", module: "query_store" }, + { requiredPermission: "read", section: "club", module: "query" }, + { requiredPermission: "read", section: "configuration", module: "query_store" }, ]), queryBuilder ); router.use( "/newsletter", PermissionHelper.passCheckSomeMiddleware([ - { requiredPermissions: "read", section: "club", module: "newsletter" }, - { requiredPermissions: "read", section: "club", module: "member" }, - { requiredPermissions: "read", section: "club", module: "calendar" }, - { requiredPermissions: "read", section: "club", module: "query" }, - { requiredPermissions: "read", section: "configuration", module: "query_store" }, + { requiredPermission: "read", section: "club", module: "newsletter" }, + { requiredPermission: "read", section: "club", module: "member" }, + { requiredPermission: "read", section: "club", module: "calendar" }, + { requiredPermission: "read", section: "club", module: "query" }, + { requiredPermission: "read", section: "configuration", module: "query_store" }, ]), newsletter ); @@ -147,8 +147,8 @@ router.use("/role", PermissionHelper.passCheckMiddleware("read", "management", " router.use( "/user", PermissionHelper.passCheckSomeMiddleware([ - { requiredPermissions: "read", section: "management", module: "user" }, - { requiredPermissions: "read", section: "management", module: "role" }, + { requiredPermission: "read", section: "management", module: "user" }, + { requiredPermission: "read", section: "management", module: "role" }, ]), user ); diff --git a/src/type/permissionTypes.ts b/src/type/permissionTypes.ts index c3babac..eff7fd1 100644 --- a/src/type/permissionTypes.ts +++ b/src/type/permissionTypes.ts @@ -98,5 +98,7 @@ export const sectionsAndModules: SectionsAndModulesObject = { "newsletter_config", ], management: ["user", "role", "webapi", "backup", "setting"], - additional: [], + additional: [ + //{ key: "val", name: "name", type: "number", emptyIfAdmin: true }, + ], }; -- 2.45.3 From 69b447a2d8c7a3150439334c6befbcff72452bec Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Wed, 7 May 2025 09:05:55 +0200 Subject: [PATCH 4/5] fix: check of some permission returned always false --- src/helpers/permissionHelper.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/helpers/permissionHelper.ts b/src/helpers/permissionHelper.ts index fd66663..c6476e4 100644 --- a/src/helpers/permissionHelper.ts +++ b/src/helpers/permissionHelper.ts @@ -28,6 +28,7 @@ export default class PermissionHelper { permissions[section]?.[module]?.includes(type) ) return true; + return false; } @@ -39,7 +40,7 @@ export default class PermissionHelper { module: PermissionModule; }> ) { - checks.reduce((prev, curr) => { + return checks.reduce((prev, curr) => { return prev || this.can(permissions, curr.requiredPermission, curr.section, curr.module); }, false); } -- 2.45.3 From 9ba477a9b3aa3f7ddbc1d6d5cb91194f2738466b Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Wed, 7 May 2025 09:22:59 +0200 Subject: [PATCH 5/5] update: migration --- src/data-source.ts | 4 ++-- src/helpers/codingHelper.ts | 4 ++-- ...08-settingsFromEnv.ts => 1745059495807-settingsFromEnv.ts} | 4 ++-- src/migrations/1745059495808-settingsFromEnv_set.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) rename src/migrations/{1745059495808-settingsFromEnv.ts => 1745059495807-settingsFromEnv.ts} (82%) diff --git a/src/data-source.ts b/src/data-source.ts index fb60d49..63de0f8 100644 --- a/src/data-source.ts +++ b/src/data-source.ts @@ -52,7 +52,7 @@ import { TemplatesAndProtocolSort1742549956787 } from "./migrations/174254995678 import { QueryToUUID1742922178643 } from "./migrations/1742922178643-queryToUUID"; import { NewsletterColumnType1744351418751 } from "./migrations/1744351418751-newsletterColumnType"; import { QueryUpdatedAt1744795756230 } from "./migrations/1744795756230-QueryUpdatedAt"; -import { SettingsFromEnv1745059495808 } from "./migrations/1745059495808-settingsFromEnv"; +import { SettingsFromEnv1745059495807 } from "./migrations/1745059495807-settingsFromEnv"; import { MemberCreatedAt1746006549262 } from "./migrations/1746006549262-memberCreatedAt"; import { UserLoginRoutine1746252454922 } from "./migrations/1746252454922-UserLoginRoutine"; import { SettingsFromEnv_SET1745059495808 } from "./migrations/1745059495808-settingsFromEnv_set"; @@ -119,7 +119,7 @@ const dataSource = new DataSource({ QueryToUUID1742922178643, NewsletterColumnType1744351418751, QueryUpdatedAt1744795756230, - SettingsFromEnv1745059495808, + SettingsFromEnv1745059495807, SettingsFromEnv_SET1745059495808, MemberCreatedAt1746006549262, UserLoginRoutine1746252454922, diff --git a/src/helpers/codingHelper.ts b/src/helpers/codingHelper.ts index 354abc8..fee835a 100644 --- a/src/helpers/codingHelper.ts +++ b/src/helpers/codingHelper.ts @@ -13,7 +13,7 @@ export abstract class CodingHelper { try { return CodingHelper.decrypt(key, val, true); } catch (error) { - console.error("Decryption error:", error); + console.error("Decryption error in database-read - can be ignored"); if (fallback == "") return val; else return fallback; } @@ -25,7 +25,7 @@ export abstract class CodingHelper { try { return CodingHelper.encrypt(key, valueToEncrypt, true); } catch (error) { - console.error("Encryption error:", error); + console.error("Encryption error in database-read - can be ignored"); if (fallback == "") return val; return ""; } diff --git a/src/migrations/1745059495808-settingsFromEnv.ts b/src/migrations/1745059495807-settingsFromEnv.ts similarity index 82% rename from src/migrations/1745059495808-settingsFromEnv.ts rename to src/migrations/1745059495807-settingsFromEnv.ts index 1faee5e..1af7e08 100644 --- a/src/migrations/1745059495808-settingsFromEnv.ts +++ b/src/migrations/1745059495807-settingsFromEnv.ts @@ -3,8 +3,8 @@ import { setting_table } from "./baseSchemaTables/admin"; import SettingHelper from "../helpers/settingsHelper"; import ms from "ms"; -export class SettingsFromEnv1745059495808 implements MigrationInterface { - name = "SettingsFromEnv1745059495808"; +export class SettingsFromEnv1745059495807 implements MigrationInterface { + name = "SettingsFromEnv1745059495807"; public async up(queryRunner: QueryRunner): Promise { await queryRunner.createTable(setting_table, true, true, true); diff --git a/src/migrations/1745059495808-settingsFromEnv_set.ts b/src/migrations/1745059495808-settingsFromEnv_set.ts index c99c7b0..3a5729b 100644 --- a/src/migrations/1745059495808-settingsFromEnv_set.ts +++ b/src/migrations/1745059495808-settingsFromEnv_set.ts @@ -20,7 +20,7 @@ export class SettingsFromEnv_SET1745059495808 implements MigrationInterface { await SettingHelper.setSetting("mail.password", process.env.MAIL_PASSWORD); await SettingHelper.setSetting("mail.host", process.env.MAIL_HOST); await SettingHelper.setSetting("mail.port", Number(process.env.MAIL_PORT ?? "578")); - await SettingHelper.setSetting("mail.secure", Boolean(process.env.MAIL_SECURE ?? "false")); + await SettingHelper.setSetting("mail.secure", process.env.MAIL_SECURE == "true"); await SettingHelper.setSetting("backup.interval", Number(process.env.BACKUP_INTERVAL ?? "1")); await SettingHelper.setSetting("backup.copies", Number(process.env.BACKUP_COPIES ?? "7")); } -- 2.45.3