diff --git a/client/src/api/terms.js b/client/src/api/terms.js index f0908624..2c5e7e07 100644 --- a/client/src/api/terms.js +++ b/client/src/api/terms.js @@ -7,8 +7,8 @@ import http from './http'; /* Actions */ -const getTerms = (type, language, headers) => - http.get(`/terms/${type}${language ? `?language=${language}` : ''}`, undefined, headers); +const getTerms = (language, headers) => + http.get(`/terms${language ? `?language=${language}` : ''}`, undefined, headers); export default { getTerms, diff --git a/client/src/components/common/AboutModal/TermsPane.jsx b/client/src/components/common/AboutModal/TermsPane.jsx index 32bbfaa2..c57e240d 100644 --- a/client/src/components/common/AboutModal/TermsPane.jsx +++ b/client/src/components/common/AboutModal/TermsPane.jsx @@ -4,19 +4,15 @@ */ import React, { useEffect, useState } from 'react'; -import { useSelector } from 'react-redux'; import { useTranslation } from 'react-i18next'; import { Loader, Tab } from 'semantic-ui-react'; -import selectors from '../../../selectors'; import api from '../../../api'; import Markdown from '../Markdown'; import styles from './TermsPane.module.scss'; const TermsPane = React.memo(() => { - const type = useSelector((state) => selectors.selectCurrentUser(state).termsType); - const { i18n } = useTranslation(); const [content, setContent] = useState(null); @@ -24,7 +20,7 @@ const TermsPane = React.memo(() => { async function fetchTerms() { let terms; try { - ({ item: terms } = await api.getTerms(type, i18n.resolvedLanguage)); + ({ item: terms } = await api.getTerms(i18n.resolvedLanguage)); } catch { return; } @@ -33,7 +29,7 @@ const TermsPane = React.memo(() => { } fetchTerms(); - }, [type, i18n.resolvedLanguage]); + }, [i18n.resolvedLanguage]); return ( diff --git a/client/src/sagas/login/services/login.js b/client/src/sagas/login/services/login.js index 19864681..460b828a 100644 --- a/client/src/sagas/login/services/login.js +++ b/client/src/sagas/login/services/login.js @@ -30,7 +30,7 @@ export function* authenticate(data) { } catch (error) { let terms; if (error.step === AccessTokenSteps.ACCEPT_TERMS) { - ({ item: terms } = yield call(api.getTerms, error.termsType, i18n.resolvedLanguage)); + ({ item: terms } = yield call(api.getTerms, i18n.resolvedLanguage)); } yield put(actions.authenticate.failure(error, terms)); @@ -129,7 +129,7 @@ export function* authenticateWithOidcCallback() { } catch (error) { let terms; if (error.step === AccessTokenSteps.ACCEPT_TERMS) { - ({ item: terms } = yield call(api.getTerms, error.termsType, i18n.resolvedLanguage)); + ({ item: terms } = yield call(api.getTerms, i18n.resolvedLanguage)); } yield put(actions.authenticateWithOidc.failure(error, terms)); @@ -185,15 +185,9 @@ export function* cancelTerms() { export function* updateTermsLanguage(value) { yield put(actions.updateTermsLanguage(value)); - const { - termsForm: { - payload: { type }, - }, - } = yield select(selectors.selectAuthenticateForm); - let terms; try { - ({ item: terms } = yield call(api.getTerms, type, value)); + ({ item: terms } = yield call(api.getTerms, value)); } catch (error) { yield put(actions.updateTermsLanguage.failure(error)); return; diff --git a/server/api/controllers/access-tokens/accept-terms.js b/server/api/controllers/access-tokens/accept-terms.js index cac77311..bb010651 100644 --- a/server/api/controllers/access-tokens/accept-terms.js +++ b/server/api/controllers/access-tokens/accept-terms.js @@ -31,7 +31,7 @@ * type: string * minLength: 64 * maxLength: 64 - * description: Terms signature hash based on user role + * description: Terms signature hash * example: 940226c4c41f51afe3980ceb63704e752636526f4c52a4ea579e85b247493d94 * initialLanguage: * type: string @@ -184,14 +184,12 @@ module.exports = { } if (!user.termsSignature) { - const termsSignature = sails.hooks.terms.getSignatureByUserRole(user.role); - - if (inputs.signature !== termsSignature) { + if (!sails.hooks.terms.isSignatureValid(inputs.signature)) { throw Errors.INVALID_SIGNATURE; } const values = { - termsSignature, + termsSignature: inputs.signature, termsAcceptedAt: new Date().toISOString(), }; diff --git a/server/api/controllers/terms/show.js b/server/api/controllers/terms/show.js index 2cc4d401..7e11b041 100644 --- a/server/api/controllers/terms/show.js +++ b/server/api/controllers/terms/show.js @@ -5,7 +5,7 @@ /** * @swagger - * /terms/{type}: + * /terms: * get: * summary: Get terms and conditions * description: Retrieves terms and conditions in the specified language. @@ -13,14 +13,6 @@ * - Terms * operationId: getTerms * parameters: - * - name: type - * in: path - * required: true - * description: Type of terms to retrieve - * schema: - * type: string - * enum: [general, extended] - * example: general * - name: language * in: query * required: false @@ -47,11 +39,6 @@ * - content * - signature * properties: - * type: - * type: string - * enum: [general, extended] - * description: Type of terms - * example: general * language: * type: string * enum: [de-DE, en-US] @@ -76,11 +63,6 @@ module.exports = { inputs: { - type: { - type: 'string', - isIn: Object.values(sails.hooks.terms.Types), - required: true, - }, language: { type: 'string', isIn: User.LANGUAGES, @@ -88,7 +70,7 @@ module.exports = { }, async fn(inputs) { - const terms = await sails.hooks.terms.getPayload(inputs.type, inputs.language); + const terms = await sails.hooks.terms.getPayload(inputs.language); return { item: terms, diff --git a/server/api/helpers/access-tokens/handle-steps.js b/server/api/helpers/access-tokens/handle-steps.js index f23b8e6d..f0b1fec5 100644 --- a/server/api/helpers/access-tokens/handle-steps.js +++ b/server/api/helpers/access-tokens/handle-steps.js @@ -56,7 +56,7 @@ module.exports = { } } - if (!sails.hooks.terms.hasSignature(inputs.user.termsSignature)) { + if (!sails.hooks.terms.isSignatureValid(inputs.user.termsSignature)) { const { token: pendingToken, payload: pendingTokenPayload } = sails.helpers.utils.createJwtToken( AccessTokenSteps.ACCEPT_TERMS, @@ -82,12 +82,9 @@ module.exports = { ); } - const termsType = sails.hooks.terms.getTypeByUserRole(inputs.user.role); - throw { termsAcceptanceRequired: { pendingToken, - termsType, message: 'Terms acceptance required', step: AccessTokenSteps.ACCEPT_TERMS, }, diff --git a/server/api/helpers/users/present-one.js b/server/api/helpers/users/present-one.js index 2fbb5c91..7a7e6146 100644 --- a/server/api/helpers/users/present-one.js +++ b/server/api/helpers/users/present-one.js @@ -34,7 +34,6 @@ module.exports = { }, }, language: inputs.record.language || sails.config.i18n.defaultLocale, - termsType: sails.hooks.terms.getTypeByUserRole(inputs.record.role), }; const gravatarUrl = sails.helpers.users.buildGravatarUrl(inputs.record); diff --git a/server/api/hooks/terms/index.js b/server/api/hooks/terms/index.js index cd7eebbf..ea448c0b 100644 --- a/server/api/hooks/terms/index.js +++ b/server/api/hooks/terms/index.js @@ -14,22 +14,18 @@ const fsPromises = require('fs').promises; const crypto = require('crypto'); -const Types = { - GENERAL: 'general', - EXTENDED: 'extended', -}; - const LANGUAGES = ['de-DE', 'en-US']; const DEFAULT_LANGUAGE = 'en-US'; +const getContent = (language = DEFAULT_LANGUAGE) => + fsPromises.readFile(`${sails.config.appPath}/terms/${language}.md`, 'utf8'); + const hashContent = (content) => crypto.createHash('sha256').update(content).digest('hex'); module.exports = function defineTermsHook(sails) { - let signatureByType; - let signaturesSet; + let signature; return { - Types, LANGUAGES, /** @@ -39,49 +35,26 @@ module.exports = function defineTermsHook(sails) { async initialize() { sails.log.info('Initializing custom hook (`terms`)'); - signatureByType = { - [Types.GENERAL]: hashContent(await this.getContent(Types.GENERAL)), - [Types.EXTENDED]: hashContent(await this.getContent(Types.EXTENDED)), - }; - - signaturesSet = new Set(Object.values(signatureByType)); + const content = await getContent(); + signature = hashContent(content); }, - async getPayload(type, language = DEFAULT_LANGUAGE) { - if (!Object.values(Types).includes(type)) { - throw new Error(`Unknown type: ${type}`); - } - + async getPayload(language = DEFAULT_LANGUAGE) { if (!LANGUAGES.includes(language)) { language = DEFAULT_LANGUAGE; // eslint-disable-line no-param-reassign } + const content = await getContent(language); + return { - type, language, - content: await this.getContent(type, language), - signature: this.getSignatureByType(type), + content, + signature, }; }, - getTypeByUserRole(userRole) { - return userRole === User.Roles.ADMIN ? Types.EXTENDED : Types.GENERAL; - }, - - getContent(type, language = DEFAULT_LANGUAGE) { - return fsPromises.readFile(`${sails.config.appPath}/terms/${language}/${type}.md`, 'utf8'); - }, - - getSignatureByType(type) { - return signatureByType[type]; - }, - - getSignatureByUserRole(userRole) { - return signatureByType[this.getTypeByUserRole(userRole)]; - }, - - hasSignature(signature) { - return signaturesSet.has(signature); + isSignatureValid(value) { + return value === signature; }, }; }; diff --git a/server/api/models/User.js b/server/api/models/User.js index 7e169b36..c98e9119 100755 --- a/server/api/models/User.js +++ b/server/api/models/User.js @@ -24,7 +24,6 @@ * - avatar * - phone * - organization - * - termsType * - isDeactivated * - createdAt * - updatedAt @@ -143,11 +142,6 @@ * default: byDefault * description: Default sort order for projects display (personal field) * example: byDefault - * termsType: - * type: string - * enum: [general, extended] - * description: Type of terms applicable to the user based on role - * example: general * isSsoUser: * type: boolean * default: false diff --git a/server/config/routes.js b/server/config/routes.js index 708e4c44..c4e325db 100644 --- a/server/config/routes.js +++ b/server/config/routes.js @@ -105,7 +105,7 @@ const protectedStaticDirServer = (prefix, getPathSegment) => (req, res, next) => module.exports.routes = { 'GET /api/bootstrap': 'bootstrap/show', - 'GET /api/terms/:type': 'terms/show', + 'GET /api/terms': 'terms/show', 'GET /api/config': 'config/show', 'PATCH /api/config': 'config/update', diff --git a/server/terms/de-DE/general.md b/server/terms/de-DE.md similarity index 91% rename from server/terms/de-DE/general.md rename to server/terms/de-DE.md index 322743c7..a61f4206 100644 --- a/server/terms/de-DE/general.md +++ b/server/terms/de-DE.md @@ -1,4 +1,4 @@ -# Beispiel-Nutzungsbedingungen - Allgemeine Benutzer +# Beispiel-Nutzungsbedingungen _Nicht rechtsverbindlich. Dies ist ein Platzhaltertext nur zu Testzwecken._ _Letzte Aktualisierung: 14. August 2025_ @@ -33,4 +33,4 @@ Bei Fragen zu diesen Beispiel-Bedingungen wenden Sie sich bitte an `placeholder@ --- -**Ende des Beispiels - Allgemeine Bedingungen** +**Ende des Beispiels** diff --git a/server/terms/de-DE/extended.md b/server/terms/de-DE/extended.md deleted file mode 100644 index 7dde971a..00000000 --- a/server/terms/de-DE/extended.md +++ /dev/null @@ -1,39 +0,0 @@ -# Beispiel-Nutzungsbedingungen - Administratoren -_Nicht rechtsverbindlich. Dies ist ein Platzhaltertext nur zu Testzwecken._ - -_Letzte Aktualisierung: 14. August 2025_ - -Willkommen, Administrator! Diese Beispiel-Nutzungsbedingungen ("Bedingungen") dienen **ausschließlich der Demonstration** und sind rechtlich nicht gültig. Diese erweiterte Version enthält zusätzliche Klauseln, die höhere Verantwortlichkeiten für Administratoren widerspiegeln. - ---- - -## 1. Einführung -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sed nibh id elit ultricies posuere. - -## 2. Teilnahmeberechtigung -Administratoren müssen mindestens 21 Jahre alt und von der Organisation autorisiert sein. Nulla rhoncus diam non dictum fermentum. - -## 3. Administrative Pflichten -Als Administrator stimmen Sie zu: -- Benutzerkonten verantwortungsvoll zu verwalten. -- Sicherheitseinstellungen korrekt zu konfigurieren. -- Die Einhaltung von Datenschutzvorschriften zu gewährleisten. - -## 4. Verbotene Administrative Handlungen -Administratoren dürfen nicht: -1. Unbefugten Zugriff gewähren. -2. Prüfprotokolle ohne triftigen Grund ändern. -3. Administrative Rechte zum persönlichen Vorteil nutzen. - -## 5. Datenverwaltung -Sie sind verantwortlich für den Schutz der Benutzerdaten und die Systemintegrität. - -## 6. Änderungen -Diese Beispiel-Bedingungen können jederzeit zu Testzwecken geändert werden. - -## 7. Kontakt -Bei Fragen zu diesen Beispiel-Bedingungen wenden Sie sich bitte an `placeholder@example.com`. - ---- - -**Ende des Beispiels – Erweiterte (Admin) Bedingungen** diff --git a/server/terms/en-US/general.md b/server/terms/en-US.md similarity index 92% rename from server/terms/en-US/general.md rename to server/terms/en-US.md index db143b57..d61af4c1 100644 --- a/server/terms/en-US/general.md +++ b/server/terms/en-US.md @@ -1,4 +1,4 @@ -# Example Terms of Service - General Users +# Example Terms of Service _Not legally binding. This is placeholder text for testing purposes only._ _Last updated: August 14, 2025_ @@ -33,4 +33,4 @@ For questions about these example Terms, please contact `placeholder@example.com --- -**End of Example - General Terms** +**End of Example** diff --git a/server/terms/en-US/extended.md b/server/terms/en-US/extended.md deleted file mode 100644 index 81bff9f0..00000000 --- a/server/terms/en-US/extended.md +++ /dev/null @@ -1,39 +0,0 @@ -# Example Terms of Service - Admin Users -_Not legally binding. This is placeholder text for testing purposes only._ - -_Last updated: August 14, 2025_ - -Welcome, Admin! These Example Terms of Service ("Terms") are **for demonstration purposes only** and are not legally valid. This extended version contains additional clauses reflecting higher responsibilities for administrative users. - ---- - -## 1. Introduction -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sed nibh id elit ultricies posuere. - -## 2. Eligibility -Admins must be at least 21 years old and authorized by the organization. Nulla rhoncus diam non dictum fermentum. - -## 3. Administrative Responsibilities -As an administrator, you agree to: -- Manage user accounts responsibly. -- Configure security settings accurately. -- Ensure compliance with data protection rules. - -## 4. Prohibited Administrative Actions -Admins may not: -1. Grant unauthorized access. -2. Alter audit logs without proper reason. -3. Use administrative privileges for personal gain. - -## 5. Data Management -You are responsible for safeguarding user data and system integrity. - -## 6. Modifications -These example Terms may be updated for testing purposes without notice. - -## 7. Contact -For questions about these example Terms, please contact `placeholder@example.com`. - ---- - -**End of Example – Extended (Admin) Terms**