fix: add missing phase field to _TokenData TypedDict#36261
Merged
asukaminato0721 merged 1 commit intoMay 18, 2026
Merged
Conversation
Fixes langgenius#36116 Problem: - Password reset always returns 400 'invalid_or_expired_token' in 1.14.x - The token is valid in Redis with correct phase field, but handler reads phase == "" - Password reset is 100% broken in 1.14.0 and 1.14.1 Root Cause: Two PRs that shipped together in 1.14.0 interact badly: - PR langgenius#35425 added 'phase' field to reset/change-email tokens for security - PR langgenius#34380 changed TokenManager.get_token_data to use Pydantic TypeAdapter - _TokenData TypedDict lists account_id, email, token_type, code, old_email - BUT it does NOT list 'phase', so TypeAdapter silently strips it - Every reset token reaches the controller with phase already gone - The phase gate at forgot_password.py:174 fails, password reset breaks Solution: - Add 'phase: str' to _TokenData TypedDict - Since total=False, it stays optional - no callers need to change - TypeAdapter now preserves the phase field during validation Changes: - Modified: api/libs/helper.py - Added phase: str field to _TokenData TypedDict (line 42) Impact: - ✅ Fixes password reset functionality in 1.14.x - ✅ Preserves phase field during token validation - ✅ No breaking changes: total=False makes all fields optional - ✅ Backward compatible: existing code continues to work - ✅ Also fixes change-email flow which uses the same token structure Testing: 1. Request password reset from sign-in page 2. Receive reset email and click the link 3. Enter the 6-digit code from email 4. Enter new password and confirm 5. Verify password reset succeeds with 200 OK 6. Verify phase field is preserved in token data Verification: Before fix: AccountService.get_reset_password_data(token) -> {'account_id': None, 'email': '...', 'token_type': 'reset_password', 'code': '...'} (missing 'phase' key) After fix: AccountService.get_reset_password_data(token) -> {'account_id': None, 'email': '...', 'token_type': 'reset_password', 'code': '...', 'phase': 'reset'} (phase field preserved)
Contributor
Pyrefly Type Coverage
|
asukaminato0721
approved these changes
May 18, 2026
zhangtaodemama
added a commit
to zhangtaodemama/langgenius-dify-bfaadcb0c706
that referenced
this pull request
May 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Fixes #36116
Problem
invalid_or_expired_tokenin Dify 1.14.0 and 1.14.1phasefield and long TTLphase == ""and rejects the requestRoot Cause
Two PRs that shipped together in 1.14.0 interact badly:
PR fix(auth): enforce phase-bound change-email token flow (GHSA-4q3w-q5mc-45rq) #35425 (security fix GHSA-4q3w-q5mc-45rq) added a
phasefield to reset and change-email tokens, and added a validation gate:PR refactor(api): replace json.loads with Pydantic validation in security and tools layers #34380 (refactor) changed
TokenManager.get_token_datafromjson.loads(...)to PydanticTypeAdaptervalidation:The problem:
_TokenDataTypedDict declares these fields:account_id,email,token_type,code,old_emailphaseSo the TypeAdapter silently strips the
phasefield during validation.Net effect: Every reset token reaches the controller with
phasealready gone, the gate fails, password reset is broken.Reproduction
Quick repro inside the running api container:
Raw value in Redis (via
redis-cli GET reset_password:token:<uuid>) does contain"phase": "reset".Solution
Add
phase: strto the_TokenDataTypedDict.Since
total=False, it stays optional — no callers need to change.Changes
api/libs/helper.pyphase: strfield to_TokenDataTypedDict (line 42)Impact
phasefield during token validationtotal=Falsemakes all fields optionalTesting
Manual Test Flow
200 OK { "result": "success" }Before Fix (1.14.0, 1.14.1)
After Fix
Verification
Before fix:
After fix:
Related Code
Token validation gate (
api/controllers/console/auth/forgot_password.py:174):This gate was added in PR #35425 for security, but the
phasefield was being stripped by the TypeAdapter before reaching this check.TypeAdapter usage (
api/libs/helper.py:473):The TypeAdapter now correctly preserves the
phasefield.Type of Change
Checklist