Unverified Commit 6d172d1b authored by Philipp Berger's avatar Philipp Berger
Browse files

chore: release v1.6.2

parent c1d9883b
# Changelog
### 1.6.2 (2021-07-29)
* **backend:** ref: split checkin route for scanner and contact-form
### 1.6.1 (2021-07-28)
* **backend:** feat: lower redis usage by adding etag caching for large values
......
{
"name": "e2e",
"version": "1.6.0",
"version": "1.6.2",
"main": "index.js",
"private": true,
"engines": {
......
import { login, logout, contactFormCheckin, createGroup, deleteGroup } from '../helpers/functions';
import { traceDataPayload, createGroupPayload, DEVICE_TYPES } from '../helpers/functions.helper';
import {
login,
logout,
checkin,
createGroup,
deleteGroup,
} from '../helpers/functions';
import {
traceDataPayload,
createGroupPayload,
DEVICE_TYPES,
} from '../helpers/functions.helper';
import { DELETE_E2E_TRACE_QUERY } from '../helpers/dbQueries.js';
import { verifyLocationOverview, verifyScannerCounter } from '../helpers/inputValidation.helper';
import {
verifyLocationOverview,
verifyScannerCounter,
} from '../helpers/inputValidation.helper';
import { E2E_DEFAULT_LOCATION_FORM } from '../helpers/locations';
const CHECKIN_GROUP_NAME = 'neXenio';
......@@ -30,7 +44,11 @@ describe('Location / Checkin Options / Cam scanner', () => {
verifyScannerCounter(CHECKIN_GROUP_NAME);
// Checkin with camera scanner
cy.get('@scannerId').then(scannerId => {
contactFormCheckin({ ...traceDataPayload, scannerId: scannerId, deviceType: DEVICE_TYPES.mobile });
checkin({
...traceDataPayload,
scannerId: scannerId,
deviceType: DEVICE_TYPES.mobile,
});
cy.get('span[aria-label=redo]').click();
cy.contains('1/1');
});
......
import { DELETE_E2E_TRACE_QUERY } from '../helpers/dbQueries.js';
import { verifyLocationOverview, verifyScannerCounter } from '../helpers/inputValidation.helper';
import { traceDataPayload, createGroupPayload, DEVICE_TYPES } from '../helpers/functions.helper';
import { login, logout, contactFormCheckin, createGroup, deleteGroup } from '../helpers/functions';
import {
verifyLocationOverview,
verifyScannerCounter,
} from '../helpers/inputValidation.helper';
import {
traceDataPayload,
createGroupPayload,
DEVICE_TYPES,
} from '../helpers/functions.helper';
import {
login,
logout,
checkin,
createGroup,
deleteGroup,
} from '../helpers/functions';
import { E2E_DEFAULT_LOCATION_FORM } from '../helpers/locations';
const CHECKIN_GROUP_NAME = 'neXenio';
......@@ -28,7 +42,11 @@ describe('Location / Checkin Options / hardware scanner', () => {
verifyScannerCounter(CHECKIN_GROUP_NAME);
// Checkin with scanner tool
cy.get('@scannerId').then(scannerId => {
contactFormCheckin({ ...traceDataPayload, scannerId: scannerId, deviceType: DEVICE_TYPES.scanner });
checkin({
...traceDataPayload,
scannerId: scannerId,
deviceType: DEVICE_TYPES.mobile,
});
cy.get('span[aria-label=redo]').click();
cy.contains('1/1');
});
......
import { E2E_DEFAULT_LOCATION_SCANNER } from './locations';
export const E2E_TRACE_ID = 'w2RiSHiitpfJ0hxcJz5ZYw==';
export const DEVICE_TYPES = { "mobile":1, "tablet":2, "scanner":3, "contactForm":4 };
export const DEVICE_TYPES = {
mobile: 1,
tablet: 2,
scanner: 3,
contactForm: 4,
};
export const createGroupPayload = {
type: 'base',
......
......@@ -126,8 +126,8 @@ export const createLocation = (groupId, locationName) => {
});
};
export const contactFormCheckin = traceDataPayload => {
cy.request('POST', '/api/v3/operators/traces/checkin', traceDataPayload);
export const checkin = traceDataPayload => {
cy.request('POST', `/api/v3/traces/checkin`, traceDataPayload);
};
export const requestAccountDeletion = () => {
......
......@@ -14,6 +14,7 @@ export const E2E_SECOND_LOCATION_NAME = 'Restaurant';
export const E2E_THIRD_LOCATION_UUID = '04d3e0b3-c64f-43bd-9b1a-f53f9032e312';
export const E2E_THIRD_LOCATION_NAME = 'Nexenio Kitchen';
export const E2E_DEFAULT_LOCATION_FORM = '68e580d5-3921-48ce-b8ad-b313ec28926f';
export const E2E_DEFAULT_LOCATION_SCANNER =
'09eb8d41-1914-4950-9526-36ebc6ad58fd';
export const E2E_DEFAULT_SCANNER_LINK = `https://localhost/scanner/${E2E_DEFAULT_LOCATION_SCANNER}`;
......
import { contactFormCheckin, login } from '../helpers/functions';
import { traceDataPayload } from '../helpers/functions.helper';
import { checkin, login } from '../helpers/functions';
import { traceDataPayload, DEVICE_TYPES } from '../helpers/functions.helper';
import { DELETE_E2E_TRACE_QUERY } from '../helpers/dbQueries';
import { E2E_DEFAULT_LOCATION_FORM } from '../helpers/locations';
const checkTrackingTime = x => {
const filtered = x.split(' - ').filter(el => el !== '');
......@@ -22,7 +23,7 @@ describe('Locations / Location overview', () => {
describe('when check-in/check-out location', () => {
it('guest count and the tracking time is changed', () => {
// Check in a guest
contactFormCheckin(traceDataPayload);
checkin({ ...traceDataPayload, deviceType: DEVICE_TYPES.mobile });
// Expect the guest count to be 1
cy.getByCy('guestCount').next().should('be.visible').click();
cy.getByCy('guestCount').should('contain', 1);
......@@ -44,7 +45,10 @@ describe('Locations / Location overview', () => {
// Expect the both checkin and checkout time exist in the guest list modal
cy.getByCy('showGuestList').click({ force: true });
cy.getByCy('trackingTime').then(el => {
setTimeout(() => expect(checkTrackingTime(el.text())).to.equal(2), 3000);
setTimeout(
() => expect(checkTrackingTime(el.text())).to.equal(2),
3000
);
});
});
});
......
{
"name": "@lucaapp/web",
"version": "1.6.0",
"version": "1.6.2",
"private": true,
"license": "Apache-2.0",
"author": "Culture4Life <hello@luca-app.de> (https://www.luca-app.de/)",
......
{
"name": "@lucaapp/backend",
"version": "1.6.0",
"version": "1.6.2",
"private": true,
"license": "Apache-2.0",
"author": "Culture4Life <hello@luca-app.de> (https://www.luca-app.de/)",
......
const router = require('express').Router();
const status = require('http-status');
const moment = require('moment');
const { UniqueConstraintError } = require('sequelize');
const database = require('../../database');
const {
validateSchema,
validateParametersSchema,
} = require('../../middlewares/validateSchema');
const { limitRequestsPerHour } = require('../../middlewares/rateLimit');
const { formatLocationName } = require('../../utils/format');
const { formIdParametersSchema } = require('./forms.schemas');
const { DEVICE_TYPE_FORM } = require('../../constants/deviceTypes');
const { checkinSchema, formIdParametersSchema } = require('./forms.schemas');
// get single form infos
router.get(
......@@ -51,4 +54,66 @@ router.get(
}
);
/**
* Performs a check-in in a location via form
*
* @see https://www.luca-app.de/securityoverview/processes/guest_app_checkin.html#qr-code-scanning-validation-and-check-in-upload
*/
router.post(
'/:formId/traces/checkin',
limitRequestsPerHour('traces_checkin_post_ratelimit_hour'),
validateParametersSchema(formIdParametersSchema),
validateSchema(checkinSchema),
async (request, response) => {
const location = await database.Location.findOne({
where: {
scannerId: request.body.scannerId,
formId: request.params.formId,
},
});
if (!location) {
return response.sendStatus(status.NOT_FOUND);
}
if (request.body.deviceType !== DEVICE_TYPE_FORM) {
return response.sendStatus(status.FORBIDDEN);
}
const trace = await database.Trace.findByPk(request.body.traceId);
if (trace) {
return response.sendStatus(status.CREATED);
}
const requestTime = moment.unix(request.body.timestamp);
const now = moment();
if (Math.abs(moment.duration(now.diff(requestTime)).as('seconds')) > 300) {
return response.sendStatus(status.CONFLICT);
}
try {
await database.Trace.create({
traceId: request.body.traceId,
locationId: location.uuid,
time: [requestTime, location.endsAt],
data: request.body.data,
iv: request.body.iv,
mac: request.body.mac,
publicKey: request.body.publicKey,
deviceType: request.body.deviceType,
});
} catch (error) {
if (error instanceof UniqueConstraintError) {
return response.sendStatus(status.CREATED);
}
throw error;
}
return response.sendStatus(status.CREATED);
}
);
module.exports = router;
......@@ -4,6 +4,18 @@ const formIdParametersSchema = z.object({
formId: z.uuid(),
});
const checkinSchema = z.object({
traceId: z.traceId(),
scannerId: z.uuid(),
timestamp: z.unixTimestamp(),
data: z.base64({ max: 128 }),
iv: z.iv(),
mac: z.mac(),
publicKey: z.ecPublicKey(),
deviceType: z.deviceType(),
});
module.exports = {
checkinSchema,
formIdParametersSchema,
};
......@@ -23,7 +23,6 @@ const {
const locationsRouter = require('./operators/locations');
const passwordRouter = require('./operators/password');
const emailsRouter = require('./operators/email');
const tracesRouter = require('./operators/traces');
const {
createSchema,
......@@ -238,6 +237,5 @@ router.post('/restore', requireOperator, async (request, response) => {
router.use('/locations', locationsRouter);
router.use('/password', passwordRouter);
router.use('/email', emailsRouter);
router.use('/traces', tracesRouter.default);
module.exports = router;
import { z } from 'utils/validation';
export const checkinSchema = z.object({
traceId: z.traceId(),
scannerId: z.uuid(),
timestamp: z.unixTimestamp(),
data: z.base64({ max: 128 }),
iv: z.iv(),
mac: z.mac(),
publicKey: z.ecPublicKey(),
deviceType: z.deviceType(),
});
import { Router, Request, Response } from 'express';
import { z } from 'zod';
import status from 'http-status';
import moment from 'moment';
import { UniqueConstraintError } from 'sequelize';
import database from 'database';
import { validateSchema } from 'middlewares/validateSchema';
import { requireOperator } from 'middlewares/requireUser';
import { limitRequestsPerHour } from 'middlewares/rateLimit';
import { checkinSchema } from './traces.schemas';
const router = Router();
/**
* Performs a check-in in a location with a badge or via form with authenticated operator
*
* @see https://www.luca-app.de/securityoverview/processes/guest_app_checkin.html#qr-code-scanning-validation-and-check-in-upload
*/
router.post(
'/checkin',
limitRequestsPerHour('traces_checkin_post_ratelimit_hour'),
requireOperator,
validateSchema(checkinSchema),
async (
request: Request<unknown, unknown, z.infer<typeof checkinSchema>>,
response: Response
) => {
const location = await database.Location.findOne({
where: {
scannerId: request.body.scannerId,
operator: request.user!.uuid,
},
});
if (!location) {
return response.sendStatus(status.NOT_FOUND);
}
const trace = await database.Trace.findByPk(request.body.traceId);
if (trace) {
return response.sendStatus(status.CREATED);
}
const requestTime = moment.unix(request.body.timestamp);
const now = moment();
if (Math.abs(moment.duration(now.diff(requestTime)).as('seconds')) > 300) {
return response.sendStatus(status.CONFLICT);
}
try {
await database.Trace.create({
traceId: request.body.traceId,
locationId: location.uuid,
time: [requestTime, location.endsAt],
data: request.body.data,
iv: request.body.iv,
mac: request.body.mac,
publicKey: request.body.publicKey,
deviceType: request.body.deviceType,
});
} catch (error) {
if (error instanceof UniqueConstraintError) {
return response.sendStatus(status.CREATED);
}
throw error;
}
return response.sendStatus(status.CREATED);
}
);
export default router;
const router = require('express').Router();
const status = require('http-status');
const moment = require('moment');
const { Op } = require('sequelize');
const { Op, UniqueConstraintError } = require('sequelize');
const database = require('../../database');
const {
validateSchema,
validateParametersSchema,
} = require('../../middlewares/validateSchema');
const { limitRequestsPerHour } = require('../../middlewares/rateLimit');
const { formatLocationName } = require('../../utils/format');
const {
checkinSchema,
scannerIdParametersSchema,
scannerAccessIdParametersSchema,
} = require('./scanners.schemas');
......@@ -162,4 +165,62 @@ router.get(
}
);
/**
* Performs a check-in in a location via a scanner
*
* @see https://www.luca-app.de/securityoverview/processes/guest_app_checkin.html#qr-code-scanning-validation-and-check-in-upload
*/
router.post(
'/:scannerAccessId/traces/checkin',
limitRequestsPerHour('traces_checkin_post_ratelimit_hour'),
validateParametersSchema(scannerAccessIdParametersSchema),
validateSchema(checkinSchema),
async (request, response) => {
const location = await database.Location.findOne({
where: {
scannerId: request.body.scannerId,
scannerAccessId: request.params.scannerAccessId,
},
});
if (!location) {
return response.sendStatus(status.NOT_FOUND);
}
const trace = await database.Trace.findByPk(request.body.traceId);
if (trace) {
return response.sendStatus(status.CREATED);
}
const requestTime = moment.unix(request.body.timestamp);
const now = moment();
if (Math.abs(moment.duration(now.diff(requestTime)).as('seconds')) > 300) {
return response.sendStatus(status.CONFLICT);
}
try {
await database.Trace.create({
traceId: request.body.traceId,
locationId: location.uuid,
time: [requestTime, location.endsAt],
data: request.body.data,
iv: request.body.iv,
mac: request.body.mac,
publicKey: request.body.publicKey,
deviceType: request.body.deviceType,
});
} catch (error) {
if (error instanceof UniqueConstraintError) {
return response.sendStatus(status.CREATED);
}
throw error;
}
return response.sendStatus(status.CREATED);
}
);
module.exports = router;
......@@ -8,7 +8,19 @@ const scannerAccessIdParametersSchema = z.object({
scannerAccessId: z.uuid(),
});
const checkinSchema = z.object({
traceId: z.traceId(),
scannerId: z.uuid(),
timestamp: z.unixTimestamp(),
data: z.base64({ max: 128 }),
iv: z.iv(),
mac: z.mac(),
publicKey: z.ecPublicKey(),
deviceType: z.deviceType(),
});
module.exports = {
checkinSchema,
scannerIdParametersSchema,
scannerAccessIdParametersSchema,
};
......@@ -17,8 +17,10 @@ const {
const { limitRequestsPerHour } = require('../../middlewares/rateLimit');
const {
DEVICE_TYPE_IOS,
DEVICE_TYPE_ANDROID,
DEVICE_TYPE_WEBAPP,
DEVICE_TYPE_STATIC,
DEVICE_TYPE_FORM,
} = require('../../constants/deviceTypes');
const {
......@@ -35,14 +37,18 @@ const {
*
* @see https://www.luca-app.de/securityoverview/processes/guest_app_checkin.html#qr-code-scanning-validation-and-check-in-upload
*/
const forbiddenDeviceTypes = new Set([DEVICE_TYPE_STATIC, DEVICE_TYPE_FORM]);
const allowedDeviceTypes = new Set([
DEVICE_TYPE_IOS,
DEVICE_TYPE_ANDROID,
DEVICE_TYPE_WEBAPP,
]);
router.post(
'/checkin',
limitRequestsPerHour('traces_checkin_post_ratelimit_hour'),
validateSchema(checkinSchema),
async (request, response) => {
if (forbiddenDeviceTypes.has(request.body.deviceType)) {
if (!allowedDeviceTypes.has(request.body.deviceType)) {
return response.sendStatus(status.PRECONDITION_FAILED);
}
......
{
"name": "@lucaapp/contact-form",
"version": "1.6.0",
"version": "1.6.2",
"private": true,
"license": "Apache-2.0",
"author": "Culture4Life <hello@luca-app.de> (https://www.luca-app.de/)",
......
......@@ -52,7 +52,7 @@ const LoadContactFormHOC = ({ component: Component }) => {
return (
<>
<Component scanner={data} />
<Component scanner={data} formId={formId} />
</>
);
};
......
Supports Markdown
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