Skip to content

Release 0.2.0 - #1

Open
GuilhermeBarile wants to merge 217 commits into
masterfrom
devel
Open

GuilhermeBarile wants to merge 217 commits into
masterfrom
devel

Conversation

@GuilhermeBarile

@GuilhermeBarile GuilhermeBarile commented Feb 25, 2026 •

Copy link
Copy Markdown

Release 0.2.0

This is a breaking rewrite of the package compared with master / 0.1.x. It introduces a provider-based auth architecture, database-backed roles and relations, OAuth/session hardening, a reusable user-management API, and modern CI/test coverage.

No automatic data migration or backward compatibility is provided. Existing 0.1.x applications must follow the documented manual migration and rollout procedure in docs/UPGRADING-0.2.0.md.

Breaking changes

  • PHP requirement is now >=8.2 (previously >=5.3).
  • Objectiveweb\Auth is now an abstract provider contract instead of a PDO-backed concrete implementation.
  • Direct PDO/MySQL-specific persistence in Auth has been removed.
  • Database authentication now lives in Objectiveweb\Auth\DBAuth and uses objectiveweb/db ^0.8.0.
  • BasicAuth is now a complete in-memory provider for tests/dev/small setups.
  • The old Objectiveweb\Auth\AuthController was replaced by controllers under Objectiveweb\Auth\Controller\*.
  • PasswordMismatchException was removed; authentication failures use the current auth/user exception model.
  • The legacy password compatibility file (src/password.php) was removed.
  • Composer PSR-4 autoloading is now scoped to Objectiveweb\Auth\.
  • The user model changed from a single username-centric record to users plus provider credentials (provider + uid).

Provider architecture

DBAuth

Added DBAuth as the database-backed provider:

  • user registration, lookup, update and deletion;
  • password login and password reset;
  • multiple credentials per user;
  • configurable login and recovery providers;
  • credential creation, rename and deletion;
  • UUID generation;
  • account lifecycle via disabled_at;
  • token expiration via token_expires_at / configurable TTL;
  • role storage and lookup;
  • application-defined session context via user_context_callback;
  • authorization relations/delegations;
  • eager relation hydration;
  • audit, invitation/reset and deletion-guard callbacks;
  • database prefix compatibility through objectiveweb/db;
  • DB::table()-backed query pagination/counting/sorting with native Collection results;
  • portable role lookup without database-specific GROUP_CONCAT.

BasicAuth

Added a full in-memory provider implementing the same practical contract as DBAuth:

  • registration/login/password reset;
  • querying with the same Collection return contract as DBAuth, plus user updates;
  • credential management;
  • roles;
  • lifecycle state;
  • token handling;
  • deletion guards;
  • session context hydration and refresh using the shared Auth callback;
  • consistent credential rename normalization and metadata preservation.

Authentication and session security

  • Session IDs are regenerated when authentication is established.

  • Password and OAuth login share the same session-establishment path.

  • Suspended users cannot authenticate.

  • Existing sessions are revalidated so role/lifecycle changes take effect on protected requests.

  • Public user payloads are centrally sanitized so password hashes, reset tokens and token-expiry internals are not exposed.

  • Registration, reset, query, invitation callbacks and duplicate-credential errors now return sanitized user data.

  • Password-reset tokens are stored as indexed SHA-256 digests of high-entropy one-time tokens and support expiration.

  • Password-reset token consumption is atomic: compare-and-swap updates the password and clears the token in one conditional statement.

  • Password login returns a generic HTTP 401 for missing identities, incorrect passwords and suspended accounts, reducing username enumeration.

  • Each provider credential has separate verification state and expiring, single-use hashed tokens. OAuth associations are verified only with provider-specific proof.

  • Anonymous password-recovery responses are indistinguishable for known and unknown UIDs; recovery callbacks are delivery-only and cannot alter the response.

  • Authenticated password changes require the existing password, verified through the provider; passwordless/OAuth-only accounts must use an independently verified reset or reauthentication flow.

  • Authenticated password changes require session-bound CSRF via X-CSRF-Token and JSON HTTP requests.

  • Logout redirects reject external, scheme-relative, backslash and control-character forms before ending the session.

  • Random token generation now uses cryptographically secure randomness.

  • user_context_callback loads a sanitized user's application context at login and recomputes it on session revalidation.

Authorization

Added role- and relation-based authorization:

  • Auth::ANONYMOUS, AUTHENTICATED and ALL access markers;
  • RequireRole router middleware;
  • role-aware session revalidation;
  • user_can() checks for global roles, delegated users and arbitrary resource relations;
  • resource-ID listing for relation abilities;
  • configurable ability- or role-based relation mappings;
  • eager relation loading scoped to the current subject user;
  • application-defined authorization context loaded into the session and refreshed on revalidation.

User-management API

Added Objectiveweb\Auth\Controller\UserController, protected by the admin role, with support for:

  • paginated/searchable user listing returned directly as Objectiveweb\DB\Collection;
  • role and status filtering;
  • whitelisted sorting;
  • user detail responses;
  • user creation/invitation;
  • profile and role updates;
  • credential create/rename/delete;
  • suspend/activate;
  • password-reset and invite actions;
  • application-owned association payloads are rejected and must be handled by application controllers;
  • guarded deletion and cleanup of Auth-owned authorization relations only.

Management hardening includes:

  • JSON + session-bound CSRF token enforcement for HTTP writes;
  • prevention of self-deletion/self-suspension;
  • prevention of removing the current user's own admin role;
  • prevention of deleting/suspending the final active administrator;
  • suspended admins are not counted as active administrators;
  • strict role payload validation;
  • credential-only fields such as provider / profile are rejected on normal user updates;
  • secret fields are removed from management responses.

Auth controller

The new Objectiveweb\Auth\Controller\AuthController separates authentication concerns from admin user management and supports:

  • registration with configurable registration scope, restricted to trusted local credentials and safe profile fields;
  • login/logout;
  • logout redirects restricted to same-origin absolute paths (/path, never //host or external URLs);
  • current-user/session responses;
  • password recovery/reset tokens;
  • callbacks for registration/recovery flows.

OAuth

Added/reworked OAuthController with:

  • external provider authentication;
  • Real-provider interoperability coverage: dedicated oauth-real-providers CI job installs optional League Google/Facebook packages and runs actual provider authorization, PKCE, mocked-HTTP token/userinfo exchange, first-time account choice, returning login, explicit linking, and negative state/token-error cases. No live provider credentials are used;
  • League resource-owner interoperability: only getId() and toArray() are assumed; name, avatar and email hints use safe optional getters with validated profile-field fallbacks. Invalid/missing data is ignored, without changing provider-subject verification or account-linking rules;
  • provider credential linking;
  • explicit account linking via authenticated, CSRF-protected JSON OAuthController::postLink($provider) returning authorization_url; ordinary sign-in never silently attaches new credentials or confirms pending links;
  • provider-subject-based credential linking only; matching emails never attach an external identity to an existing account;
  • First-time OAuth account choice: unknown provider subjects remain pending (10-minute session proof); no local account is created on the callback. The frontend may explicitly create an OAuth-only account or sign in with an existing email/password and link the provider identity. Account choices are CSRF-protected and single-use.
  • an example account-choice UI at examples/oauth-account-choice.html with configurable same-origin redirect and application-routed getPendingAccount, postCreateAccount, postExistingAccount, postCancelPendingAccount endpoints;
  • linking OAuth credentials to an already authenticated user;
  • suspended-user enforcement;
  • session-ID regeneration;
  • single-use OAuth state scoped to the initiating provider and session principal, with 10-minute expiration;
  • PKCE S256 with per-authorization verifier, challenge, and token-exchange validation;
  • state cleanup on success, invalid callbacks and provider errors;
  • no OAuth state logging;
  • mandatory trusted redirectUri for every provider, validated before login, linking and token exchange (HTTPS, or explicitly configured HTTP loopback for local development);
  • no request-derived callback URLs, no trust in HTTP_HOST, HTTP_X_FORWARDED_PROTO, or arbitrary request paths;
  • negative tests for missing, malformed, insecure or injected callback URLs and positive tests for HTTPS and loopback configuration.

Database schema and migrations

Added bundled Phinx migrations for the default DBAuth model:

  • user;
  • user_credentials;
  • role;
  • user_roles;
  • delegations.

The default user schema includes:

  • unique UUID;
  • password;
  • name/image;
  • reset token + expiration;
  • lifecycle disabled_at;
  • creation timestamp.

Credential schema includes provider/UID identity, profile, token, verification expiration/verified timestamp, last-login and creation fields. The credential-verification migration is included.

Migrations intentionally use logical table names and remain compatible with application-level Phinx/database prefixes.

Correctness fixes

  • Eager relation hydration now keeps the subject-user constraint and cannot include another user's row for a shared target.
  • Role-user lookup now works across SQLite/MySQL/PostgreSQL without GROUP_CONCAT.
  • Role lookup preserves all roles for returned users.
  • Role lookup carries lifecycle state so active-admin protection is accurate.
  • BasicAuth credential rename normalizes before duplicate checks and preserves metadata.
  • Application association storage/validation is no longer coupled to Auth.
  • Provider behavior is aligned more closely between BasicAuth and DBAuth.

Tooling and dependencies

  • Added objectiveweb/db ^0.8.0.
  • Added objectiveweb/router ^3.0 as a development dependency for controller/middleware compatibility testing.
  • Added Phinx for development/migration testing.
  • Updated PHPUnit from 4.x to 10.x.
  • Fixed the Composer support/issues URL.
  • Replaced Travis CI with GitHub Actions.
  • Added composer validate --strict.

CI and tests

GitHub Actions now runs:

  • PHPUnit on PHP 8.2, 8.3, 8.4 and 8.5;
  • strict Composer validation;
  • pre-merge production install gate from release.yml on pull requests: PHP 8.2, Composer --no-dev, runtime autoload/authentication smoke tests, and an isolated consumer installation of the v0.2.0 candidate via a copied Composer path package;
  • PostgreSQL 16 migration execution;
  • MySQL 8.0 and 8.4 LTS migrations against dedicated containers, with schema/index/foreign-key validation and real DBAuth login, role, pagination, credential-verification, password-reset and deletion smoke tests;
  • Phinx migration status verification;
  • migrated-schema verification;
  • PostgreSQL execution coverage for portable role lookup and Collection-based DBAuth queries.

The test suite now includes dedicated coverage for:

  • AuthController;
  • BasicAuth;
  • DBAuth on SQLite;
  • OAuth callbacks/linking/state/session behavior, including provider/session state binding, replay/expiration, PKCE S256 and account collision prevention;
  • RequireRole middleware;
  • UserController;
  • prefix-aware DB access;
  • roles and lifecycle behavior;
  • credentials;
  • password reset;
  • password-change current-password, passwordless-account and CSRF regression coverage on BasicAuth and DBAuth;
  • logout open-redirect regression coverage;
  • eager authorization relations and application-defined session context;
  • public-user secret sanitization;
  • final-active-admin protection;
  • PostgreSQL migrations, role lookup and Collection query pagination/search.
  • MySQL 8.0/8.4 migration compatibility (explicit NOT NULL on composite primary keys), migrated schema constraints, and authentication/credential flows.

Release documentation and migration

  • CHANGELOG.md: detailed 0.2.0 changes, breaking APIs, security work and deferred scope.
  • docs/UPGRADING-0.2.0.md: step-by-step manual migration from 0.1.x, including database identity mapping, password-hash preservation, session/token invalidation, code changes and cutover/rollback checklist.
  • docs/RELEASE-0.2.0.md: copy-ready release notes and release operator checklist; marked unreleased until a tag is published.
  • Updated README entry points and corrected the SQLite example to include credential verification columns.

Documentation

README was rewritten for the 0.2.0 architecture and now documents:

  • DBAuth and BasicAuth setup;
  • required/default schema;
  • credentials and providers;
  • roles and relation authorization;
  • migration from managed_relations to application-owned associations plus user_context_callback;
  • callbacks and lifecycle configuration;
  • password reset;
  • OAuth configuration;
  • user-management API, direct Collection list responses, pagination metadata and CSRF requirements.

Deferred to a later release

Rate limiting is intentionally not implemented in 0.2.0. Applications must enforce login, registration, recovery, reset-token and credential-verification throttling at a trusted reverse proxy, gateway, WAF or middleware. Do not use SQL rate-limit tables or per-request application database counters. The future pluggable implementation is tracked in #2.

Run user updates, role creation, and role membership replacement in one
DB transaction so failures cannot leave partial changes.

Add SQLite regression coverage that injects a membership insert failure
and asserts that user fields, role definitions, and assignments roll back.
Delete relation rows only for entries marked auth_owned=true, preserving
application-owned association data even when used for authorization.
Document the opt-in for bundled delegations and add deletion regressions
covering both Auth-owned and application-owned relation tables.
Refresh roles and lifecycle status before global, delegated, and resource
permissions, invalidating stale sessions for suspended or deleted users.
Preserve false/empty-list unauthenticated return semantics and include
regression tests for role revocation, suspension, and account deletion.
Require a plain DELETE /api/users/{id} for account deletion and return
404 for unknown or incomplete subresource paths rather than deleting users.
Cover unsupported paths, valid user deletion and credential-only deletion
with regression tests; correct the previous unauthenticated delete test.
Only overwrite the stored BasicAuth credential profile when update_credential
receives a non-null value, matching DBAuth's established semantics.
Keep null defaults for new credentials and verify that login, null updates,
and explicit profile replacements behave correctly.
Use the MySQL 8 utf8mb4_0900_bin collation for credential UID and
provider columns in the existing Phinx migration. This prevents
case-insensitive or trailing-space-insensitive matching of distinct
external provider subjects. Verify column collations and actual
credential lookup/uniqueness on MySQL 8.0 and 8.4, and document
existing-schema migration considerations.
… tokens

Restrict anonymous password recovery to independently verified
credentials for every configured recovery provider, including local.
Prefer the first verified matching channel rather than an unverified
match, avoid rotating reset tokens for unverified accounts, and keep
uniform anonymous responses. Cover BasicAuth and DBAuth behavior in
regression tests and document existing-account migration requirements.
Update password hash, reset-token digest and expiration together in a
single DBAuth UPDATE, and apply the same behavior in BasicAuth.
Cover direct password changes and authenticated password-change routes
with SQLite, in-memory and MySQL regressions. Document reset-link
invalidation in the README and changelog.
Add DBAuth with_admin_mutation using db->transaction() and a stable
role-row write lock, then reload the target and enforce the invariant
within the same transaction before deletes, demotions, or suspensions.
Protect PUT disabled_at as well as the suspend action. Add tests for
all mutation paths and stale-count interleaving, and document the
invariant and limits of direct DBAuth writes.
…ssions

Use an authenticated non-target account for self-protection checks in
new administrator mutation tests, matching the existing controller tests.
…lete

Remove the with_admin_mutation abstraction from Auth, DBAuth and
UserController. Use the existing db->transaction() calls in DBAuth
update/delete for shared-role locking and fresh active-admin checks.
Skip the lock for ordinary user updates and account activation.
Protect direct DBAuth mutations, retain provider-neutral controller
validation, and extend regression coverage.
Existing session revalidation tests must not demote, suspend or delete
the only active administrator now that DBAuth enforces the invariant
for direct update/delete calls. Keep a separate active administrator
so those tests focus on session invalidation.
Create a second active admin before exercising role removal so the
integration test verifies role syncing without violating the new
DBAuth final-admin invariant. Assert that the demoted user loses the
role while the backup retains it, and adjust remaining user count.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants