Unverified Commit 0b45adb9 authored by Philipp Berger's avatar Philipp Berger
Browse files

chore: release v1.8.0

parent b48bdf70
# Changelog
### 1.8.0 (2021-08-12)
* **backend:** fix: limit number of locations in location transfers
* **backend:** feat: check integrity of device types in tracing processes
* **backend:** feat: take badge attestation key offline
* **health-department:** fix: additional data under certain conditions not displayed in the contact person overview
* **health-department:** fix: clear storage after logout
* **health-department:** feat: encrypt share data request only for the requesting health department
* **health-department:** feat: download contact persons in octowareTN format
* **location:** fix: improve error handling during share data process
* **location:** fix: improve error handling during private key import
* **location:** fix: broken button arrangement when deleting the account
* **location:** feat: check private key after initial key download for new operators
* **scanner:** feat: add warning that v3 badges are to be replaced
* **scanner:** feat: improve scanning qr codes on badges
* **webapp:** fix: error message in the background shown after deleting the account
* **webapp:** feat: add screen to accept new AGBs
* chore: improve e2e tests
### 1.7.0 (2021-08-05)
* **backend:** feat: instrument with metrics
* **health-department:** feat: update route access restrictions for employees
......
{
"name": "e2e",
"version": "1.7.0",
"version": "1.8.0",
"main": "index.js",
"private": true,
"engines": {
......
......@@ -16,6 +16,7 @@ export const openHDLoginPage = () => {
export const loginToHD = (email, password) => {
cy.get('#username').type(email);
cy.get('#password').click();
cy.get('#password').type(password);
cy.get('button[type=submit]').click();
};
......
import faker from 'faker';
import { loginToHD, openHDLoginPage } from '../helper/ui/login.helper';
import { loginHealthDepartment, logout } from '../helper/api/auth.helper';
import { loginToHD, openHDLoginPage, addHealthDepartmentPrivateKeyFile } from '../helper/ui/login.helper';
import { HEALTH_DEPARTMENT_TRACKING_ROUTE, HEALTH_DEPARTMENT_USER_MANAGEMENT_ROUTE } from '../helper/routes';
import {
HEALTH_DEPARTMENT_TRACKING_ROUTE,
HEALTH_DEPARTMENT_USER_MANAGEMENT_ROUTE,
} from '../helper/routes';
const EMPLOYEE_FIRST_NAME = faker.name.firstName();
const EMPLOYEE_LAST_NAME = faker.name.lastName();
const EMPLOYEE_PHONE = faker.phone.phoneNumber('170 #######');
const EMPLOYEE_FIRST_NAME = 'Luca';
const EMPLOYEE_LAST_NAME = 'Tester';
const EMPLOYEE_EMAIL = 'employee@hd.com';
const EMPLOYEE_PHONE = '+49 176 12345678';
describe('Health Department / User Management / Create new employee', () => {
describe('when create a new HD employee', () => {
......@@ -15,38 +17,65 @@ describe('Health Department / User Management / Create new employee', () => {
cy.visit(HEALTH_DEPARTMENT_USER_MANAGEMENT_ROUTE);
cy.getByCy('addEmployee').should('exist').should('be.visible').click();
//fillin employee form
cy.get('.ant-modal').within(($modal) => {
cy.get('#firstName').should('exist').should('be.visible').type(EMPLOYEE_FIRST_NAME);
cy.get('#lastName').should('exist').should('be.visible').type(EMPLOYEE_LAST_NAME);
cy.get('#phone').should('exist').should('be.visible').type(EMPLOYEE_PHONE);
cy.get('#email').should('exist').should('be.visible').type(EMPLOYEE_EMAIL);
cy.get('.ant-modal').within($modal => {
cy.get('#firstName')
.should('exist')
.should('be.visible')
.type(EMPLOYEE_FIRST_NAME);
cy.get('#lastName')
.should('exist')
.should('be.visible')
.type(EMPLOYEE_LAST_NAME);
cy.get('#phone')
.should('exist')
.should('be.visible')
.type(EMPLOYEE_PHONE);
cy.get('#email')
.should('exist')
.should('be.visible')
.type(EMPLOYEE_EMAIL);
cy.get('button[type=button]').should('exist').should('be.visible');
cy.get('button[type=submit]').should('exist').should('be.visible').click();
cy.get('button[type=submit]')
.should('exist')
.should('be.visible')
.click();
});
//save generated password
cy.get('.ant-notification').should('exist').should('be.visible');
cy.get('.ant-modal').within(($modal) => {
cy.getByCy('generatedPassword').then(($password) => {
cy.get('.ant-modal').within($modal => {
cy.getByCy('generatedPassword').then($password => {
cy.wrap($password.text()).as('password');
});
cy.get('button[type=button]').should('exist').should('be.visible').click();
cy.get('button[type=button]')
.should('exist')
.should('be.visible')
.click();
});
cy.get('.ant-popconfirm').within(($popup) => {
cy.get('.ant-popover-buttons > button:nth-child(1)').should('exist').should('be.visible');
cy.get('.ant-popover-buttons > .ant-btn-primary').should('exist').should('be.visible').click();
cy.get('.ant-popconfirm').within($popup => {
cy.get('.ant-popover-buttons > button:nth-child(1)')
.should('exist')
.should('be.visible');
cy.get('.ant-popover-buttons > .ant-btn-primary')
.should('exist')
.should('be.visible')
.click();
});
//verify employee is created
cy.get('#employeeTable > div').last().then(($row) => {
expect($row.text()).contains(EMPLOYEE_FIRST_NAME + ' ' + EMPLOYEE_LAST_NAME);
expect($row.text()).contains(EMPLOYEE_PHONE);
expect($row.text()).contains(EMPLOYEE_EMAIL);
});
cy.get('#employeeTable > div')
.last()
.then($row => {
expect($row.text()).contains(
EMPLOYEE_FIRST_NAME + ' ' + EMPLOYEE_LAST_NAME
);
expect($row.text()).contains(EMPLOYEE_PHONE);
expect($row.text()).contains(EMPLOYEE_EMAIL);
});
logout();
//login as a new employee
openHDLoginPage();
cy.get('@password').then(password => {
loginToHD(EMPLOYEE_EMAIL, password);
})
});
cy.url().should('include', HEALTH_DEPARTMENT_TRACKING_ROUTE);
});
});
......
eyJpdiI6IktLUTJzUGNCMWlrzkkvTHh6ZUZLeXVqZnhxaFJtME4yaGpxR3M0Rlc2ZVk9IiwidGFnIjoiRG1RQWNhL0FqUU9WL1JQZ3RkS3MwQT09IiwiZGF0YSI6IkFoM1lIOU9xL0xNLzNmcVY5VXUxLzcxYjFSZU4xN01YRnVvMlVuVGpOYkE9IiwidiI6Mn0=
\ No newline at end of file
import { logout, downloadLocationPrivateKeyFile } from '../../helpers/functions';
import { E2E_EMAIL, E2E_PASSWORD } from '../../helpers/users';
import {
logout,
downloadLocationPrivateKeyFile,
uploadLocationPrivateKeyFile,
} from '../../helpers/functions';
import {
E2E_EMAIL,
E2E_PASSWORD,
E2E_LOCATION_PRIVATE_KEY_PATH,
E2E_LOCATION_PRIVATE_KEY_NAME,
E2E_LOCATION_WRONG_PRIVATE_KEY_PATH,
E2E_LOCATION_WRONG_PRIVATE_KEY_NAME,
} from '../../helpers/users';
import { RESET_LOCATION_KEY_QUERY } from '../../helpers/dbQueries';
import { enterEmail, enterPassword } from '../authentication.helper';
......@@ -10,12 +21,38 @@ describe('Location / Authentication / Login', () => {
beforeEach(() => cy.visit('/'));
afterEach(() => logout());
describe('Login as an operator for the first time and download private key', () => {
it('display location home page', () => {
describe('Login as an operator for the first time and download a private key, after downloading the private key upload a wrong key and go back to download another key', () => {
it('should show an error notification stating that the key is wrong to go back to regenerate a new download key', () => {
enterEmail(E2E_EMAIL);
enterPassword(E2E_PASSWORD);
cy.getByCy('loginError').should('not.exist');
cy.wait(1000);
downloadLocationPrivateKeyFile();
uploadLocationPrivateKeyFile(
E2E_LOCATION_WRONG_PRIVATE_KEY_PATH,
E2E_LOCATION_WRONG_PRIVATE_KEY_NAME
);
cy.wait(1000);
cy.getByCy('regenerateKey').should('exist');
cy.getByCy('regenerateKey', { timeout: 1000 }).click();
downloadLocationPrivateKeyFile();
cy.get('.ant-notification-notice-content').should('exist');
});
});
describe('Login as an operator for the first time and download private key, after downloading private double check if it is the correct key', () => {
it('should close the modal successfully', () => {
enterEmail(E2E_EMAIL);
enterPassword(E2E_PASSWORD);
cy.getByCy('loginError').should('not.exist');
cy.wait(1000);
downloadLocationPrivateKeyFile();
cy.wait(1000);
uploadLocationPrivateKeyFile(
E2E_LOCATION_PRIVATE_KEY_PATH,
E2E_LOCATION_PRIVATE_KEY_NAME
);
cy.getByCy('complete', { timeout: 1000 }).click();
cy.get('.ant-modal-body').should('not.exist');
});
});
describe('Login as an operator with wrong password', () => {
......@@ -35,4 +72,4 @@ describe('Location / Authentication / Login', () => {
cy.getByCy('forgotPasswordPage').should('exist');
});
});
});
\ No newline at end of file
});
import { uploadLocationPrivateKeyFile } from '../../helpers/functions';
import { E2E_EMAIL, E2E_PASSWORD, E2E_LOCATION_PRIVATE_KEY_PATH } from '../../helpers/users';
import {
E2E_EMAIL,
E2E_PASSWORD,
E2E_LOCATION_PRIVATE_KEY_PATH,
E2E_LOCATION_PRIVATE_KEY_NAME,
} from '../../helpers/users';
import { enterEmail, enterPassword } from '../authentication.helper';
import { verifyLocationHomePage, verifyModalWindowIsClosed } from '../../helpers/inputValidation.helper'
import {
verifyLocationHomePage,
verifyModalWindowIsClosed,
} from '../../helpers/inputValidation.helper';
describe('Location / Authentication / Login', () => {
describe('Login as an operator and upload private key', () => {
it('accepts private key and displays location home page', () => {
cy.visit('/')
cy.visit('/');
enterEmail(E2E_EMAIL);
enterPassword(E2E_PASSWORD);
cy.getByCy('loginError').should('not.exist');
uploadLocationPrivateKeyFile(E2E_LOCATION_PRIVATE_KEY_PATH);
uploadLocationPrivateKeyFile(
E2E_LOCATION_PRIVATE_KEY_PATH,
E2E_LOCATION_PRIVATE_KEY_NAME
);
verifyModalWindowIsClosed();
verifyLocationHomePage();
});
......
import { DELETE_E2E_TRACE_QUERY } from '../helpers/dbQueries.js';
import {
verifyLocationOverview,
verifyScannerCounter,
verifyLocationOverview,
} from '../helpers/inputValidation.helper';
import {
DEVICE_TYPES,
traceDataPayload,
createGroupPayload,
DEVICE_TYPES,
} from '../helpers/functions.helper';
import {
login,
......
......@@ -10,7 +10,7 @@ import {
import { removeLocation } from '../location/location.helper';
describe('Download QR Codes PDF', () => {
describe('Download QR Codes PDF', {retries: 3}, () => {
beforeEach(() => login());
after(() => removeLocation(NEW_RESTAURANT_LOCATION));
......@@ -18,7 +18,7 @@ describe('Download QR Codes PDF', () => {
it('downloads the Group PDF', () => {
cy.getByCy('createGroup').click();
cy.getByCy('restaurant').click();
cy.get('#groupName').type(RESTAURANT_NAME);
cy.get('#groupName', {timeout: 20000}).type(RESTAURANT_NAME);
cy.get('form').submit();
cy.get('#locationSearch').type(RESTAURANT_ADDRESS);
cy.get('.pac-container > div:first-of-type').click({ force: true });
......@@ -34,7 +34,7 @@ describe('Download QR Codes PDF', () => {
cy.get('.ant-message-notice').should('exist');
cy.get('.ant-message-notice', { timeout: 20000 }).should('not.exist');
cy.readFile(
'./downloads/luca_QRCode_Test Restaurant_Test Restaurant.pdf'
'./downloads/luca_QRCode_Test Restaurant_Test Restaurant.pdf', {timeout: 30000}
);
cy.task('deleteFileIfExists', './downloads/luca_QRCode_Test Restaurant_Test Restaurant.pdf');
});
......@@ -72,7 +72,7 @@ describe('Download QR Codes PDF', () => {
cy.getByCy('activateTableSubdivision').click();
cy.getByCy('locationCard-generateQRCodes').click();
cy.getByCy('qrCodeDownload').click();
cy.get('.ant-message-notice', { timeout: 40000 }).should('not.exist');
cy.get('.ant-message-notice', { timeout: 60000 }).should('not.exist');
cy.readFile(
'./downloads/luca_QRCodes_Nexenio_1 e2e_Nexenio_1 e2e - NEW_RESTAURANT_LOCATION_Tables.pdf'
);
......
......@@ -138,12 +138,18 @@ export const undoAccountDeletion = () => {
return cy.request('POST', 'api/v3/operators/restore');
};
export const uploadLocationPrivateKeyFile = filename => {
export const downloadLocationPrivateKeyFile = () => {
cy.getByCy('downloadPrivateKey', { timeout: 8000 }).click();
cy.getByCy('checkPrivateKeyIsDownloaded').click();
cy.getByCy('next').should('exist').click();
};
export const uploadLocationPrivateKeyFile = (filename, name) => {
cy.readFile(filename).then(fileContent => {
cy.get('input[type=file]').attachFile({
fileContent,
mimeType: 'text/plain',
fileName: 'luca_locations_Simon_Tester_privateKey.luca',
fileName: name,
});
});
};
......@@ -157,12 +163,6 @@ export const skipLocationPrivateKeyFile = () => {
});
};
export const downloadLocationPrivateKeyFile = () => {
cy.getByCy('downloadPrivateKey', { timeout: 8000 }).click();
cy.getByCy('checkPrivateKeyIsDownloaded').click();
cy.getByCy('finish').should('exist').click();
};
export const checkoutGuests = () => {
cy.getByCy('checkoutGuest').click();
cy.get('.ant-popover-buttons .ant-btn-primary').click();
......
......@@ -6,4 +6,14 @@ export const E2E_PHONE_NUMBER = '+4917612345678';
export const E2E_FIRSTNAME = 'Torsten';
export const E2E_LASTNAME = 'Tester';
export const E2E_LOCATION_PRIVATE_KEY_PATH = './downloads/luca_locations_Torsten_Tester_privateKey.luca';
export const E2E_LOCATION_PRIVATE_KEY_PATH =
'./downloads/luca_locations_Torsten_Tester_privateKey.luca';
export const E2E_LOCATION_PRIVATE_KEY_NAME =
'luca_locations_Torsten_Tester_privateKey.luca';
export const E2E_LOCATION_WRONG_PRIVATE_KEY_PATH =
'./specs/locations/assets/luca_locations_wrong_privateKey.luca';
export const E2E_LOCATION_WRONG_PRIVATE_KEY_NAME =
'luca_locations_wrong_privateKey.luca';
......@@ -3,11 +3,7 @@ import moment from 'moment';
import { DELETE_E2E_TRACE_QUERY } from '../helpers/dbQueries';
import { checkin, login, checkoutGuests } from '../helpers/functions';
import { traceDataPayload, DEVICE_TYPES } from '../helpers/functions.helper';
import {
verifyCheckinGuestTime,
verifyCheckoutGuestTime,
verifyGuestsCount,
} from '../helpers/inputValidation.helper';
import { verifyCheckinGuestTime, verifyCheckoutGuestTime, verifyGuestsCount } from '../helpers/inputValidation.helper';
const CURRENT_DATE = moment().format('DD.MM.YYYY');
......
......@@ -12,8 +12,9 @@ import { clean } from '../../workflow/helpers/functions';
import { addHealthDepartmentPrivateKeyFile } from '../../health-department/helper/ui/login.helper';
import { loginHealthDepartment } from '../../health-department/helper/api/auth.helper';
import { WEBAPP_ROUTE, LOCATIONS_ROUTE } from '../helpers/routes';
import {E2E_EMAIL, E2E_PASSWORD} from "../../locations/helpers/users";
describe('WebApp / CheckIn', () => {
describe('WebApp / CheckIn', {retries: 3}, () => {
before(() => {
clean();
basicLocationLogin();
......@@ -25,7 +26,7 @@ describe('WebApp / CheckIn', () => {
registerDevice();
});
after(() => {
basicLocationLogin();
basicLocationLogin(E2E_EMAIL, E2E_PASSWORD, false);
cy.get('@groupId').then(groupId => {
deleteGroup(groupId);
});
......@@ -38,7 +39,7 @@ describe('WebApp / CheckIn', () => {
cy.get('@scannerId').then(scannerId => {
cy.visit(`${WEBAPP_ROUTE}/${scannerId}`);
});
cy.url().should('contain', '/checkout');
cy.url({timeout: 4000}).should('contain', '/checkout');
cy.getByCy('locationName').should('contain', createGroupPayload.name);
// Simulate clock
......
import {WEBAPP_ROUTE} from "./routes";
export const DATABASE_NAME = 'luca';
export const DATABASE_VERSION = 50;
export const DATABASE_VERSION = 60;
export const USER_ID = 'USER_ID';
export const USER_DATA_SECRET = 'USER_DATA_SECRET';
......
export const E2E_COMPLETE_EMAIL = 'complete_workflow@nexenio.com';
export const E2E_COMPLETE_PASSWORD = 'workflowTesting!';
export const WORKFLOW_LOCATION_PRIVATE_KEY_PATH = './downloads/luca_locations_Simon_Tester_privateKey.luca';
export const WORKFLOW_LOCATION_PRIVATE_KEY_PATH =
'./downloads/luca_locations_Simon_Tester_privateKey.luca';
export const WORKFLOW_LOCATION_PRIVATE_KEY_NAME =
'luca_locations_Simon_Tester_privateKey.luca';
......@@ -20,6 +20,7 @@ import { clean } from '../helpers/functions';
import {
E2E_COMPLETE_EMAIL,
E2E_COMPLETE_PASSWORD,
WORKFLOW_LOCATION_PRIVATE_KEY_NAME,
WORKFLOW_LOCATION_PRIVATE_KEY_PATH,
} from '../helpers/users';
import {
......@@ -53,12 +54,18 @@ context('Workflow', () => {
cy.visit(APP_ROUTE);
downloadLocationPrivateKeyFile();
cy.wait(1000);
uploadLocationPrivateKeyFile(
WORKFLOW_LOCATION_PRIVATE_KEY_PATH,
WORKFLOW_LOCATION_PRIVATE_KEY_NAME
);
cy.getByCy('complete', { timeout: 1000 }).click();
cy.get('.ant-modal-body').should('not.exist');
cy.wait(1000);
createGroup({
...createGroupPayload,
name: FORM_WORKFLOW_TESTING_GROUP_NAME,
});
cy.get('.ant-modal-close-x').click();
// Checkin with contact form
cy.log('Checkin with in testing Location with Contact Form');
cy.stubNewWindow();
......@@ -104,7 +111,10 @@ context('Workflow', () => {
cy.getByCy('dataRequests').click();
cy.stubNewWindow();
cy.getByCy('completeDataTransfer').first().click();
uploadLocationPrivateKeyFile(WORKFLOW_LOCATION_PRIVATE_KEY_PATH);
uploadLocationPrivateKeyFile(
WORKFLOW_LOCATION_PRIVATE_KEY_PATH,
WORKFLOW_LOCATION_PRIVATE_KEY_NAME
);
cy.getByCy('next').click();
cy.getByCy('finish').click();
......
{
"name": "@lucaapp/web",
"version": "1.7.0",
"version": "1.8.0",
"private": true,
"license": "Apache-2.0",
"author": "Culture4Life <hello@luca-app.de> (https://www.luca-app.de/)",
......
......@@ -45,7 +45,7 @@ module.exports = {
keys: {
badge: {
targetKeyId: 'BADGE_TARGET_KEY_ID',
private: 'BADGE_KEY_PRIVATE',
keyLength: 'BADGE_GENERATOR_KEY_LENGTH',
attestation: {
v3: 'BADGE_ATTESTATION_KEY_PUBLIC_V3',
v4: 'BADGE_ATTESTATION_KEY_PUBLIC_V4',
......
......@@ -67,6 +67,9 @@ module.exports = {
maxAge: moment.duration(28, 'days').as('hours'),
maxDuration: moment.duration(24, 'hours').as('hours'),
},
locationTransfers: {
maxLocations: 100 * 14, // Max 100 location transfers per day for 14 days of contact tracing
},
smsChallenges: {
maxAge: moment.duration(45, 'days').as('hours'),
},
......@@ -108,9 +111,9 @@ module.exports = {
minKeyAge: moment.duration(24, 'hours').as('hours'),
},
badge: {
targetKeyId: 1,
targetKeyId: 2,
keyLength: 64,
// DEV ONLY
private: 'qzbym5WwwkbSQ9BJvIdGZIjdh9p72HwQseZXbDs+AbU=',
attestation: {
// DEV ONLY
v3:
......
{
"name": "@lucaapp/backend",
"version": "1.7.0",
"version": "1.8.0",
"private": true,
"license": "Apache-2.0",
"author": "Culture4Life <hello@luca-app.de> (https://www.luca-app.de/)",
......@@ -83,6 +83,7 @@
"devDependencies": {
"@types/expect": "24.3.0",
"@types/mocha": "8.2.3",
"@types/node": "16.4.13",
"@typescript-eslint/eslint-plugin": "4.28.2",
"@typescript-eslint/parser": "4.28.2",
"chai": "4.3.4",
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment