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 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/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/helpers/permissionHelper.ts b/src/helpers/permissionHelper.ts index 6a83f92..c6476e4 100644 --- a/src/helpers/permissionHelper.ts +++ b/src/helpers/permissionHelper.ts @@ -17,33 +17,31 @@ 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 checks.reduce((prev, curr) => { + return prev || this.can(permissions, curr.requiredPermission, curr.section, curr.module); }, false); } @@ -66,12 +64,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 +81,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 +97,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 +109,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 +132,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 +141,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/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")); } 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/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); }); 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 }, + ], };