Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 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
510eb00
AB#63130: Update React and other dependencies, fix deprecation errors…
e-halinen Aug 19, 2025
2a05a6c
Merge branch 'master' into merge_master_into_dev
avicarie Sep 17, 2025
4c8e01a
v1.2.0
avicarie Sep 17, 2025
0532cf3
Merge pull request #137 from HSLdevcom/merge_master_into_dev
avicarie Sep 17, 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=
4 changes: 2 additions & 2 deletions .env.prod
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ API_CLIENT_ID=2411181397763460
API_CLIENT_SECRET=
REDIRECT_URI=https://kartat.hsl.fi/kartta
LOGIN_PROVIDER_URI=https://id.hsl.fi
DOMAINS_ALLOWED_TO_GENERATE=
DOMAINS_ALLOWED_TO_LOGIN=
GROUP_GENERATE=
GROUP_READONLY=
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:16-buster-slim
FROM node:20-bookworm-slim

# This installs the necessary libs to make the bundled version of Chromium that Pupppeteer installs work
RUN apt-get update \
Expand All @@ -12,7 +12,7 @@ RUN apt-get update \
&& mv ./azcopy /usr/bin/ \
&& rm -rf /var/lib/apt/lists/*

ENV WORK /opt/publisher
ENV WORK=/opt/publisher
# ENV NODE_ENV production # Cannot use until devdependency list is fixed in package.json

# Create app directory
Expand Down
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 || '',
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hsl-routemap-server",
"version": "1.1.0",
"version": "1.2.0",
"description": "HSL Routemap server",
"main": "index.js",
"scripts": {
Expand Down
34 changes: 12 additions & 22 deletions scripts/auth/authEndpoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,19 @@ 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 groups = get(userInfo, 'groups');

const emailValidationOptions = {
host_whitelist: allowedDomains,
};

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

if (
!validator.isEmail(userInfo.email, emailValidationOptions) &&
!groups.includes(ROUTEMAP_TEST_GROUP)
) {
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 @@ -63,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 @@ -100,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 @@ -111,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
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