Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3bc5886
Add pikaratikka to the maps
jhanninen Sep 8, 2023
9412f31
Add icons and colors for pikaratikka
jhanninen Sep 8, 2023
67b730a
Fix typo on color code
jhanninen Sep 11, 2023
ca949f3
Merge pull request #107 from HSLdevcom/MM-511
jhanninen Sep 14, 2023
3bfe059
Change pikaratikka to updated color
jhanninen Sep 28, 2023
1b52131
Merge pull request #110 from HSLdevcom/MM-511
jhanninen Sep 28, 2023
df46413
MM-523: Fix rendering error about minimum radius on circle vectors (#…
e-halinen Sep 28, 2023
c1a9a16
#25217: Initialize this.root in component constructors
e-halinen Oct 17, 2023
210a9d3
Cleanup
e-halinen Oct 17, 2023
7d2cd01
25664: Prevent large generations timeout and left on pending state
e-halinen Nov 2, 2023
e21ee8b
Merge pull request #112 from HSLdevcom/25217_map_generation_race_cond…
ahjyrkia Dec 11, 2023
3a4d741
Merge pull request #114 from HSLdevcom/25664_pending_large_maps
ahjyrkia Dec 19, 2023
fe95731
Bump hsl-map-style version to 1.1.3 (#117)
e-halinen Apr 24, 2024
4ef2d65
AB#48634: Bump hsl-map-style version to 1.2.0 (#118)
e-halinen Dec 30, 2024
689aa5f
zoneSymbol positioning fix (#115)
ahjyrkia Feb 7, 2025
e3474d7
Merge branch 'master' into development
e-halinen Feb 7, 2025
a33f132
AB#57971 improve access control (#120)
e-halinen May 6, 2025
15a61b1
AB#56581: Add version numbering and check against prod (#122)
e-halinen May 23, 2025
9eb1e45
AB#56568: Change license to AGPL-3.0-only (#124)
e-halinen Jun 4, 2025
78ef518
react updates (#116)
ahjyrkia Jun 4, 2025
38fc477
AB#57970: Switch prod login provider (#125)
e-halinen Jun 12, 2025
7b4e871
v1.1.0
e-halinen Jun 12, 2025
00512e2
AB#61706: Add bounding box margins for rendered label positions (#127)
e-halinen Jun 25, 2025
74f38c1
AB#61706: Improve label placement (#128)
e-halinen Jul 3, 2025
8dc3bd5
AB#60123: Switch to assigned group-based access control (#123)
e-halinen Jul 10, 2025
86d90ba
AB#60109: Fix readonly role CRUD permissions (#129)
e-halinen Jul 10, 2025
3f77ad5
Bump luxon from 3.0.3 to 3.2.1
dependabot[bot] Aug 7, 2025
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
6 changes: 2 additions & 4 deletions .env.dev
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,5 @@ API_CLIENT_ID=2411181397763460
API_CLIENT_SECRET=
REDIRECT_URI=https://dev.kartat.hsl.fi/kartta
LOGIN_PROVIDER_URI=https://hslid-uat.cinfra.fi
DOMAINS_ALLOWED_TO_GENERATE=
DOMAINS_ALLOWED_TO_LOGIN=

ROUTEMAP_TEST_GROUP=Karttageneraattori-test
GROUP_GENERATE=
GROUP_READONLY=
8 changes: 4 additions & 4 deletions .env.prod
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ AZURE_STORAGE_KEY=
AZURE_FONTS_SAS_URL=
AZURE_UPLOAD_CONTAINER=routemap-prod

CLIENT_ID=7833861618225795
CLIENT_ID=0704589208046070
CLIENT_SECRET=
API_CLIENT_ID=2411181397763460
API_CLIENT_SECRET=
REDIRECT_URI=https://kartat.hsl.fi/kartta
LOGIN_PROVIDER_URI=https://hslid-uat.cinfra.fi
DOMAINS_ALLOWED_TO_GENERATE=
DOMAINS_ALLOWED_TO_LOGIN=
LOGIN_PROVIDER_URI=https://id.hsl.fi
GROUP_GENERATE=
GROUP_READONLY=
22 changes: 0 additions & 22 deletions .env.stage

This file was deleted.

22 changes: 22 additions & 0 deletions .github/workflows/check_version.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Check version numbering is updated prior to merging

on:
pull_request:
branches:
- master

jobs:
check_version:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Check if version has been updated
id: check
uses: EndBug/version-check@v2
- name: Fail if version is not changed prior to merge
if: steps.check.outputs.changed == 'false'
uses: actions/github-script@v7
with:
script: |
core.setFailed('No version number change found. Run `yarn version`-command to upgrade version before merge can be completed.')
211 changes: 211 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ module.exports = {
AZURE_STORAGE_KEY: secretsEnv.AZURE_STORAGE_KEY || '',
CLIENT_SECRET: secretsEnv.CLIENT_SECRET || '',
API_CLIENT_SECRET: secretsEnv.API_CLIENT_SECRET || '',
DOMAINS_ALLOWED_TO_LOGIN: secretsEnv.DOMAINS_ALLOWED_TO_LOGIN || '',
GROUP_GENERATE: secretsEnv.GROUP_GENERATE || '',
GROUP_READONLY: secretsEnv.GROUP_READONLY || '',
HSL_TESTING_HSLID_USERNAME: secretsEnv.HSL_TESTING_HSLID_USERNAME || '',
HSL_TESTING_HSLID_PASSWORD: secretsEnv.HSL_TESTING_HSLID_PASSWORD || '',
ROUTEMAP_TEST_GROUP: secretsEnv.ROUTEMAP_TEST_GROUP || '',
};
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hsl-routemap-server",
"version": "0.1.0",
"version": "1.1.0",
"description": "HSL Routemap server",
"main": "index.js",
"scripts": {
Expand Down Expand Up @@ -30,7 +30,7 @@
"url": "git+https://github.com/HSLdevcom/hsl-routemap-server.git"
},
"author": "",
"license": "MIT",
"license": "AGPL-3.0-only",
"bugs": {
"url": "https://github.com/HSLdevcom/hsl-routemap-server/issues"
},
Expand Down Expand Up @@ -96,13 +96,14 @@
"pg": "^8.7.3",
"prop-types": "^15.6.0",
"puppeteer": "^15.4.1",
"react": "16.8.6",
"react": "18.2.0",
"react-apollo": "^2.0.1",
"react-dom": "16.8.6",
"react-dom": "18.2.0",
"recompose": "^0.30.0",
"segseg": "^0.2.2",
"serve": "^13.0.2",
"uuid": "^3.1.0",
"validator": "^13.15.0",
"viewport-mercator-project": "^4.1.1"
}
}
29 changes: 13 additions & 16 deletions scripts/auth/authEndpoints.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
const { get, last, clone } = require('lodash');
const AuthService = require('./authService');
const validator = require('validator');

const { DOMAINS_ALLOWED_TO_LOGIN, ROUTEMAP_TEST_GROUP } = require('../../constants');
const { GROUP_GENERATE, GROUP_READONLY } = require('../../constants');

const allowedDomains = DOMAINS_ALLOWED_TO_LOGIN.split(',');
const hasAllowedGroup = async (userInfo) => {
const groups = get(userInfo, 'groups', {});

const hasAllowedDomain = async (userInfo) => {
const groupNames = get(userInfo, 'groups');
const domain = last(userInfo.email.toLowerCase().split('@')) || '';

if (groupNames.includes(ROUTEMAP_TEST_GROUP)) {
return true;
}

if (!allowedDomains.includes(domain)) {
console.log(`User does not have allowed domain. Logging out.`);
if (!groups || !Array.isArray(groups)) {
console.log('User does not have valid groups assigned');
return false;
}

return true;
if (groups.includes(GROUP_GENERATE) || groups.includes(GROUP_READONLY)) {
return true;
}
return false;
};

const authorize = async (req, res, session) => {
Expand Down Expand Up @@ -56,7 +52,7 @@ const authorize = async (req, res, session) => {
if (session && tokenResponse.access_token) {
modifiedSession.accessToken = tokenResponse.access_token;
const userInfo = await AuthService.requestUserInfo(modifiedSession.accessToken);
const isAllowed = await hasAllowedDomain(userInfo);
const isAllowed = await hasAllowedGroup(userInfo);
if (!isAllowed) {
return {
status: 401,
Expand Down Expand Up @@ -93,7 +89,7 @@ const authorize = async (req, res, session) => {

const checkExistingSession = async (req, res, session) => {
if (session && session.accessToken) {
const isAllowed = await hasAllowedDomain(session);
const isAllowed = await hasAllowedGroup(session);
if (!isAllowed) {
await AuthService.logoutFromIdentityProvider(session.accessToken);
return {
Expand All @@ -104,6 +100,7 @@ const checkExistingSession = async (req, res, session) => {
const response = {
isOk: true,
email: session.email,
groups: session.groups,
};
return {
status: 200,
Expand Down
65 changes: 58 additions & 7 deletions scripts/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const {
} = require('./joreStore');
const { downloadPostersFromCloud } = require('./cloudService');

const { REDIS_CONNECTION_STRING } = require('../constants');
const { REDIS_CONNECTION_STRING, GROUP_GENERATE } = require('../constants');

const PORT = 4000;

Expand Down Expand Up @@ -66,6 +66,16 @@ const errorHandler = async (ctx, next) => {
}
};

const allowedToGenerate = (user) => {
if (!user || !user.email) return false;

if (user.groups && user.groups.includes(GROUP_GENERATE)) {
return true;
}

return false;
};

const authMiddleware = async (ctx, next) => {
const endpointsNotRequiringAuthentication = ['/login', '/logout', '/session'];
if (endpointsNotRequiringAuthentication.includes(ctx.path)) {
Expand All @@ -82,6 +92,14 @@ const authMiddleware = async (ctx, next) => {
// Not authenticated, throw 401
ctx.throw(401);
} else {
// If the request is CRUD, check if the user has privileges to perform the action
if (ctx.method !== 'GET' && ctx.method !== 'HEAD') {
const user = authResponse.body;
if (!allowedToGenerate(user)) {
ctx.throw(403, 'User does not have privileges to perform this action.');
}
}

await next();
}
}
Expand All @@ -106,12 +124,40 @@ async function main() {
});

router.post('/builds', async (ctx) => {
const authResponse = await authEndpoints.checkExistingSession(
ctx.request,
ctx.response,
ctx.session,
);

if (!authResponse.body.isOk) {
ctx.throw(401, 'Not allowed.');
}

if (!authResponse.body.groups.includes(GROUP_GENERATE)) {
ctx.throw(403, 'User does not have permission to modify builds.');
}

const { title } = ctx.request.body;
const build = await addBuild({ title });
ctx.body = build;
});

router.put('/builds/:id', async (ctx) => {
const authResponse = await authEndpoints.checkExistingSession(
ctx.request,
ctx.response,
ctx.session,
);

if (!authResponse.body.isOk) {
ctx.throw(401, 'Not allowed.');
}

if (!authResponse.body.groups.includes(GROUP_GENERATE)) {
ctx.throw(403, 'User does not have permission to modify builds.');
}

const { id } = ctx.params;
const { status } = ctx.request.body;
const build = await updateBuild({
Expand Down Expand Up @@ -143,13 +189,18 @@ async function main() {
if (!authResponse.body.isOk) {
ctx.throw(401, 'Not allowed.');
}
const posters = [];
for (let i = 0; i < props.length; i++) {
// eslint-disable-next-line no-await-in-loop
const poster = await generatePoster(buildId, props[i]);
posters.push(poster);

if (!authResponse.body.groups.includes(GROUP_GENERATE)) {
ctx.throw(403, 'User does not have permission to generate posters.');
} else {
const posters = [];
for (let i = 0; i < props.length; i++) {
// eslint-disable-next-line no-await-in-loop
const poster = await generatePoster(buildId, props[i]);
posters.push(poster);
}
ctx.body = posters;
}
ctx.body = posters;
});

router.post('/cancelPoster', async (ctx) => {
Expand Down
13 changes: 8 additions & 5 deletions scripts/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,14 @@ async function renderComponent(options) {
timeout: 5 * 60000,
};

const contents = await page.pdf(printOptions);

await fs.outputFile(pdfPath(id), contents);
await page.close();
await uploadPosterToCloud(pdfPath(id));
try {
const contents = await page.pdf(printOptions);
await fs.outputFile(pdfPath(id), contents);
await page.close();
await uploadPosterToCloud(pdfPath(id));
} catch (e) {
throw new Error('PDF Generation failed', e);
}
}

async function renderComponentRetry(options) {
Expand Down
18 changes: 9 additions & 9 deletions src/components/labelPlacement/costFunctions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const OVERLAP_COST_FIXED = 6;
const OVERFLOW_COST = 10000;
const INTERSECTION_COST = 700;
const INTERSECTION_WITH_FIXED_COST = 25;
const DISTANCE_COST = 120;
const DISTANCE_COST = 70;
const ANGLE_COST = 1;
const ALPHA_COST = 25;

Expand Down Expand Up @@ -80,7 +80,7 @@ function getOverlapArea(a, b) {

function getPositionOverlapCost(positions, indexes, position) {
let overlap = 0;
indexes.forEach(j => {
indexes.forEach((j) => {
if (positions[j].allowCollision || (!positions[j].shouldBeVisible && positions[j].allowHidden))
return;
else if (j === position.index) return;
Expand All @@ -99,7 +99,7 @@ function getPositionOverlapCost(positions, indexes, position) {
*/
function getOverlapCost(positions, indexes, closeByPositions) {
let overlap = 0;
closeByPositions.forEach(position => {
closeByPositions.forEach((position) => {
if ((position.shouldBeVisible || !position.allowHidden) && !position.allowCollision) {
overlap += getPositionOverlapCost(positions, indexes, position);
}
Expand All @@ -125,9 +125,9 @@ function hasIntersectingLines(a, b) {
*/
function getIntersectionCost(positions, indexes, closeByPositions) {
let sum = 0;
closeByPositions.forEach(position => {
closeByPositions.forEach((position) => {
if (position.isFixed) return;
indexes.forEach(j => {
indexes.forEach((j) => {
if (positions[j].isFixed) return;
if (j >= position.index && indexes.includes(position.index)) return;
if (hasIntersectingLines(position, positions[j])) sum += 1;
Expand All @@ -140,7 +140,7 @@ function getPositionFixedIntersectionCost(positions, indexes, index) {
let sum = 0;
const position = positions[index];
if (position.allowCollision) return sum;
indexes.forEach(j => {
indexes.forEach((j) => {
if (positions[j].allowCollision) return;
if (j >= index && indexes.includes(index)) return;
// If both are dynamic or fixed, return
Expand All @@ -161,7 +161,7 @@ function getPositionFixedIntersectionCost(positions, indexes, index) {
const p3 = segseg(a0, a1, bl, br);
const p4 = segseg(a0, a1, tr, br);

const intersections = [p1, p2, p3, p4].filter(p => Array.isArray(p));
const intersections = [p1, p2, p3, p4].filter((p) => Array.isArray(p));

if (intersections.length === 2) {
const dx = intersections[0][0] - intersections[1][0];
Expand Down Expand Up @@ -197,7 +197,7 @@ function getDistanceCost(positions, indexes) {
return (
DISTANCE_COST *
indexes
.filter(index => !positions[index].isFixed)
.filter((index) => !positions[index].isFixed)
.reduce(
(prev, index) =>
prev +
Expand All @@ -220,7 +220,7 @@ function getAngleCost(positions, indexes) {
return (
ANGLE_COST *
indexes
.filter(index => !positions[index].isFixed)
.filter((index) => !positions[index].isFixed)
.reduce((prev, index) => {
const phi = Math.abs(positions[index].angle - positions[index].initialAngle) % 180;
return prev + (phi > 90 ? 180 - phi : phi) * positions[index].anglePriority;
Expand Down
Loading
Loading