Add OTP support for email verification and password recovery by imtia33 · Pull Request #13677 · appwrite/appwrite · GitHub
Skip to content

Add OTP support for email verification and password recovery - #13677

Merged
Meldiron merged 8 commits into
appwrite:mainfrom
imtia33:feat/add-OTP-support-for-email-verification-password-recovery
Sep 16, 2026
Merged

Meldiron merged 8 commits into
appwrite:mainfrom
imtia33:feat/add-OTP-support-for-email-verification-password-recovery

Conversation

@imtia33

@imtia33 imtia33 commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds support for OTP-based Email Verification and Password Recovery in the Account API, making it easy and seamless for mobile application developers to verify emails and reset passwords without requiring web redirects or deep linking.

4 new endpoints are introduced:

  1. POST /v1/account/verifications/email/otp (createEmailVerificationOTP): Generates and emails a 6-digit OTP code to verify the user's email address.
  2. PUT /v1/account/verifications/email/otp (updateEmailVerificationOTP): Validates the 6-digit OTP code and marks the email as verified.
  3. POST /v1/account/recovery/otp (createRecoveryOTP): Generates and emails a 6-digit OTP code for password reset.
  4. PUT /v1/account/recovery/otp (updateRecoveryOTP): Validates the 6-digit OTP code and updates the user's password.

Note: I know this looks like a huge PR but actual changes are minimal. Reused existing architecture of email verification and password recovery. This PR creates new endpoints for OTP support for both methods mentioned. This can be scoped down to one method at a time if required.

Update: Now the PR is scoped down to email verification only .

Implementation Rationale & Reuse

The implementation reuses Appwrite's existing architecture and patterns:

  • Existing token types: TOKEN_TYPE_VERIFICATION (2) and TOKEN_TYPE_RECOVERY (3).
  • Existing token expiration (TOKEN_EXPIRATION_OTP - 15 minutes).
  • Existing email infrastructure (MAIL_TEMPLATE_OTP / email-otp.tpl) and security phrase mechanism (Phrase()).
  • Replaces magic URL token generation (ProofsToken) with 6-digit OTP generation (ProofsCode). Net-new logic additions are minimal.

Test Plan

Added end-to-end unit and integration test coverage in tests/e2e/Services/Account/AccountCustomClientTest.php:

  • testCreateEmailVerificationOTP: Verifies creation of verification OTP and email delivery.
  • testUpdateEmailVerificationOTP: Tests successful verification with OTP, as well as failure cases (expired code, wrong user, invalid code).
  • testCreateRecoveryOTP: Verifies creation of recovery OTP and email delivery.
  • testUpdateRecoveryOTP: Tests password reset with OTP, verifying password changes and invalid attempt failures.

Local Verification

Executed PHPUnit E2E tests inside the Appwrite container:

docker compose exec appwrite vendor/bin/phpunit tests/e2e/Services/Account/AccountCustomClientTest.php --filter=OTP

Output:

OK (5 tests, 170 assertions)

Related PRs and Issues

Draft PR to gather feedback on OTP-based email verification and password recovery endpoints for mobile developers.

Checklist

  • Have you read the Contributing Guidelines on issues?
  • If the PR includes a change to an API's metadata (desc, label, params, etc.), does it also include updated API specs and example docs?

@greptile-apps

greptile-apps Bot commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Comment thread app/controllers/api/account.php
Comment thread app/controllers/api/account.php Outdated
Comment thread tests/e2e/Services/Account/AccountCustomClientTest.php Outdated
Comment thread tests/e2e/Services/Account/AccountCustomClientTest.php
Comment thread tests/e2e/Services/Account/AccountCustomClientTest.php
Comment thread src/Appwrite/Platform/Modules/Console/Http/Templates/Email/Get.php
@Meldiron

Copy link
Copy Markdown
Contributor

@Meldiron Meldiron left a comment •

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.

Agentic comments

Comment thread app/controllers/api/account.php
Comment thread app/controllers/api/account.php Outdated
Comment thread app/controllers/api/account.php Outdated
Comment thread app/controllers/api/account.php
Comment thread app/controllers/api/account.php Outdated
Comment thread src/Appwrite/Platform/Modules/Console/Http/Templates/Email/Get.php
Comment thread tests/e2e/Services/Account/AccountCustomClientTest.php Outdated
Comment thread tests/e2e/Services/Account/AccountCustomClientTest.php
Comment thread tests/e2e/Services/Account/AccountCustomClientTest.php
…t-for-email-verification-password-recovery

# Conflicts:
#	tests/e2e/Services/Account/AccountCustomClientTest.php
Comment thread tests/e2e/Services/Account/AccountCustomClientTest.php Outdated
Comment thread tests/e2e/Services/Project/TemplatesBase.php
@imtia33
imtia33 force-pushed the feat/add-OTP-support-for-email-verification-password-recovery branch from 2567955 to b09c2e4 Compare September 15, 2026 15:48
Matej Baco and others added 2 commits September 16, 2026 11:17
Adds a unit regression on User::tokenVerify asserting a verification OTP
and a link secret are not interchangeable, plus the expired OTP case that
e2e cannot reach without a clock shim.

Adds the matching e2e case: an active OTP is rejected by
PUT /v1/account/verifications/email, and the missing USER_EMAIL_NOT_FOUND
guard on the create endpoint.

Derives the wrong-secret value from the delivered code so the negative
case cannot collide with the active OTP.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
testCreateEmailVerificationOTP started from setupAccountWithSession(),
which returns a process-wide cached account shared by every test the
ParaTest worker runs. setupAccountWithUpdatedEmail() rotates that user's
email server-side but only refreshes self::$updatedEmailData, leaving
self::$sessionData['email'] stale.

The OTP mail is therefore sent to the rotated address while the test
polls maildev for the old one, so the lookup fails. It is order
dependent, which is why it passes under --filter=OTP (the rotating tests
never run) and failed in the full CI suite.

Use createFreshAccountWithSession(), the existing helper for this, so the
account is unverified, uniquely addressed and cannot be mutated by other
tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Meldiron
Meldiron merged commit 9efa573 into appwrite:main Sep 16, 2026
48 checks passed
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