chore: Unify term types
This commit is contained in:
@@ -7,8 +7,8 @@ import http from './http';
|
|||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
|
|
||||||
const getTerms = (type, language, headers) =>
|
const getTerms = (language, headers) =>
|
||||||
http.get(`/terms/${type}${language ? `?language=${language}` : ''}`, undefined, headers);
|
http.get(`/terms${language ? `?language=${language}` : ''}`, undefined, headers);
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getTerms,
|
getTerms,
|
||||||
|
|||||||
@@ -4,19 +4,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Loader, Tab } from 'semantic-ui-react';
|
import { Loader, Tab } from 'semantic-ui-react';
|
||||||
|
|
||||||
import selectors from '../../../selectors';
|
|
||||||
import api from '../../../api';
|
import api from '../../../api';
|
||||||
import Markdown from '../Markdown';
|
import Markdown from '../Markdown';
|
||||||
|
|
||||||
import styles from './TermsPane.module.scss';
|
import styles from './TermsPane.module.scss';
|
||||||
|
|
||||||
const TermsPane = React.memo(() => {
|
const TermsPane = React.memo(() => {
|
||||||
const type = useSelector((state) => selectors.selectCurrentUser(state).termsType);
|
|
||||||
|
|
||||||
const { i18n } = useTranslation();
|
const { i18n } = useTranslation();
|
||||||
const [content, setContent] = useState(null);
|
const [content, setContent] = useState(null);
|
||||||
|
|
||||||
@@ -24,7 +20,7 @@ const TermsPane = React.memo(() => {
|
|||||||
async function fetchTerms() {
|
async function fetchTerms() {
|
||||||
let terms;
|
let terms;
|
||||||
try {
|
try {
|
||||||
({ item: terms } = await api.getTerms(type, i18n.resolvedLanguage));
|
({ item: terms } = await api.getTerms(i18n.resolvedLanguage));
|
||||||
} catch {
|
} catch {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -33,7 +29,7 @@ const TermsPane = React.memo(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fetchTerms();
|
fetchTerms();
|
||||||
}, [type, i18n.resolvedLanguage]);
|
}, [i18n.resolvedLanguage]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tab.Pane attached={false} className={styles.wrapper}>
|
<Tab.Pane attached={false} className={styles.wrapper}>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export function* authenticate(data) {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
let terms;
|
let terms;
|
||||||
if (error.step === AccessTokenSteps.ACCEPT_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));
|
yield put(actions.authenticate.failure(error, terms));
|
||||||
@@ -129,7 +129,7 @@ export function* authenticateWithOidcCallback() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
let terms;
|
let terms;
|
||||||
if (error.step === AccessTokenSteps.ACCEPT_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));
|
yield put(actions.authenticateWithOidc.failure(error, terms));
|
||||||
@@ -185,15 +185,9 @@ export function* cancelTerms() {
|
|||||||
export function* updateTermsLanguage(value) {
|
export function* updateTermsLanguage(value) {
|
||||||
yield put(actions.updateTermsLanguage(value));
|
yield put(actions.updateTermsLanguage(value));
|
||||||
|
|
||||||
const {
|
|
||||||
termsForm: {
|
|
||||||
payload: { type },
|
|
||||||
},
|
|
||||||
} = yield select(selectors.selectAuthenticateForm);
|
|
||||||
|
|
||||||
let terms;
|
let terms;
|
||||||
try {
|
try {
|
||||||
({ item: terms } = yield call(api.getTerms, type, value));
|
({ item: terms } = yield call(api.getTerms, value));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
yield put(actions.updateTermsLanguage.failure(error));
|
yield put(actions.updateTermsLanguage.failure(error));
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
* type: string
|
* type: string
|
||||||
* minLength: 64
|
* minLength: 64
|
||||||
* maxLength: 64
|
* maxLength: 64
|
||||||
* description: Terms signature hash based on user role
|
* description: Terms signature hash
|
||||||
* example: 940226c4c41f51afe3980ceb63704e752636526f4c52a4ea579e85b247493d94
|
* example: 940226c4c41f51afe3980ceb63704e752636526f4c52a4ea579e85b247493d94
|
||||||
* initialLanguage:
|
* initialLanguage:
|
||||||
* type: string
|
* type: string
|
||||||
@@ -184,14 +184,12 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!user.termsSignature) {
|
if (!user.termsSignature) {
|
||||||
const termsSignature = sails.hooks.terms.getSignatureByUserRole(user.role);
|
if (!sails.hooks.terms.isSignatureValid(inputs.signature)) {
|
||||||
|
|
||||||
if (inputs.signature !== termsSignature) {
|
|
||||||
throw Errors.INVALID_SIGNATURE;
|
throw Errors.INVALID_SIGNATURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
const values = {
|
const values = {
|
||||||
termsSignature,
|
termsSignature: inputs.signature,
|
||||||
termsAcceptedAt: new Date().toISOString(),
|
termsAcceptedAt: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /terms/{type}:
|
* /terms:
|
||||||
* get:
|
* get:
|
||||||
* summary: Get terms and conditions
|
* summary: Get terms and conditions
|
||||||
* description: Retrieves terms and conditions in the specified language.
|
* description: Retrieves terms and conditions in the specified language.
|
||||||
@@ -13,14 +13,6 @@
|
|||||||
* - Terms
|
* - Terms
|
||||||
* operationId: getTerms
|
* operationId: getTerms
|
||||||
* parameters:
|
* parameters:
|
||||||
* - name: type
|
|
||||||
* in: path
|
|
||||||
* required: true
|
|
||||||
* description: Type of terms to retrieve
|
|
||||||
* schema:
|
|
||||||
* type: string
|
|
||||||
* enum: [general, extended]
|
|
||||||
* example: general
|
|
||||||
* - name: language
|
* - name: language
|
||||||
* in: query
|
* in: query
|
||||||
* required: false
|
* required: false
|
||||||
@@ -47,11 +39,6 @@
|
|||||||
* - content
|
* - content
|
||||||
* - signature
|
* - signature
|
||||||
* properties:
|
* properties:
|
||||||
* type:
|
|
||||||
* type: string
|
|
||||||
* enum: [general, extended]
|
|
||||||
* description: Type of terms
|
|
||||||
* example: general
|
|
||||||
* language:
|
* language:
|
||||||
* type: string
|
* type: string
|
||||||
* enum: [de-DE, en-US]
|
* enum: [de-DE, en-US]
|
||||||
@@ -76,11 +63,6 @@
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
inputs: {
|
inputs: {
|
||||||
type: {
|
|
||||||
type: 'string',
|
|
||||||
isIn: Object.values(sails.hooks.terms.Types),
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
language: {
|
language: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
isIn: User.LANGUAGES,
|
isIn: User.LANGUAGES,
|
||||||
@@ -88,7 +70,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async fn(inputs) {
|
async fn(inputs) {
|
||||||
const terms = await sails.hooks.terms.getPayload(inputs.type, inputs.language);
|
const terms = await sails.hooks.terms.getPayload(inputs.language);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
item: terms,
|
item: terms,
|
||||||
|
|||||||
@@ -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 } =
|
const { token: pendingToken, payload: pendingTokenPayload } =
|
||||||
sails.helpers.utils.createJwtToken(
|
sails.helpers.utils.createJwtToken(
|
||||||
AccessTokenSteps.ACCEPT_TERMS,
|
AccessTokenSteps.ACCEPT_TERMS,
|
||||||
@@ -82,12 +82,9 @@ module.exports = {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const termsType = sails.hooks.terms.getTypeByUserRole(inputs.user.role);
|
|
||||||
|
|
||||||
throw {
|
throw {
|
||||||
termsAcceptanceRequired: {
|
termsAcceptanceRequired: {
|
||||||
pendingToken,
|
pendingToken,
|
||||||
termsType,
|
|
||||||
message: 'Terms acceptance required',
|
message: 'Terms acceptance required',
|
||||||
step: AccessTokenSteps.ACCEPT_TERMS,
|
step: AccessTokenSteps.ACCEPT_TERMS,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
language: inputs.record.language || sails.config.i18n.defaultLocale,
|
language: inputs.record.language || sails.config.i18n.defaultLocale,
|
||||||
termsType: sails.hooks.terms.getTypeByUserRole(inputs.record.role),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const gravatarUrl = sails.helpers.users.buildGravatarUrl(inputs.record);
|
const gravatarUrl = sails.helpers.users.buildGravatarUrl(inputs.record);
|
||||||
|
|||||||
@@ -14,22 +14,18 @@
|
|||||||
const fsPromises = require('fs').promises;
|
const fsPromises = require('fs').promises;
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
|
|
||||||
const Types = {
|
|
||||||
GENERAL: 'general',
|
|
||||||
EXTENDED: 'extended',
|
|
||||||
};
|
|
||||||
|
|
||||||
const LANGUAGES = ['de-DE', 'en-US'];
|
const LANGUAGES = ['de-DE', 'en-US'];
|
||||||
const DEFAULT_LANGUAGE = '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');
|
const hashContent = (content) => crypto.createHash('sha256').update(content).digest('hex');
|
||||||
|
|
||||||
module.exports = function defineTermsHook(sails) {
|
module.exports = function defineTermsHook(sails) {
|
||||||
let signatureByType;
|
let signature;
|
||||||
let signaturesSet;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
Types,
|
|
||||||
LANGUAGES,
|
LANGUAGES,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,49 +35,26 @@ module.exports = function defineTermsHook(sails) {
|
|||||||
async initialize() {
|
async initialize() {
|
||||||
sails.log.info('Initializing custom hook (`terms`)');
|
sails.log.info('Initializing custom hook (`terms`)');
|
||||||
|
|
||||||
signatureByType = {
|
const content = await getContent();
|
||||||
[Types.GENERAL]: hashContent(await this.getContent(Types.GENERAL)),
|
signature = hashContent(content);
|
||||||
[Types.EXTENDED]: hashContent(await this.getContent(Types.EXTENDED)),
|
|
||||||
};
|
|
||||||
|
|
||||||
signaturesSet = new Set(Object.values(signatureByType));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async getPayload(type, language = DEFAULT_LANGUAGE) {
|
async getPayload(language = DEFAULT_LANGUAGE) {
|
||||||
if (!Object.values(Types).includes(type)) {
|
|
||||||
throw new Error(`Unknown type: ${type}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!LANGUAGES.includes(language)) {
|
if (!LANGUAGES.includes(language)) {
|
||||||
language = DEFAULT_LANGUAGE; // eslint-disable-line no-param-reassign
|
language = DEFAULT_LANGUAGE; // eslint-disable-line no-param-reassign
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const content = await getContent(language);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type,
|
|
||||||
language,
|
language,
|
||||||
content: await this.getContent(type, language),
|
content,
|
||||||
signature: this.getSignatureByType(type),
|
signature,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getTypeByUserRole(userRole) {
|
isSignatureValid(value) {
|
||||||
return userRole === User.Roles.ADMIN ? Types.EXTENDED : Types.GENERAL;
|
return value === signature;
|
||||||
},
|
|
||||||
|
|
||||||
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);
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
* - avatar
|
* - avatar
|
||||||
* - phone
|
* - phone
|
||||||
* - organization
|
* - organization
|
||||||
* - termsType
|
|
||||||
* - isDeactivated
|
* - isDeactivated
|
||||||
* - createdAt
|
* - createdAt
|
||||||
* - updatedAt
|
* - updatedAt
|
||||||
@@ -143,11 +142,6 @@
|
|||||||
* default: byDefault
|
* default: byDefault
|
||||||
* description: Default sort order for projects display (personal field)
|
* description: Default sort order for projects display (personal field)
|
||||||
* example: byDefault
|
* example: byDefault
|
||||||
* termsType:
|
|
||||||
* type: string
|
|
||||||
* enum: [general, extended]
|
|
||||||
* description: Type of terms applicable to the user based on role
|
|
||||||
* example: general
|
|
||||||
* isSsoUser:
|
* isSsoUser:
|
||||||
* type: boolean
|
* type: boolean
|
||||||
* default: false
|
* default: false
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ const protectedStaticDirServer = (prefix, getPathSegment) => (req, res, next) =>
|
|||||||
module.exports.routes = {
|
module.exports.routes = {
|
||||||
'GET /api/bootstrap': 'bootstrap/show',
|
'GET /api/bootstrap': 'bootstrap/show',
|
||||||
|
|
||||||
'GET /api/terms/:type': 'terms/show',
|
'GET /api/terms': 'terms/show',
|
||||||
|
|
||||||
'GET /api/config': 'config/show',
|
'GET /api/config': 'config/show',
|
||||||
'PATCH /api/config': 'config/update',
|
'PATCH /api/config': 'config/update',
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Beispiel-Nutzungsbedingungen - Allgemeine Benutzer
|
# Beispiel-Nutzungsbedingungen
|
||||||
_Nicht rechtsverbindlich. Dies ist ein Platzhaltertext nur zu Testzwecken._
|
_Nicht rechtsverbindlich. Dies ist ein Platzhaltertext nur zu Testzwecken._
|
||||||
|
|
||||||
_Letzte Aktualisierung: 14. August 2025_
|
_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**
|
||||||
@@ -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**
|
|
||||||
@@ -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._
|
_Not legally binding. This is placeholder text for testing purposes only._
|
||||||
|
|
||||||
_Last updated: August 14, 2025_
|
_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**
|
||||||
@@ -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**
|
|
||||||
Reference in New Issue
Block a user