This project contains the web interface to interact with the irmago MyIRMA backend server. In particular it allows users to
- edit the email addresses that are linked to the account;
- inspect a log of recent actions; and
- disable the user's Yivi app.
Instructions on how to run the MyIRMA backend server can be found in the irmago README.
In the project directory, you can run:
Runs the app in the development mode.
Open http://localhost:3000 to view it in a browser.
The page will reload if you make edits.
Lint is not run inline by the Vite dev server — use yarn lint (or the CI workflow) to surface ESLint and Stylelint findings.
If you want your local React app to communicate with the MyIRMA backend, you will face CORS issues in your browser. Even if you use a local setup, the MyIRMA backend server will use another port to host its endpoints on. You can either resolve this by running your browser in insecure mode, or you can check the running instructions for a combined local setup below.
Below some examples on how to start a browser in insecure mode.
-
MacOS
open -n -a /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --args --user-data-dir="/tmp/chrome-disable-web-security" --disable-web-security -
Linux
chromium --user-data-dir=/tmp/chromium-disable-web-security --disable-web-security
Warnings about using an unsupported browser option can be ignored.
Builds the app for production to the build folder.
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.
Your app is ready to be deployed!
You can easily test the React app using Docker and Go:
- Clone the irmago project and go to this directory in a shell
- Run
docker-compose up -dand wait until all services are running - Run
go install ./irma - Run
irma keyshare myirmaserver --static-path /path/to/irma_keyshare_webclient/build -c ./testdata/configurations/myirmaserver.yml - Open http://localhost:8081 to view it in a browser.
The deployed app reads its runtime config from public/config.js, which sets window.config. The file in this repo is the local-development template; deployments (e.g. the Kubernetes manifests in irma-keyshare-ops) overwrite it with environment-specific values.
The lang field decides which translation the app starts in. Starting in 4.0.0 the shipped template runs a small detectLanguage() helper at script-load time:
- If
localStorage.langholdsnloren(set by a previous click on the in-app language switcher), that wins. - Otherwise, walk
navigator.languagesin preference order and pick the first base subtag (nloren) we support. - Otherwise, fall back to
en.
This is a behavior change from earlier versions where lang was hard-coded to 'en'. Operators who want to keep the old "always start in English" behavior should replace the detectLanguage() call with a literal in the deployed config.js:
window.config = {
...
lang: 'en',
...
};The in-app EN/NL switcher in the header writes the user's pick to localStorage.lang regardless of how the initial language was chosen, so subsequent loads honour the explicit pick.
Three workflows split the work:
ci.ymlruns on every PR and every push tomaster. Four parallel jobs:lint,test,build,image-scan. The image-scan job builds the production Docker image locally and runsanchore/scan-action, but never pushes. This is the PR gate.delivery.ymlruns on push tomasterand onworkflow_dispatch. Builds, scans, and pushes:edgeto GHCR — same scan asci.yml/release.yml, withfail-build: truesince publishing has no PR-style escape hatch. Dispatch is restricted tomastervia a job-levelif: github.ref == 'refs/heads/master': re-publishing:edgefrom the currentmastercommit is supported, but dispatching from a PR branch causes the job to be skipped (the Actions UI renders skipped jobs distinctly from passing ones, so a stray "Run workflow" click can't masquerade as a successful republish). This keepsmasteras the single source of truth for the deployed:edgetag — for pre-merge end-to-end testing of a PR branch, build and push the image to your own GHCR namespace instead.release.ymlruns onrelease: published. Builds + scans + pushes:X.Y.Z,:X.Y,:X, and:latestbased on the release tag.
All three workflows run anchore/scan-action against the built image, with SARIF uploaded to GitHub Code Scanning. The scope of the build-failing gate is deliberately narrow — note what does not block merges or releases:
- Severity cutoff is
high. Moderate and low CVEs surface in Code Scanning but do not fail the build. Bumpseverity-cutoffin all three workflows if you want a stricter gate. only-fixed: true. Unfixable CVEs (no upstream patch available) are reported via SARIF but don't fail the build — blocking on something we can't actually patch isn't useful. They become "fixable" automatically once a fix is published.fail-buildis gated to non-PR events. PR runs inci.ymlalways upload a SARIF report but never block the PR on its content. The intent is that a freshly-disclosed upstream CVE shouldn't block every open PR until the base image is bumped. The gate is unconditional ondelivery.yml(master push or workflow_dispatch) andrelease.yml(published release) — anything that publishes to GHCR has to clear the bar.
The runtime base image is pinned by manifest-list digest (joseluisq/static-web-server:2.42.0@sha256:…) so an upstream silent re-tag of :latest can't shift scan results without a source change — bump both the tag and the digest together when reviewing CVE fixes. The build-stage image (node:24-alpine) is intentionally not digest-pinned: its output never ships, and pinning would slow CVE patches in the build toolchain without changing the scanned artefact.