Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BC-4938 - Add school specific Terms of Use (Nutzungsordnung) for new school admin page #3301

Merged
merged 9 commits into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ data:
},
'^/impressum': {
},
'^/datenschutz': {
'^/privacypolicy': {
}, */
'^/about': {
defaultSrc: 'https://www10-fms.hpi.uni-potsdam.de https://cloud-instances.s3.hidrive.strato.com https://s3.hidrive.strato.com',
Expand Down
2 changes: 1 addition & 1 deletion config/http-headers.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ const config = {
},
'^/impressum': {
},
'^/datenschutz': {
'^/privacypolicy': {
}, */
'^/about': {
defaultSrc: 'https://www10-fms.hpi.uni-potsdam.de https://cloud-instances.s3.hidrive.strato.com',
Expand Down
50 changes: 6 additions & 44 deletions controllers/dataprivacy.js
Original file line number Diff line number Diff line change
@@ -1,60 +1,22 @@
const express = require('express');
const { URL } = require('url');
const api = require('../api');
const authHelper = require('../helpers/authentication');
const { DOCUMENT_BASE_DIR, SC_THEME } = require('../config/global');
const { specificFiles } = require('../config/documents');
const { getBase64File } = require('../helpers/fileHelper');
const { getConsentVersion } = require('../helpers/consentVersionHelper');

const router = express.Router();

const privacyUrl = () => {
return new URL(`${SC_THEME}/${specificFiles.privacyExemplary}`, DOCUMENT_BASE_DIR);
};

const downloadPolicyPdf = (res, fileData, fileTitle) => {
// ERR_INVALID_CHAR will get thrown on ukrainian translation without encoding
const encodedFileTitle = encodeURI(fileTitle);
const download = Buffer.from(fileData, 'base64');
res.writeHead(200, {
'Content-Type': 'application/pdf',
'Content-Disposition': `attachment; filename="${encodedFileTitle}.pdf"`,
}).end(download);
};

const getBase64File = async (req, res, fileId, fileTitle) => {
if (fileId) {
const base64File = await api(req).get(`/base64Files/${fileId}`);
if (base64File.data) {
const fileData = base64File.data.replace(
'data:application/pdf;base64,',
'',
);
downloadPolicyPdf(res, fileData, fileTitle);
}
}
};
const privacyUrl = () => new URL(`${SC_THEME}/${specificFiles.privacyExemplary}`, DOCUMENT_BASE_DIR);

router.get('/', async (req, res, next) => {
try {
const isAuthenticated = await authHelper.isAuthenticated(req);
const qs = {
$limit: 1,
consentTypes: 'privacy',
$sort: {
publishedAt: -1,
},
};

if (isAuthenticated && res.locals.currentSchool) {
qs.schoolId = res.locals.currentSchool;
}

const consentVersions = await api(req).get('/consentVersions', { qs });
const consentVersions = await getConsentVersion(req, res, 'privacy');

if (consentVersions.data.length) {
const fileId = consentVersions.data[0].consentDataId;
if (!fileId) {
res.redirect(privacyUrl());
res.redirect(privacyUrl().toString());
}

const fileTitle = res.locals.theme.name === 'thr'
Expand All @@ -63,7 +25,7 @@ router.get('/', async (req, res, next) => {

await getBase64File(req, res, fileId, fileTitle);
} else {
res.redirect(privacyUrl());
res.redirect(privacyUrl().toString());
}
} catch (err) {
next(err);
Expand Down
11 changes: 8 additions & 3 deletions controllers/firstLogin.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,14 @@ const hasAccount = (req, res) => api(req).get('/consents', {
},
});

const getSchoolPrivacy = async (req, res) => {
const getSchoolConsentVersionByType = async (req, res, consentType) => {
if (consentType !== 'privacy' && consentType !== 'termsOfUse') {
return undefined;
}

const qs = {
schoolId: res.locals.currentUser.schoolId,
consentTypes: ['privacy'],
consentTypes: [consentType],
consentDataId: { $exists: true },
$limit: 1,
$sort: {
Expand Down Expand Up @@ -249,7 +253,8 @@ router.get('/', async (req, res, next) => {
sso: !!(res.locals.currentPayload || {}).systemId,
now: Date.now(),
sections: sections.map((name) => `firstLogin/sections/${name}`),
schoolPrivacyLink: await getSchoolPrivacy(req, res),
schoolPrivacyLink: await getSchoolConsentVersionByType(req, res, 'privacy'),
schoolTermsLink: await getSchoolConsentVersionByType(req, res, 'termsOfUse'),
schoolPrivacyName: res.$t('global.text.dataProtection'),
submitPageIndex,
userConsent,
Expand Down
3 changes: 2 additions & 1 deletion controllers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ router.use('/partner/', require('./partner'));
router.use('/community/', require('./community'));
router.use('/about/', require('./about'));
router.use('/help/', require('./help'));
router.use('/datenschutz/', require('./dataprivacy'));
router.use('/privacypolicy/', require('./dataprivacy'));
router.use('/termsofuse/', require('./termsofuse'));
router.use('/my-material', require('./my-material'));
router.use('/base64Files', require('./base64Files'));
router.use('/logs', require('./logs'));
Expand Down
28 changes: 18 additions & 10 deletions controllers/registration.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ const checkValidRegistration = async (req) => {
*/
router.get(['/register', '/register/*'], (req, res, next) => res.render('registration/deprecated_warning'));

const getSchoolPrivacy = async (req, res) => {
const getSchoolConsentVersionByType = async (req, res, consentType) => {
const importHash = getImportHash(req);

try {
const consentVersion = await api(req).get(`/registration/consent/${importHash}`);
const consentVersion = await api(req).get(`/registration/consent/${importHash}?consentType=${consentType}`);
if (consentVersion) {
const { consentDataId } = consentVersion;
return consentDataId ? `/base64Files/${consentDataId}` : undefined;
Expand Down Expand Up @@ -251,8 +251,10 @@ router.get(['/registration/:classOrSchoolId/byparent', '/registration/:classOrSc
hideMenu: true,
user,
needConsent,
schoolPrivacyLink: await getSchoolPrivacy(req, res),
schoolPrivacyName: res.$t('global.text.dataProtection'),
schoolPrivacyLink: await getSchoolConsentVersionByType(req, res, 'privacy'),
schoolTermsLink: await getSchoolConsentVersionByType(req, res, 'termsOfUse'),
schoolPrivacyName: res.$t('global.text.dataProtectionFile'),
schoolTermsName: res.$t('global.text.termsOfUseFile'),
sectionNumber,
CONSENT_WITHOUT_PARENTS_MIN_AGE_YEARS,
invalid,
Expand Down Expand Up @@ -317,8 +319,10 @@ router.get(['/registration/:classOrSchoolId/bystudent', '/registration/:classOrS
user,
needConsent,
sectionNumber,
schoolPrivacyLink: await getSchoolPrivacy(req, res),
schoolPrivacyName: res.$t('global.text.dataProtection'),
schoolPrivacyLink: await getSchoolConsentVersionByType(req, res, 'privacy'),
schoolTermsLink: await getSchoolConsentVersionByType(req, res, 'termsOfUse'),
schoolPrivacyName: res.$t('global.text.dataProtectionFile'),
schoolTermsName: res.$t('global.text.termsOfUseFile'),
CONSENT_WITHOUT_PARENTS_MIN_AGE_YEARS,
invalid,
secure,
Expand Down Expand Up @@ -393,8 +397,10 @@ router.get(['/registration/:classOrSchoolId/:byRole'], async (req, res) => {
user,
needConsent,
sectionNumber,
schoolPrivacyLink: await getSchoolPrivacy(req, res),
schoolPrivacyName: res.$t('global.text.dataProtection'),
schoolPrivacyLink: await getSchoolConsentVersionByType(req, res, 'privacy'),
schoolTermsLink: await getSchoolConsentVersionByType(req, res, 'termsOfUse'),
schoolPrivacyName: res.$t('global.text.dataProtectionFile'),
schoolTermsName: res.$t('global.text.termsOfUseFile'),
invalid,
secure,
correctID,
Expand Down Expand Up @@ -432,8 +438,10 @@ router.get(['/registration/:classOrSchoolId', '/registration/:classOrSchoolId/:s
sso: req.params.sso === 'sso',
account: req.params.accountId || '',
CONSENT_WITHOUT_PARENTS_MIN_AGE_YEARS,
schoolPrivacyLink: await getSchoolPrivacy(req, res),
schoolPrivacyName: res.$t('global.text.dataProtection'),
schoolPrivacyLink: await getSchoolConsentVersionByType(req, res, 'privacy'),
schoolTermsLink: await getSchoolConsentVersionByType(req, res, 'termsOfUse'),
schoolPrivacyName: res.$t('global.text.dataProtectionFile'),
schoolTermsName: res.$t('global.text.termsOfUseFile'),
invalid,
secure,
correctID,
Expand Down
33 changes: 33 additions & 0 deletions controllers/termsofuse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const express = require('express');
const { URL } = require('url');
const { DOCUMENT_BASE_DIR, SC_THEME } = require('../config/global');
const { specificFiles } = require('../config/documents');
const { getBase64File } = require('../helpers/fileHelper');
const { getConsentVersion } = require('../helpers/consentVersionHelper');

const router = express.Router();

const termsUrl = () => new URL(`${SC_THEME}/${specificFiles.termsOfUseSchool}`, DOCUMENT_BASE_DIR);

router.get('/', async (req, res, next) => {
try {
const consentVersions = await getConsentVersion(req, res, 'termsOfUse');

if (consentVersions.data.length) {
const fileId = consentVersions.data[0].consentDataId;
if (!fileId) {
res.redirect(termsUrl().toString());
}

const fileTitle = res.$t('global.text.termsOfUseFile');

await getBase64File(req, res, fileId, fileTitle);
} else {
res.redirect(termsUrl().toString());
}
} catch (err) {
next(err);
}
});

module.exports = router;
24 changes: 24 additions & 0 deletions helpers/consentVersionHelper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const api = require('../api');
const authHelper = require('./authentication');

const getConsentVersion = async (req, res, consentType) => {
const isAuthenticated = await authHelper.isAuthenticated(req);
const qs = {
$limit: 1,
consentTypes: [consentType],
$sort: {
publishedAt: -1,
},
};

if (isAuthenticated && res.locals.currentSchool) {
qs.schoolId = res.locals.currentSchool;
}

const consentVersion = await api(req).get('/consentVersions', { qs });
return consentVersion;
};

module.exports = {
getConsentVersion,
};
28 changes: 28 additions & 0 deletions helpers/fileHelper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const api = require('../api');

const downloadAsPdf = (res, fileData, fileTitle) => {
// ERR_INVALID_CHAR will get thrown on ukrainian translation without encoding
const encodedFileTitle = encodeURI(fileTitle);
const download = Buffer.from(fileData, 'base64');
res.writeHead(200, {
'Content-Type': 'application/pdf',
'Content-Disposition': `attachment; filename="${encodedFileTitle}.pdf"`,
}).end(download);
};

const getBase64File = async (req, res, fileId, fileTitle) => {
if (fileId) {
const base64File = await api(req).get(`/base64Files/${fileId}`);
if (base64File.data) {
const fileData = base64File.data.replace(
'data:application/pdf;base64,',
'',
);
downloadAsPdf(res, fileData, fileTitle);
}
}
};

module.exports = {
getBase64File,
};
5 changes: 3 additions & 2 deletions locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -1458,7 +1458,6 @@
"i": "Ich,",
"pleaseClickOnRead": "Bitte nimm mit Klick auf „Gelesen” diese Informationen zur Kenntnis, um mit der Nutzung fortzufahren.",
"pleaseConfirmTheFollowingDeclarationOfConsent": "Bitte bestätige folgende Einwilligungserklärungen, damit du die {{title}} nutzen kannst.",
"termsOfUse": "Nutzungsordnung",
"the": "die"
}
},
Expand Down Expand Up @@ -1771,6 +1770,8 @@
"dataProtectionThr": "Datenschutzhinweise",
"dataProtectionFile": "Datenschutzerklärung der Schule",
"dataProtectionFileThr": "Datenschutzhinweise der Schule",
"termsOfUse": "Nutzungsordnung",
"termsOfUseFile": "Nutzungsordnung der Schule",
"emailDomainBlocked": "Dieser Mailprovider wird nicht mehr unterstützt. Bitte wende dich an deinen Schul-Admin.",
"errorChangingFilePermissions": "Problem beim Ändern der Berechtigungen",
"errorWhileLoadingPage": "Diese Seite konnte nicht geladen werden",
Expand Down Expand Up @@ -2861,7 +2862,7 @@
},
"text": {
"acceptConsentWithoutParents": "Wenn du zwischen 14 und {{age}} Jahre alt bist, bestätige bitte zusätzlich die Einverständniserklärung, damit du die {{title}} nutzen kannst.",
"agreeTermsOfUse": "Ich habe die <a href=\"{{link}}\" target=\"_blank\" rel=\"noopener\">Nutzungsordnung der {{title}}</a> gelesen und stimme ihr zu.",
"agreeTermsOfUse": "Ich habe die <a href=\"{{link}}\" title=\"{{name}}\" class=\"{{class}}\" target=\"_blank\" rel=\"noopener\">Nutzungsordnung</a> der {{title}} gelesen und stimme ihr zu.",
"andAuthorisedToRepresent": "und berechtigt, den anderen Elternteil bei den nachfolgenden Erklärungen zu vertreten.",
"iAgreeThatThepersonalData": "Ich erkläre mich damit einverstanden, dass die personenbezogenen Daten meines Kindes entsprechend der",
"invalidLink": "Der Link ist leider nicht mehr gültig. Fordere einen neuen Link von deiner Lehrkraft oder dem Schul-Admin an.",
Expand Down
5 changes: 3 additions & 2 deletions locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1458,7 +1458,6 @@
"i": "I,",
"pleaseClickOnRead": "Please acknowledge this information by clicking on „Read” in order to proceed with the use.",
"pleaseConfirmTheFollowingDeclarationOfConsent": "Please confirm the following declarations of consent so that you can use the {{title}}.",
"termsOfUse": "Terms of Use",
"the": "the"
}
},
Expand Down Expand Up @@ -1771,6 +1770,8 @@
"dataProtectionThr": "Privacy Policy",
"dataProtectionFile": "Privacy Policy of School",
"dataProtectionFileThr": "Privacy Policy of School",
"termsOfUse": "Terms of Use",
"termsOfUseFile": "Terms of Use of School",
"emailDomainBlocked": "This mail provider is no longer supported. Please contact your school administrator.",
"errorChangingFilePermissions": "Problem changing permissions",
"errorWhileLoadingPage": "This page could not be loaded",
Expand Down Expand Up @@ -2861,7 +2862,7 @@
},
"text": {
"acceptConsentWithoutParents": "If you are between 14 and {{age}} years old, please also confirm the declaration of consent so that you can use the {{title}}.",
"agreeTermsOfUse": "I have read and agree to the <a href=\"{{link}}\" target=\"_blank\" rel=\"noopener\">Terms of Use of the {{title}}</a>.",
"agreeTermsOfUse": "I have read and agree with the school's <a href=\"{{link}}\" title=\"{{name}}\" class=\"{{class}}\" target=\"_blank\" rel=\"noopener\">Terms of Use</a> of the {{title}}.",
"andAuthorisedToRepresent": "and entitled to represent the other parent in the following declarations.",
"iAgreeThatThepersonalData": "I consent to my child's personal data being processed in accordance with the",
"invalidLink": "Unfortunately the link is no longer valid. Request a new link from your teacher or school administrator.",
Expand Down
5 changes: 3 additions & 2 deletions locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -1458,7 +1458,6 @@
"i": "Yo,",
"pleaseClickOnRead": "Acepta esta información haciendo clic en \"Leer\" para continuar con el uso.",
"pleaseConfirmTheFollowingDeclarationOfConsent": "Confirma las siguientes declaraciones de consentimiento para que puedas utilizar {{title}}.",
"termsOfUse": "Condiciones de uso",
"the": "el"
}
},
Expand Down Expand Up @@ -1771,6 +1770,8 @@
"dataProtectionThr": "Política de Privacidad",
"dataProtectionFile": "Política de Privacidad de la Escuela",
"dataProtectionFileThr": "Política de Privacidad de la Escuela",
"termsOfUse": "Condiciones de Uso",
"termsOfUseFile": "Condiciones de Uso de la Escuela",
"emailDomainBlocked": "Este proveedor de correo ya no es compatible. Ponte en contacto con el administrador de tu escuela.",
"errorChangingFilePermissions": "Hay un problema para cambiar los permisos",
"errorWhileLoadingPage": "Esta página no se ha podido cargar",
Expand Down Expand Up @@ -2861,7 +2862,7 @@
},
"text": {
"acceptConsentWithoutParents": "Si tienes entre 14 y {{age}} años, confirma también la declaración de consentimiento para que puedas utilizar {{title}}.",
"agreeTermsOfUse": "He leído y acepto las <a href=\"{{link}}\" target=\"_blank\" rel=\"noopener\">Condiciones de uso de {{title}}</a>.",
"agreeTermsOfUse": "He leído y acepto las <a href=\"{{link}}\" title=\"{{name}}\" class=\"{{class}}\" target=\"_blank\" rel=\"noopener\">Condiciones de Uso</a> de {{title}}.",
"andAuthorisedToRepresent": "y con derecho a representar al otro padre en las siguientes declaraciones.",
"iAgreeThatThepersonalData": "Doy mi consentimiento para que los datos personales de mi hijo sean procesados de acuerdo con la",
"invalidLink": "Desafortunadamente el enlace ya no es válido. Solicita un nuevo enlace a tu profesor o al administrador de la escuela.",
Expand Down
5 changes: 3 additions & 2 deletions locales/uk.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@
"dataProtectionThr": "Політика конфіденційності",
"dataProtectionFile": "Політика конфіденційності школи",
"dataProtectionFileThr": "Політика конфіденційності школи",
"termsOfUse": "Умови використання",
"termsOfUseFile": "Умови використання школи",
"emailDomainBlocked": "Цей постачальник пошти більше не підтримується. Будь ласка, зв’яжіться з адміністратором школи.",
"errorChangingFilePermissions": "Проблема зі зміною дозволів",
"fileTooLarge": "Вкладені файли перевищують максимально дозволений розмір — {{ maxFileSizeInGb }} Гб!",
Expand Down Expand Up @@ -2447,7 +2449,7 @@
"unknownError": "Ой, сталася невідома помилка. Спробуйте ще раз.",
"welcomeMailSubject": "Ласкаво просимо до {{title}}!",
"welcomeMailText": "Вітаємо, {{firstName}}!\n Ви можете ввійти до {{title}}, використовуючи такі дані для входу в систему:\n Адреса: {{address}}\n Електронна пошта: {{email}}\n {{password}}\n {{infotext}}\n Ваша\n команда {{shortTitle}} бажає вам веселощів та гарного початку",
"agreeTermsOfUse": "Я прочитав і погоджуюся з <a href=\"{{link}}\" target=\"_blank\" rel=\"noopener\">Умовами використання{{title}}</a>.",
"agreeTermsOfUse": "Я прочитав і погоджуюся з <a href=\"{{link}}\" title=\"{{name}}\" class=\"{{class}}\" target=\"_blank\" rel=\"noopener\">Умовами використання</a> школи {{title}}.",
"invalidLink": "На жаль, посилання більше не дійсне. Попросіть нове посилання у свого викладача або адміністратора школи.",
"invalidLinkEmbeddedWebsite": "Шкільна хмара була вбудована у веб-сайт. Щоби продовжити ",
"invalidLinkOpenWebsite": "Відкрийте цю сторінку в окремому вікні"
Expand Down Expand Up @@ -3039,7 +3041,6 @@
"i": "Я,",
"pleaseClickOnRead": "Підтвердьте цю інформацію, натиснувши «Прочитати», щоб продовжити використання.",
"pleaseConfirmTheFollowingDeclarationOfConsent": "Підтвердьте наведені нижче заяви про згоду, щоб ви могли використовувати {{title}}.",
"termsOfUse": "Умови використання",
"the": "це"
},
"headline": {
Expand Down
Loading