Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4d8ed80
feat: Add MultiFactor Authentication
matiasperrone-exo Apr 16, 2026
a682489
chore: Add PR's requested changes and additional AI comments
matiasperrone-exo Apr 24, 2026
c2e5389
chore: Add PR's requested changes
matiasperrone-exo Apr 29, 2026
9ecc006
chore: Add guards on setEventType and setMethod methods on TwoFactorA…
matiasperrone-exo Apr 29, 2026
194adbc
chore: Guard the unique-index migration against existing duplicates.
matiasperrone-exo Apr 29, 2026
8bc5a70
chore: Add UniqueConstraint annotation to mirror the database migration
matiasperrone-exo Apr 29, 2026
a2c2e84
chore: Refactor UserRecoveryCode and TwoFactorAuditLog models; update…
matiasperrone-exo May 14, 2026
856f031
chore: remove BaseEntity oveloaded props and methods. fix skipped test
matiasperrone-exo May 18, 2026
4d8e6a5
chore: unify migrations
matiasperrone-exo May 21, 2026
6607c19
Feature | Implement Two-Factor Authentication (2FA) support for users…
matiasperrone-exo Aug 11, 2026
0359d77
MFA hardening: OAuth2 memento guard on recovery, DTO refactors, full …
smarcet Aug 12, 2026
236ddef
fix(session): restore Secure and SameSite=None defaults for the sessi…
smarcet Sep 22, 2026
f2d8521
fix(auth): split the login SPA reset from cancelLogin to keep OAuth2 …
smarcet Sep 22, 2026
7f00d36
fix(2fa): reserve verify/recovery attempts atomically before the cont…
smarcet Sep 22, 2026
d1a91df
chore(auth): drop unused imports from MFACookieManager
smarcet Sep 22, 2026
01da08a
fix(2fa): require CSRF on the profile 2FA enable and recovery-code re…
smarcet Sep 22, 2026
317a87c
fix(2fa): make the pending MFA session TTL configurable, default 600s
smarcet Sep 22, 2026
c92f563
fix(tests): drop stale OAuth2ProtocolTestCase.php entry from phpunit.xml
smarcet Sep 22, 2026
e72bc80
fix(e2e): assert the intended post-error password step, not the old r…
smarcet Sep 22, 2026
c19f19a
fix: MFA-enforced accounts leak enumeration via passwordless login gu…
smarcet Sep 23, 2026
807befd
Recovery code login flow UI (#151)
romanetar Sep 23, 2026
9f24344
chore: fix ssl redirect
smarcet Sep 23, 2026
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
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ CACHE_DRIVER=redis

SESSION_DRIVER=redis
SESSION_COOKIE_DOMAIN=
SESSION_COOKIE_SECURE=false
SESSION_SECURE_COOKIE=false
SESSION_COOKIE_SAME_SITE=lax

QUEUE_DRIVER=database
QUEUE_CONN=
Expand Down
139 changes: 139 additions & 0 deletions .github/workflows/pull_request_frontend_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
name: Front End Tests On Pull Request

on:
pull_request:
types: [opened, reopened, edited, synchronize]
branches: ["main"]

jobs:

js-unit-tests:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'yarn'
- name: Install JS dependencies
run: yarn install --frozen-lockfile
- name: Run Jest unit tests
run: yarn test:unit:ci
- name: Upload Jest coverage
uses: actions/upload-artifact@v4
if: always()
with:
name: jest-coverage
path: tests/js/coverage
retention-days: 5

e2e-tests:
runs-on: ubuntu-latest
env:
APP_ENV: testing
APP_DEBUG: true
APP_KEY: base64:4vh0op/S1dAsXKQ2bbdCfWRyCI9r8NNIdPXyZWt9PX4=
APP_URL: http://localhost:8001
DEV_EMAIL_TO: smarcet@gmail.com
DB_CONNECTION: mysql
DB_HOST: 127.0.0.1
DB_PORT: 3306
DB_DATABASE: idp_test
DB_USERNAME: root
DB_PASSWORD: 1qaz2wsx
REDIS_HOST: 127.0.0.1
REDIS_PORT: 6379
REDIS_DB: 0
REDIS_PASSWORD: 1qaz2wsx
REDIS_DATABASES: 16
SSL_ENABLED: false
SESSION_DRIVER: redis
SESSION_SECURE_COOKIE: false
SESSION_COOKIE_SAME_SITE: lax
PHP_VERSION: 8.3
OTEL_SDK_DISABLED: true
OTEL_SERVICE_ENABLED: false
TURNSTILE_SITE_KEY: ${{ secrets.TURNSTILE_SITE_KEY }}
TURNSTILE_SECRET_KEY: ${{ secrets.TURNSTILE_SECRET_KEY }}
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: 1qaz2wsx
MYSQL_DATABASE: idp_test
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=3
steps:
- name: Create Redis
uses: supercharge/redis-github-action@1.8.1
with:
redis-port: 6379
redis-password: 1qaz2wsx
- name: Check out repository code
uses: actions/checkout@v4
- name: Install PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ env.PHP_VERSION }}
extensions: pdo_mysql, mbstring, exif, pcntl, bcmath, sockets, gettext, apcu
- name: Install PHP dependencies
uses: ramsey/composer-install@v3
env:
COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.PAT }}"} }'
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'yarn'
- name: Install JS dependencies
run: yarn install --frozen-lockfile
- name: Build frontend assets
run: yarn build
- name: Prepare application
run: |
./update_doctrine.sh
php artisan doctrine:migrations:migrate --no-interaction
php artisan db:seed --force
php artisan idp:create-super-admin test@test.com '1Qaz2wsx!'
php artisan idp:create-raw-user e2e@test.com '1Qaz2wsx!'
for i in 001 002 003 004 005 006 007 008 009 010 011 012; do
php artisan idp:create-super-admin "mfa-ts-$i@test.com" '1Qaz2wsx!'
done
php artisan idp:create-super-admin mfa-oauth2@test.com '1Qaz2wsx!'
php artisan idp:create-super-admin mfa-oauth2-consent@test.com '1Qaz2wsx!'
php artisan idp:create-super-admin mfa-oauth2-trust@test.com '1Qaz2wsx!'
php artisan idp:create-oauth2-test-client
- name: Install Playwright Chromium
run: npx playwright install --with-deps chromium
- name: Start web server
run: php artisan serve --host=127.0.0.1 --port=8001 &
- name: Wait for server to be ready
run: |
for i in $(seq 1 20); do
curl -sf http://localhost:8001 > /dev/null 2>&1 && echo "Server ready" && exit 0
sleep 2
done
echo "Server did not start in time" && exit 1
- name: Run E2E tests
run: yarn test:e2e --reporter=list
- name: Upload Playwright report
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: tests/e2e/report
retention-days: 7
- name: Upload Playwright traces
uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-traces
path: test-results/
retention-days: 7
2 changes: 2 additions & 0 deletions .github/workflows/pull_request_unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ jobs:
PHP_VERSION: 8.3
OTEL_SDK_DISABLED: true
OTEL_SERVICE_ENABLED: false
TURNSTILE_SITE_KEY: ${{ secrets.TURNSTILE_SITE_KEY }}
TURNSTILE_SECRET_KEY: ${{ secrets.TURNSTILE_SECRET_KEY }}
services:
mysql:
image: mysql:8.0
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ jobs:
PHP_VERSION: 8.3
OTEL_SDK_DISABLED: true
OTEL_SERVICE_ENABLED: false
TURNSTILE_SITE_KEY: ${{ secrets.TURNSTILE_SITE_KEY }}
TURNSTILE_SECRET_KEY: ${{ secrets.TURNSTILE_SECRET_KEY }}
services:
mysql:
image: mysql:8.0
Expand Down
140 changes: 140 additions & 0 deletions .github/workflows/push_frontend_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
name: Front End Tests On Push

on: push

jobs:

js-unit-tests:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'yarn'
- name: Install JS dependencies
run: yarn install --frozen-lockfile
- name: Run Jest unit tests
run: yarn test:unit:ci
- name: Upload Jest coverage
uses: actions/upload-artifact@v4
if: always()
with:
name: jest-coverage
path: tests/js/coverage
retention-days: 5

e2e-tests:
runs-on: ubuntu-latest
env:
APP_ENV: testing
APP_DEBUG: true
APP_KEY: base64:4vh0op/S1dAsXKQ2bbdCfWRyCI9r8NNIdPXyZWt9PX4=
APP_URL: http://localhost:8001
DEV_EMAIL_TO: smarcet@gmail.com
DB_CONNECTION: mysql
DB_HOST: 127.0.0.1
DB_PORT: 3306
DB_DATABASE: idp_test
DB_USERNAME: root
DB_PASSWORD: 1qaz2wsx
REDIS_HOST: 127.0.0.1
REDIS_PORT: 6379
REDIS_DB: 0
REDIS_PASSWORD: 1qaz2wsx
REDIS_DATABASES: 16
SSL_ENABLED: false
SESSION_DRIVER: redis
SESSION_SECURE_COOKIE: false
SESSION_COOKIE_SAME_SITE: lax
PHP_VERSION: 8.3
OTEL_SDK_DISABLED: true
OTEL_SERVICE_ENABLED: false
TURNSTILE_SITE_KEY: ${{ secrets.TURNSTILE_SITE_KEY }}
TURNSTILE_SECRET_KEY: ${{ secrets.TURNSTILE_SECRET_KEY }}
# `php artisan serve` (below) is PHP's built-in single-threaded dev server —
# it can only handle one request at a time, so Playwright workers must stay
# at 1 here or concurrent page loads queue up and blow the 30s test timeout.
PLAYWRIGHT_WORKERS: 1
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: 1qaz2wsx
MYSQL_DATABASE: idp_test
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=3
steps:
- name: Create Redis
uses: supercharge/redis-github-action@1.8.1
with:
redis-port: 6379
redis-password: 1qaz2wsx
- name: Check out repository code
uses: actions/checkout@v4
- name: Install PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ env.PHP_VERSION }}
extensions: pdo_mysql, mbstring, exif, pcntl, bcmath, sockets, gettext, apcu
- name: Install PHP dependencies
uses: ramsey/composer-install@v3
env:
COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.PAT }}"} }'
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'yarn'
- name: Install JS dependencies
run: yarn install --frozen-lockfile
- name: Build frontend assets
run: yarn build
- name: Prepare application
run: |
./update_doctrine.sh
php artisan doctrine:migrations:migrate --no-interaction
php artisan db:seed --force
php artisan idp:create-super-admin test@test.com '1Qaz2wsx!'
php artisan idp:create-raw-user e2e@test.com '1Qaz2wsx!'
for i in 001 002 003 004 005 006 007 008 009 010 011 012; do
php artisan idp:create-super-admin "mfa-ts-$i@test.com" '1Qaz2wsx!'
done
php artisan idp:create-super-admin mfa-oauth2@test.com '1Qaz2wsx!'
php artisan idp:create-super-admin mfa-oauth2-consent@test.com '1Qaz2wsx!'
php artisan idp:create-super-admin mfa-oauth2-trust@test.com '1Qaz2wsx!'
php artisan idp:create-oauth2-test-client
- name: Install Playwright Chromium
run: npx playwright install --with-deps chromium
- name: Start web server
run: php artisan serve --host=127.0.0.1 --port=8001 &
- name: Wait for server to be ready
run: |
for i in $(seq 1 20); do
curl -sf http://localhost:8001 > /dev/null 2>&1 && echo "Server ready" && exit 0
sleep 2
done
echo "Server did not start in time" && exit 1
- name: Run E2E tests
run: yarn test:e2e --reporter=list
- name: Upload Playwright report
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: tests/e2e/report
retention-days: 7
- name: Upload Playwright traces
uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-traces
path: test-results/
retention-days: 7
13 changes: 12 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,15 @@ model.sql
/.phpunit.cache/
docker-compose/mysql/model/*.sql
public/assets/*.map
public/assets/css/*.map
public/assets/css/*.map
.codegraph
docs/plans

# Playwright
/tests/e2e/report/
/test-results/

# Jest
/tests/js/coverage/
/playwright-report/
/.playwright-out/
Loading
Loading