Skip to content

chore: absorb auth into packages/auth - #13890

Merged
ChiragAgg5k merged 112 commits into
mainfrom
chore/absorb-auth
Sep 26, 2026
Merged

ChiragAgg5k merged 112 commits into
mainfrom
chore/absorb-auth

Conversation

@ChiragAgg5k

Copy link
Copy Markdown
Member

Summary

This moves utopia-php/auth into packages/auth, so Appwrite loads it directly. It's wave 1 of #13828 and follows the playbook in rfc/monorepo.md. There are three commits plus a whitespace fix:

  1. Add 'packages/auth/' from commit '580e41d6…': subtree import from the mirror with full history. The mirror head is the 0.12.0 commit Appwrite already locks (580e41d6), so Appwrite runs the same code.
  2. chore(auth): mirror plumbing: mirror.yml now points at this repository's mirror-redirect.yml, the README banner is refreshed, and absorb removes the Dockerfile, which only built a local test container.
  3. refactor: load auth from packages/:
    • Adds the root autoload and replace entries, removes the utopia-php/auth: ^0.12 require line and the https://github.com/utopia-php/auth vcs repository entry, and removes the package from the lock.
    • Step A of the standard shape:
      • src/Auth/* moves up to src/.
      • tests/Auth/* moves up to tests/. The tests move from Utopia\Tests\Auth to Utopia\Auth\Tests, with a matching autoload-dev entry. tests/StoreTest.php already declared Utopia\Auth\Tests but wasn't autoloadable before; now it is.
      • There's no services tier: every test is pure hashing, JWT or OAuth2 parsing, so nothing goes under tests/E2E.
      • docker-compose.yml is removed. It only ran the unit tests in a container built from the deleted Dockerfile, and the standard shape keeps a compose file only when composer test:e2e exists. The README's test section now says composer install && composer test and lists the four extensions the tests need.
    • Adds a new standalone phpstan.neon at level max over src and tests, and a standalone rector.php. The package had no PHPStan config of its own, so it ships with a phpstan-baseline.neon of 33 findings: mixed out-parameters and results from openssl_pkey_export(), openssl_pkey_get_details() and openssl_sign() in the asymmetric issuer and verifier, integer arithmetic in the PHPass encoder, array shapes in AuthorizationDetails and ResourceIndicators, and decoded-claim arithmetic in the tests. The RFC's phase 8 burn-down list now includes them.
    • Pint and Rector fixes from bin/monorepo check auth --fix: empty-body braces, fn ( spacing, blank lines after control blocks, protected → private on final test classes. There's no behaviour change.
    • The README's contribution-guide link now points here.
  4. style(auth): strip trailing whitespace from LICENSE: the upstream LICENSE fails git diff --check.

There's nothing to hoist. The package requires only PHP and ext-hash, ext-openssl, ext-scrypt and ext-sodium. ext-openssl is already in the root require, and extensions are exempt from the hoisting rule (same as image's ext-gd). The Dockerfile and CI both install with --ignore-platform-reqs, so dropping ext-scrypt and ext-sodium from the lock changes no install. If we want the root manifest to declare them anyway (user password hashing depends on both), that's a one-line follow-up.

Net for the load commit: the lock diff is the utopia-php/auth entry (−60 lines) plus the content hash.

Validation

  • bin/monorepo validate: all packages valid
  • bin/monorepo check auth: Pint, PHPStan level max (with baseline) and Rector pass
  • bin/monorepo test auth: 283 tests / 572 assertions
  • vendor/bin/phpunit --testsuite packages --filter 'Utopia\\Auth': 283 tests / 572 assertions under the root autoloader
  • Utopia\Auth\Proofs\Password resolves to packages/auth/src/Proofs/Password.php from the root autoloader, and vendor/utopia-php/auth is gone
  • Root PHPStan passes on 8 of the 29 Appwrite files that use Utopia\Auth (Documents/User.php, Auth/Validator/PasswordHistory.php, Auth/MFA/Type.php, Users/Base.php, Users/Http/Users/Scrypt/Modified/Create.php, Account/Http/Account/Sessions/IdToken/Create.php, Realtime/Message/Handlers/Authentication.php, app/init/resources/request.php)
  • composer update --lock: removes only utopia-php/auth
  • composer validate --no-check-publish: valid (the existing utopia-php/platform exact-constraint warning only)
  • bin/monorepo split auth --dry-run: 1f261783, which fast-forwards from the mirror head 580e41d6
  • git diff --check
  • composer lint: passes

Merge and follow-up

Merge with a merge commit.

The utopia-php/auth mirror already has the canonical main ruleset (id 9271238), with the split app as an always-bypass actor, so the absorb ran with --skip-ruleset.

After merge:

  • Confirm Split pushes to utopia-php/auth.
  • Remove packages/auth from utopia-php/monorepo.
  • Triage: the mirror has no open PRs or issues. auth: OAuth2 clients, identity validators, and Phone calling codes utopia-php/monorepo#137 (draft, eldadfux, +11.5K) is still open against packages/auth. It moves Appwrite's identity validators and 47 OAuth2 provider adapters into the library. It needs a decision on whether to port it onto packages/auth here or close it with a pointer. The mirror also has stale branches (dev, feat-oauth-helpers, cursor/auth-validators-oauth2-1299).

🤖 Generated with Claude Code

eldadfux and others added 30 commits March 8, 2025 09:23
Co-authored-by: Matej Bačo <[email protected]>
- Set default algorithm in Password class when initializing
- Update tests to use new algorithm methods and default settings
- Modify token generation to explicitly set SHA algorithm
- Adjust test assertions to match new default hashing methods
- Update Proof classes to generate random values without input parameter
- Add password generation with configurable length and charset
- Modify README.md with comprehensive usage examples for different proof types
- Update tests to reflect new generation and configuration methods
- Improve code flexibility and security for authentication proofs
Move common hash and verify implementations from individual Proof subclasses to the abstract Proof base class, reducing code duplication and simplifying the class hierarchy
Introduce a new section demonstrating the usage of the Utopia\Auth\Store class, showcasing key features like setting, getting, encoding, and decoding data with practical code examples
loks0n and others added 12 commits July 14, 2026 15:38
docs: centralize CODE_OF_CONDUCT and CONTRIBUTING at the monorepo root
…ocuments

Add OAuth Client ID Metadata Documents
chore(auth): align phpunit config with the other packages
chore: stop committing package lock files
feat(auth): add AuthorizationDetails reader for RFC 9396 grants
…s-list-shape

fix(auth): reject non-list authorization_details shapes
feat(auth): add a generic HS256 JWT issuer
…ls-restrict

feat(auth): narrow authorization_details to what a resolver still allows
…d32c4'

git-subtree-dir: packages/auth
git-subtree-mainline: b866958
git-subtree-split: 580e41d
@ChiragAgg5k ChiragAgg5k added the absorb History-preserving package absorption; merge commit required label Sep 24, 2026
@greptile-apps

greptile-apps Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

[High risk] Absorbs external auth package into monorepo as internal package.

The PR should not merge until the repository’s regex and implementation-coupled-test requirements are satisfied.

Fix All in Claude CodeFindings

  1. P2 MD5 tests mirror production ▶
  2. P2 Tests mirror production logic ▶
  3. P2 Round-trip test checks original ▶
  4. P2 Store example calls missing methods ▶
  5. P2 Scrypt requirement no longer enforced ▶
  6. P2 Regex validation violates repository guide ▶
  7. P2 Signature test mirrors issuer ▶
Fix with agent prompt
### Issue 1
packages/auth/tests/Algorithms/MD5Test.php:26
This assertion calculates the expected hash with `md5()`, the same function production uses. The test repeats that pattern for other inputs. If the implementation and assertions change together, the tests could pass despite a compatibility regression. The repository requires tests to check observable behavior rather than mirror source code; that requirement must be satisfied before merging.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

### Issue 2
packages/auth/tests/Issuers/Asymmetric/IdTokenTest.php:262-266
`expectedLeftHalfHash()` repeats the production hashing and encoding steps. If both copies change in the same incorrect way, the test can still pass. The Argon2 tests similarly derive expected values from the object's options. The repository requires tests to check observable behavior rather than mirror source code or configuration; this requirement should be satisfied before merging.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

### Issue 3
packages/auth/tests/StoreTest.php:87-90
The test decodes into `$newStore` but then checks values on `$store`. It would pass even if decoding lost every value, so it cannot catch a regression in Store serialization.

```suggestion
        // Verify all data was preserved
        foreach ($data as $key => $value) {
            $this->assertEquals($value, $newStore->getProperty($key));
        }
```

### Issue 4
packages/auth/docs/store.md:15-22
The example uses `set()` and `get()`, but `Store` provides `setProperty()` and `getProperty()`. A reader running the example will get an undefined-method error, including at the final decoded-value lookup. Please use the public method names throughout.

### Issue 5
composer.json:undefined-190
Replacing the locked auth dependency removes its `ext-scrypt` requirement from the root Composer dependency graph. If a deployment lacks that extension, an ordinary Composer platform check no longer flags it, while the scrypt user-creation path throws when it calls `Scrypt::hash()`. The package still declares the requirement, but the root project needs to declare it for Composer to enforce it.

### Issue 6
packages/auth/src/Hashes/ScryptModified.php:undefined-117
The imported salt validator uses `preg_match()`. The repository guide says not to add regular expressions: use string operations or an existing validator, or explain in the PR why neither works. The same pattern appears in `setSaltSeparator()` and `setSignerKey()`, as well as imported auth tests. This repository requirement must be satisfied before merging.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

### Issue 7
packages/auth/tests/Issuers/Symmetric/RefreshTokenTest.php:undefined-81
This test calculates its expected signature with the same HMAC and base64url steps as the issuer; the wrong-secret assertion repeats that approach. If both copies change incorrectly together, the test can still pass. The repository requires tests of observable behavior rather than tests that mirror source code. This requirement must be satisfied before merging.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

This PR absorbs Utopia Auth into packages/auth, loads it through the root Composer autoloader, and adds package-local tests and tooling. The branch also incorporates a registry change already present in the base.

  • The auth import preserves the existing library code while changing its package layout and dependency wiring.
  • The imported MD5 tests contain implementation-coupled assertions.

Reviews (10) · Last reviewed commit: "Merge remote-tracking branch 'origin/mai..."

Comment on lines +262 to +266
* Mirror of IdToken::leftHalfHash for assertion purposes.
*/
private function expectedLeftHalfHash(string $value): string
{
return rtrim(strtr(base64_encode(substr(hash('sha256', $value, true), 0, 16)), '+/', '-_'), '=');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Tests mirror production logic

expectedLeftHalfHash() repeats the production hashing and encoding steps. If both copies change in the same incorrect way, the test can still pass. The Argon2 tests similarly derive expected values from the object's options. The repository requires tests to check observable behavior rather than mirror source code or configuration; this requirement should be satisfied before merging.

Context Used: Call out and harshly judge implementation-coupled tests. We don't mirror source code, configuration, or version pins in assertions. We test observable behavior; use linters for syntax and schema checks. (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/auth/tests/Issuers/Asymmetric/IdTokenTest.php
Line: 262-266

Comment:
**Tests mirror production logic**

`expectedLeftHalfHash()` repeats the production hashing and encoding steps. If both copies change in the same incorrect way, the test can still pass. The Argon2 tests similarly derive expected values from the object's options. The repository requires tests to check observable behavior rather than mirror source code or configuration; this requirement should be satisfied before merging.

**Context Used:** Call out and harshly judge implementation-coupled tests. We don't mirror source code, configuration, or version pins in assertions. We test observable behavior; use linters for syntax and schema checks. ([source](https://app.greptile.com/review/custom-context?memory=instruction-0))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code Fix in Codex

Comment on lines +87 to +90
// Verify all data was preserved
foreach ($data as $key => $value) {
$this->assertEquals($value, $store->getProperty($key));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Round-trip test checks original

The test decodes into $newStore but then checks values on $store. It would pass even if decoding lost every value, so it cannot catch a regression in Store serialization.

Suggested change
// Verify all data was preserved
foreach ($data as $key => $value) {
$this->assertEquals($value, $store->getProperty($key));
}
// Verify all data was preserved
foreach ($data as $key => $value) {
$this->assertEquals($value, $newStore->getProperty($key));
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/auth/tests/StoreTest.php
Line: 87-90

Comment:
**Round-trip test checks original**

The test decodes into `$newStore` but then checks values on `$store`. It would pass even if decoding lost every value, so it cannot catch a regression in Store serialization.

```suggestion
        // Verify all data was preserved
        foreach ($data as $key => $value) {
            $this->assertEquals($value, $newStore->getProperty($key));
        }
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex

Comment on lines +15 to +22
$store->set('userId', '12345')
->set('name', 'John Doe')
->set('isActive', true)
->set('preferences', ['theme' => 'dark', 'notifications' => true]);

// Get values with optional defaults
$userId = $store->get('userId');
$missing = $store->get('missing', 'default value');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Store example calls missing methods

The example uses set() and get(), but Store provides setProperty() and getProperty(). A reader running the example will get an undefined-method error, including at the final decoded-value lookup. Please use the public method names throughout.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/auth/docs/store.md
Line: 15-22

Comment:
**Store example calls missing methods**

The example uses `set()` and `get()`, but `Store` provides `setProperty()` and `getProperty()`. A reader running the example will get an undefined-method error, including at the final decoded-value lookup. Please use the public method names throughout.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex

Comment thread composer.json
},
"replace": {
"utopia-php/agents": "*",
"utopia-php/auth": "*",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Scrypt requirement no longer enforced

Replacing the locked auth dependency removes its ext-scrypt requirement from the root Composer dependency graph. If a deployment lacks that extension, an ordinary Composer platform check no longer flags it, while the scrypt user-creation path throws when it calls Scrypt::hash(). The package still declares the requirement, but the root project needs to declare it for Composer to enforce it.

Prompt To Fix With AI
This is a comment left during a code review.
Path: composer.json
Line: 181

Comment:
**Scrypt requirement no longer enforced**

Replacing the locked auth dependency removes its `ext-scrypt` requirement from the root Composer dependency graph. If a deployment lacks that extension, an ordinary Composer platform check no longer flags it, while the scrypt user-creation path throws when it calls `Scrypt::hash()`. The package still declares the requirement, but the root project needs to declare it for Composer to enforce it.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex

@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

✨ Benchmark results

Comparing main (before) → chore/absorb-auth (after).

Metric Before After Change
🚀 Requests/sec 179.46 184.3 ⚪ +2.7%
⏱️ Latency P50 95.85 ms 95.13 ms ⚪ -0.7%
⏱️ Latency P95 232.49 ms 222.43 ms ⚪ -4.3%
Per-scenario breakdown & investigation details

Metrics below reflect the current branch (after). Δ P95 compares against the base.

Scenario P50 (ms) P95 (ms) Requests RPS Δ P95 (ms)
API total 95.13 222.43 11,400 184.3 -10.06
Account 181.85 337.78 600 10.38 -5.26
TablesDB 91.75 170.63 6,200 103 -19.74
Storage 87.88 184.62 3,000 51.25 -5.85
Functions 132.16 258.55 1,600 27.85 -19.49

Top API waits (after)

API request Max wait (ms)
account.prefs.update 511
storage.buckets.create 492.05
account.name.update 486.6
tablesdb.rows.delete 368.77
functions.variables.update 356.72

throw new \InvalidArgumentException('Salt cannot be empty');
}

if (! preg_match('/^[A-Za-z0-9+\/]+={0,2}$/', $salt)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Regex validation violates repository guide

The imported salt validator uses preg_match(). The repository guide says not to add regular expressions: use string operations or an existing validator, or explain in the PR why neither works. The same pattern appears in setSaltSeparator() and setSignerKey(), as well as imported auth tests. This repository requirement must be satisfied before merging.

Context Used: CLAUDE.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/auth/src/Hashes/ScryptModified.php
Line: 117

Comment:
**Regex validation violates repository guide**

The imported salt validator uses `preg_match()`. The repository guide says not to add regular expressions: use string operations or an existing validator, or explain in the PR why neither works. The same pattern appears in `setSaltSeparator()` and `setSignerKey()`, as well as imported auth tests. This repository requirement must be satisfied before merging.

**Context Used:** CLAUDE.md ([source](https://github.com/appwrite/appwrite/blob/main/CLAUDE.md))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code Fix in Codex

$token = $this->refreshToken->issue('user-123', 'aud', 'client-abc', 1209600);

$parts = explode('.', $token);
$expected = $this->base64UrlEncode(hash_hmac('sha256', $parts[0] . '.' . $parts[1], $this->secret, true));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Signature test mirrors issuer

This test calculates its expected signature with the same HMAC and base64url steps as the issuer; the wrong-secret assertion repeats that approach. If both copies change incorrectly together, the test can still pass. The repository requires tests of observable behavior rather than tests that mirror source code. This requirement must be satisfied before merging.

Context Used: Call out and harshly judge implementation-coupled tests. We don't mirror source code, configuration, or version pins in assertions. We test observable behavior; use linters for syntax and schema checks. (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/auth/tests/Issuers/Symmetric/RefreshTokenTest.php
Line: 81

Comment:
**Signature test mirrors issuer**

This test calculates its expected signature with the same HMAC and base64url steps as the issuer; the wrong-secret assertion repeats that approach. If both copies change incorrectly together, the test can still pass. The repository requires tests of observable behavior rather than tests that mirror source code. This requirement must be satisfied before merging.

**Context Used:** Call out and harshly judge implementation-coupled tests. We don't mirror source code, configuration, or version pins in assertions. We test observable behavior; use linters for syntax and schema checks. ([source](https://app.greptile.com/review/custom-context?memory=instruction-0))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code Fix in Codex


$this->assertNotEmpty($hash);
$this->assertSame(32, \strlen($hash));
$this->assertSame(md5($password), $hash);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 MD5 tests mirror production

This assertion calculates the expected hash with md5(), the same function production uses. The test repeats that pattern for other inputs. If the implementation and assertions change together, the tests could pass despite a compatibility regression. The repository requires tests to check observable behavior rather than mirror source code; that requirement must be satisfied before merging.

Context Used: Call out and harshly judge implementation-coupled tests. We don't mirror source code, configuration, or version pins in assertions. We test observable behavior; use linters for syntax and schema checks. (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/auth/tests/Algorithms/MD5Test.php
Line: 26

Comment:
**MD5 tests mirror production**

This assertion calculates the expected hash with `md5()`, the same function production uses. The test repeats that pattern for other inputs. If the implementation and assertions change together, the tests could pass despite a compatibility regression. The repository requires tests to check observable behavior rather than mirror source code; that requirement must be satisfied before merging.

**Context Used:** Call out and harshly judge implementation-coupled tests. We don't mirror source code, configuration, or version pins in assertions. We test observable behavior; use linters for syntax and schema checks. ([source](https://app.greptile.com/review/custom-context?memory=instruction-0))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code Fix in Codex

@ChiragAgg5k
ChiragAgg5k merged commit 34a88e2 into main Sep 26, 2026
51 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

absorb History-preserving package absorption; merge commit required

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants