Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
50 changes: 50 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Stepup-Middleware — Claude Instructions

## Commit Rules

- **Never add Co-Authored-By lines** to commits.
- Make atomic commits — one logical change per commit.

## Definition of Done

Before marking any task complete, run the full CI suite:

```bash
# Static analysis
php vendor/bin/phpstan analyse --memory-limit=-1 --no-ansi -c ./ci/qa/phpstan.neon

# Code style
./ci/qa/phpcs

# Unit + integration tests
php vendor/bin/phpunit --configuration=ci/qa/phpunit.xml

# Database integration tests
php vendor/bin/phpunit --configuration=ci/qa/phpunit.xml --testsuite=database
```

All must pass (phpstan: no errors, phpcs: no errors, phpunit: no new failures beyond pre-existing PHP 8.5/Mockery deprecation errors).

## Known Pre-existing Test Failures

75 unit tests fail due to PHP 8.5 + PHPUnit 10 treating Mockery implicit nullable parameter deprecations as errors (`PHPUnit\Framework\Exception: PHP Deprecated: Mockery::formatArgs()`). These are in the Mockery library itself and unrelated to application code. Do not attempt to fix them without updating Mockery.

## Project Overview

Symfony 6/7 event-sourced middleware application (Broadway). Two databases: `middleware` (read models + event store) and `gateway` (SP/IdP SAML entities).

## Test Environment

- Uses SQLite in test env (see `config/packages/doctrine.yaml` `when@test`)
- `event_stream` table is not managed by Doctrine ORM — must be created manually in tests that process commands
- Gateway schema must be initialized separately from middleware schema

## Running Tests

```bash
# Single test file
php vendor/bin/phpunit -c ci/qa/phpunit.xml path/to/Test.php

# Full unit suite
php vendor/bin/phpunit -c ci/qa/phpunit.xml --testsuite unit
```
18 changes: 0 additions & 18 deletions ci/qa/phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,6 @@ parameters:
count: 1
path: ../../src/Surfnet/Stepup/Configuration/Configuration.php

-
message: '#^Parameter \#2 \$emailTemplates of class Surfnet\\Stepup\\Configuration\\Event\\EmailTemplatesUpdatedEvent constructor expects array, mixed given\.$#'
identifier: argument.type
count: 1
path: ../../src/Surfnet/Stepup/Configuration/Configuration.php

-
message: '#^Parameter \#2 \$identityProviders of class Surfnet\\Stepup\\Configuration\\Event\\IdentityProvidersUpdatedEvent constructor expects array, mixed given\.$#'
identifier: argument.type
Expand All @@ -90,12 +84,6 @@ parameters:
count: 1
path: ../../src/Surfnet/Stepup/Configuration/Configuration.php

-
message: '#^Parameter \#2 \$sraaList of class Surfnet\\Stepup\\Configuration\\Event\\SraaUpdatedEvent constructor expects array, mixed given\.$#'
identifier: argument.type
count: 1
path: ../../src/Surfnet/Stepup/Configuration/Configuration.php

-
message: '#^Property Surfnet\\Stepup\\Configuration\\Configuration\:\:\$configuration type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
Expand Down Expand Up @@ -4086,12 +4074,6 @@ parameters:
count: 1
path: ../../src/Surfnet/StepupMiddleware/ManagementBundle/Validator/ConfigurationStructureValidator.php

-
message: '#^Method Surfnet\\StepupMiddleware\\ManagementBundle\\Validator\\ConfigurationStructureValidator\:\:validateSraaConfiguration\(\) has parameter \$configuration with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: ../../src/Surfnet/StepupMiddleware/ManagementBundle/Validator/ConfigurationStructureValidator.php

-
message: '#^Parameter \#1 \$configuration of method Surfnet\\StepupMiddleware\\ManagementBundle\\Validator\\ConfigurationStructureValidator\:\:validateRoot\(\) expects array, mixed given\.$#'
identifier: argument.type
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<p>Dear {{ commonName }},</p>

<p>Thank you for registering your token. Please visit this link to verify your email address:</p>

<p><a href='{{ verificationUrl }}'>{{ verificationUrl }}</a></p>

<p>If you can not click on the URL, please copy the link and paste it in the address bar of your browser.</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<p>Dear {{ commonName }},</p>

<p>Thank you for registering a recovery method. You can use this method if you want to reactivate a token that you have lost.</p>

<p>Always make sure you have at least one recovery method available.</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<p>Dear {{ commonName }},</p>

<p>
{% if isRevokedByRa %}
Your recovery method was removed by an administrator.
{% else %}
Your recovery method has been removed. Please contact your institution's helpdesk immediately if you did not do this yourself, as this could mean that your account has been compromised.
{% endif %}
</p>

<p>You can no longer use this recovery method to activate a token.</p>

<p>Always make sure you have at least one recovery method available.</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<p>Dear {{ commonName }},</p>

<p>Thank you for registering your token. Please visit one of the locations below within 14 days to get your token activated. After {{ expirationDate | format_date('full', locale=locale) }} your activation code is no longer valid.</p>

<p>Please bring the following:</p>
<ul>
<li>Your token</li>
<li>A valid proof of identity (passport, drivers license or national ID-card)</li>
<li>The activation code from this e-mail</li>
</ul>

<p style='font-size: 150%; text-align: center'>Activation code: <code>{{ registrationCode }}</code></p>

<p>Location(s) to activate your token:</p>
{% if raLocations is empty %}
<p>No locations known.</p>
{% else %}
<ul>
{% for ra in raLocations %}
<li><address>
<strong>{{ ra.name }}</strong><br />
{{ ra.location }}<br />
{{ ra.contactInformation }}
</address></li>
{% endfor %}
</ul>
{% endif %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<p>Dear {{ commonName }},</p>

<p>Thank you for registering your token. Please visit one of the locations below within 14 days to get your token activated. After {{ expirationDate | format_date('full', locale=locale) }} your activation code is no longer valid.</p>

<p>Please bring the following:</p>
<ul>
<li>Your token</li>
<li>A valid proof of identity (passport, drivers license or national ID-card)</li>
<li>The activation code from this e-mail</li>
</ul>

<p style='font-size: 150%; text-align: center'>Activation code: <code>{{ registrationCode }}</code></p>

<p>Location(s) to activate your token:</p>
{% if ras is empty %}
<p>No RAs are known.</p>
{% else %}
<ul>
{% for ra in ras %}
<li><address>
<strong>{{ ra.commonName }}</strong><br />
{{ ra.location }}<br />
{{ ra.contactInformation }}
</address></li>
{% endfor %}
</ul>
{% endif %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<p>Dear {{ commonName }},</p>

<p>
{% if isRevokedByRa %}
The registration of your {{ tokenType }} with ID {{ tokenIdentifier }} was deleted by an administrator.
{% else %}
You have deleted the registration of your {{ tokenType }} token with ID {{ tokenIdentifier }}. If you did not delete your token you must immediately contact the support desk of your institution, as this may indicate that your account has been compromised.
{% endif %}
</p>

<p>You can no longer use this token to access services that require two-step authentication.</p>

<p>Do you want to replace your token? Please visit <a href='{{ selfServiceUrl }}'>{{ selfServiceUrl }}</a> and register a new token.</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<p>Dear {{ commonName }},</p>

<p>You have registered, but not yet activated, a token. Please visit one of the locations below to get your token activated. After {{ expirationDate | format_date('full', locale=locale) }} your activation code is no longer valid.</p>

<p>Please bring the following:</p>
<ul>
<li>Your token</li>
<li>A valid proof of identity (passport, drivers license or national ID-card)</li>
<li>The activation code from this e-mail</li>
</ul>

<p style='font-size: 150%; text-align: center'>Activation code: <code>{{ registrationCode }}</code></p>

<p>Location(s) to activate your token:</p>
{% if raLocations is empty %}
<p>No locations known.</p>
{% else %}
<ul>
{% for ra in raLocations %}
<li><address>
<strong>{{ ra.name }}</strong><br />
{{ ra.location }}<br />
{{ ra.contactInformation }}
</address></li>
{% endfor %}
</ul>
{% endif %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<p>Dear {{ commonName }},</p>

<p>You have registered, but not yet activated, a token. Please visit one of the locations below to get your token activated. After {{ expirationDate | format_date('full', locale=locale) }} your activation code is no longer valid.</p>

<p>Please bring the following:</p>
<ul>
<li>Your token</li>
<li>A valid proof of identity (passport, drivers license or national ID-card).</li>
<li>The activation code from this e-mail</li>
</ul>

<p style='font-size: 150%; text-align: center'>Activation code: <code>{{ registrationCode }}</code></p>

<p>Location(s) to activate your token:</p>
{% if ras is empty %}
<p>No RAs are known.</p>
{% else %}
<ul>
{% for ra in ras %}
<li><address>
<strong>{{ ra.commonName }}</strong><br />
{{ ra.location }}<br />
{{ ra.contactInformation }}
</address></li>
{% endfor %}
</ul>
{% endif %}
19 changes: 19 additions & 0 deletions config/openconext/email_templates/en_GB/vetted.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<p>Dear {{ commonName }},</p>

<p>Thank you for registering your token. Your token is ready to use. You can use this token for services connected to SURFconext that require two-step authentication. This e-mail contains more info on how to use your token.</p>

<p><strong>Handle your token with care</strong></p>
<ul>
<li>Your token is private, do not share your token with others</li>
<li>Never leave your token unattended</li>
<li>Lock your phone, e.g. with a code or fingerprint</li>
</ul>

<p><strong>Token lost?</strong><br />
Did you lose your token? Please visit <a href='{{ selfServiceUrl }}'>{{ selfServiceUrl }}</a> and remove your token registration. This way no one can take advantage of your token.</p>

<p><strong>Replace token</strong><br />
Do you want to replace your token? Please visit <a href='{{ selfServiceUrl }}'>{{ selfServiceUrl }}</a>, remove your activated token and start the token registration process again.</p>

<p><strong>Test token</strong><br />
Do you want to test your token? Please visit <a href='{{ selfServiceUrl }}'>{{ selfServiceUrl }}</a> and select the "Test" button next to the token you want to test.</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<p>Beste {{ commonName }},</p>

<p>Bedankt voor het registreren van je token. Klik op onderstaande link om je e-mailadres te bevestigen:</p>

<p><a href='{{ verificationUrl }}'>{{ verificationUrl }}</a></p>

<p>Is klikken op de link niet mogelijk? Kopieer dan de link en plak deze in de adresbalk van je browser.</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<p>Beste {{ commonName }},</p>

<p>Bedankt voor het registreren een herstelmethode. Je kunt deze methode gebruiken wanneer je een token dat je verloren bent opnieuw wilt activeren.</p>

<p>Zorg er altijd voor dat je tenminste één herstelmethode beschikbaar hebt.</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<p>Beste {{ commonName }},</p>

<p>
{% if isRevokedByRa %}
Je herstelmethode is verwijderd door een beheerder.
{% else %}
Je herstelmethode is verwijderd. Neem direct contact op met de helpdesk van je instelling als je dit niet zelf gedaan hebt, omdat dit kan betekenen dat je account gecompromitteerd is.
{% endif %}
</p>

<p>Je kunt deze herstelmethode niet meer gebruiken om een token te activeren.</p>

<p>Zorg er altijd voor dat je tenminste één herstelmethode beschikbaar hebt.</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<p>Beste {{ commonName }},</p>

<p>Bedankt voor het registreren van je token. Ga binnen 14 dagen naar een van de onderstaande locaties om je token te laten activeren. Je activatiecode is geldig tot en met {{ expirationDate | format_date('full', locale=locale) }}.</p>

<p>Neem daarbij het volgende mee:</p>
<ul>
<li>Je token</li>
<li>Een geldig legitimatiebewijs (paspoort, rijbewijs of nationale ID-kaart)</li>
<li>De activatiecode uit deze e-mail</li>
</ul>

<p style='font-size: 150%; text-align: center'>Activatiecode: <code>{{ registrationCode }}</code></p>

<p>Locatie(s) om je token te activeren:</p>
{% if raLocations is empty %}
<p>Er zijn geen Locaties bekend.</p>
{% else %}
<ul>
{% for ra in raLocations %}
<li><address>
<strong>{{ ra.name }}</strong><br />
{{ ra.location }}<br />
{{ ra.contactInformation }}
</address></li>
{% endfor %}
</ul>
{% endif %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<p>Beste {{ commonName }},</p>

<p>Bedankt voor het registreren van je token. Ga binnen 14 dagen naar een van de onderstaande locaties om je token te laten activeren. Je activatiecode is geldig tot en met {{ expirationDate | format_date('full', locale=locale) }}.</p>

<p>Neem daarbij het volgende mee:</p>
<ul>
<li>Je token</li>
<li>Een geldig legitimatiebewijs (paspoort, rijbewijs of nationale ID-kaart)</li>
<li>De activatiecode uit deze e-mail</li>
</ul>

<p style='font-size: 150%; text-align: center'>Activatiecode: <code>{{ registrationCode }}</code></p>

<p>Locatie(s) om je token te activeren:</p>
{% if ras is empty %}
<p>Er zijn geen RAs bekend.</p>
{% else %}
<ul>
{% for ra in ras %}
<li><address>
<strong>{{ ra.commonName }}</strong><br />
{{ ra.location }}<br />
{{ ra.contactInformation }}
</address></li>
{% endfor %}
</ul>
{% endif %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<p>Beste {{ commonName }},</p>

<p>
{% if isRevokedByRa %}
De registratie van je {{ tokenType }} token met ID {{ tokenIdentifier }} is verwijderd door een beheerder.
{% else %}
Je hebt de registratie voor je {{ tokenType }} token met ID {{ tokenIdentifier }} verwijderd. Neem direct contact op met de helpdesk van je instelling als je dit zelf niet gedaan hebt, omdat dit kan betekenen dat je account gecompromitteerd is.
{% endif %}
</p>

<p>Je kunt dit token niet meer gebruiken om in te loggen bij services die een tweede inlogstap vereisen.</p>

<p>Wil je een nieuw token aanvragen? Ga dan naar <a href='{{ selfServiceUrl }}'>{{ selfServiceUrl }}</a> en doorloop het registratieproces opnieuw.</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<p>Beste {{ commonName }},</p>

<p>Je hebt een token geregistreerd, maar het nog niet laten activeren. Je kunt tot en met {{ expirationDate | format_date('full', locale=locale) }} bij een van de onderstaande locaties terecht om je token te laten activeren.</p>

<p>Neem daarbij het volgende mee:</p>
<ul>
<li>Je token</li>
<li>Een geldig legitimatiebewijs (paspoort, rijbewijs of nationale ID-kaart)</li>
<li>De activatiecode uit deze e-mail</li>
</ul>

<p style='font-size: 150%; text-align: center'>Activatiecode: <code>{{ registrationCode }}</code></p>

<p>Locatie(s) om je token te activeren:</p>
{% if raLocations is empty %}
<p>Er zijn geen Locaties bekend.</p>
{% else %}
<ul>
{% for ra in raLocations %}
<li><address>
<strong>{{ ra.name }}</strong><br />
{{ ra.location }}<br />
{{ ra.contactInformation }}
</address></li>
{% endfor %}
</ul>
{% endif %}
Loading