POST /users/change_password.json

快速結論

  • 狀態:已補 action / routing / PHPUnit;2026-07-03 staging curl、DB side effect 與 log 已驗證。
  • 2026-06-24 legacy 正式機 access log:POST /users/change_password.json 104 筆 200
  • web-app https://staging.pro360.com.tw/dashboard/settings 點選「變更密碼」會呼叫這支 API。
  • legacy JSON path 實際進 UsersController::change_password() 後立刻轉呼叫 change_password_from_api();不走 web/admin change_password() 後半段 validation、logout、admin email 分支。
  • 成功主流程更新 users.password,response 為 {"error":0,"message":"Success"};session helper 仍保留 legacy access 更新副作用。
  • 這支不可直接沿用 new Lib/Util/UserUtil::change_password(),因為該 helper 要求 8 碼、回 shared string error,且不支援 legacy 的「目前密碼空字串 + DB password 空字串」建立密碼分支。web 前端目前只要求新密碼至少 6 碼。

Legacy 對照

檔案行號行為
/Users/mattsu/Documents/Site/get-lancer-php56/app/Controller/UsersController.php3151-3155change_password():JSON request 直接呼叫 change_password_from_api() 後 return
同上2911-2916change_password_from_api():需 valid API session;invalid session 回 {"error":"Invalid session"}
同上2918-2923RestApiHelper::getUserId($this->request) 查 session user
同上2931-2934current_password,用 crypt(current_password, users.password) 比對;若 current password 與 DB password 都是空字串也允許
同上2935-2944new_password,用 getCryptHash(new_password) 更新 users.password,成功回 {"error":0,"message":"Success"}
同上2945-2955舊密碼錯誤回 {"error":2,"code":200,"message":"Incorrect current password"}
/Users/mattsu/Documents/Site/get-lancer-php56/app/Controller/RestApiHelper.php75-152hasValidApiSession():valid API key + session row + user row;不加 is_active guard
同上100-105blocked user 回 HTTP 403 + {"error":5,"message":"Blocked User"} 並停止流程
同上108-131session cache miss 時可能寫 returnedQuoteService task queue,並更新 users.iphone_last_access / last_access_client
/Users/mattsu/Documents/Site/get-lancer-php56/app/Controller/RestApiHelper.php162-182getUserId() 會寫 full request info log,channel {url}::{method}

New 對照

項目狀態
Endpoint/V1/Users.php:837-1030已補 change_password()、shared legacy session resolver、blocked user、legacy response shape、password verify/update、legacy auth side effect
Lib/Common/RouterV3.php:210,266-289保留 RouterV3 generic request log,但 request post data 先經 password/passwd key 過濾
Lib/Common/RouterRule/Mapping.php:96-99已補 users/change_password.json explicit routing
Lib/Util/UserUtil.php:108-135有 shared change_password(),但不符合 legacy JSON path;不建議直接沿用
Lib/Util/Pro360LoginUtil.php:151-176可用既有 verify_password() / getCryptHash() 對齊 legacy crypt hash
tests/UsersChangePasswordTest.php:53-218已補 success、setup password、missing current password、wrong current password、inactive user、blocked user、invalid session、RouterV3 password log filter 測試

前端入口

位置行為
web-app/modules/components/dashboard/account/AccountDetail.js:129-143帳號基本資料頁依 password_empty 顯示「建立密碼」或「變更密碼」
web-app/modules/components/dashboard/account/ChangePassword.js:145-164前端表單驗證;有密碼帳號需 current password,new password 至少 6 碼,確認密碼需一致
web-app/modules/containers/AccountContainer.js:109-116呼叫 changePassword(currentPassword, newPassword);建立密碼成功後把 noPassword 設回 false
web-app/modules/actions/index.js:366-375dispatch CHANGE_PASSWORD,payload 為 API response
web-app/modules/utils/api-manager.js:1097-1106POST /users/change_password.json,body 為 current_password / new_password
web-app/modules/containers/AccountContainer.js:371-381前端依 error=0/1/2 顯示成功、帳戶錯誤、舊密碼錯誤

操作路徑:

https://staging.pro360.com.tw/dashboard/settings
-> 點選「變更密碼」
-> POST /users/change_password.json

Input / Response

項目legacy 行為new 搬移方向
methodPOSTPOST
path/users/change_password.json同 legacy
authvalid API key + valid user session;blocked user 403;不加 is_active guard用專用 legacy resolver,避免 new shared validator 的 active guard 改變 legacy 行為
bodycurrent_password, new_password不新增其他 required 欄位
success{"error":0,"message":"Success"}欄位與型別照 legacy
invalid session{"error":"Invalid session"}已對齊 legacy;不丟 new common exception response
blocked userHTTP 403 + {"error":5,"message":"Blocked User"}已對齊 legacy;blocked 時不改 password;RouterV3 generic request log 仍會先寫入且 password 已過濾
missing user通常在 session helper 階段回 {"error":"Invalid session"}error=1 只會在 helper 已設 user id 後第二次查 user 才可能出現new 目前比照 session helper 查不到 user 時回 invalid session
wrong current password{"error":2,"code":200,"message":"Incorrect current password"}同 legacy

DB Write

主流程明確 DB write:

table欄位來源 / 規則
userspasswordPro360LoginUtil::getCryptHash(new_password),對齊 legacy getCryptHash()

session helper legacy side effect:

table欄位 / row來源 / 規則
usersiphone_last_access, last_access_clientlegacy RestApiHelper::hasValidApiSession() 在 session DB load 後更新;new 保留此副作用
task_queueaction=returnedQuoteServiceuser 有 active service、quote_service_count > 0iphone_last_access 超過 180 天時,對齊 legacy 寫入

不應更新:

類型欄位例
sessionapi_sessions 不刪、不重建
user statusis_active、email/phone、social id/token 不改
profileuser_profiles 不改
security tokenmobile_app_hash 不改
counters/walletwallet、counter、payment 欄位不改
queue/activity除 session helper 的 returnedQuoteService 條件式 task queue 外,change password 主流程不寫 activity / queue / event

重要分支

有既有密碼

current_hash = crypt(current_password, users.password)
current_hash === users.password
-> 更新 users.password

錯誤 current password 回 error=2

尚未建立密碼

legacy 允許:

current_password === ''
users.password === ''
-> 更新 users.password

這對應 web-app 的「建立密碼」流程。搬移時不可要求 old password 非空。

新密碼格式

legacy API path 沒有後端長度 / regex validation;前端目前只擋新密碼少於 6 碼。new 不應直接用 UserUtil::change_password() 的 8 碼 regex,否則會比 legacy 更嚴。

Config / Side Effect

legacy web/admin change_password() 後半段有 user.is_logout_after_change_password 與 admin email 分支,但 JSON path 在 UsersController.php:3151-3155 已直接轉 change_password_from_api() 並 return,不會執行那些分支。

因此本 API 搬移不需要:

  • user.is_logout_after_change_password
  • 登出 / 刪 session
  • 寫 admin changed password email
  • 寫 change password event queue;task queue 只有 session helper 的 returnedQuoteService 條件式副作用

Payment / Activity / Notification

類型legacy JSON pathnew 搬移狀態
payment / transaction不寫 payment、wallet、transaction
activityQuoteActivity / user activity不寫 activity
queue主流程無;session helper 可能寫 returnedQuoteService只保留 session helper 條件式 task queue
notification / emailweb/admin email 分支不執行不寄 email、不寫通知 queue
session / cookie成功不刪 session;invalid cookie session 會清 cookie成功不刪 session;invalid header session 不額外清 cookie

Log 對照

legacy RestApiHelper::getUserId() 會寫 full request info log;此 API request body 會包含 current_password / new_password

本次 migration decision:不在 Users::change_password() 另補 endpoint 專用 log,避免和 RouterV3 generic request log 重複;保留 RouterV3 既有 request log,但在 RouterV3 寫 log 前遞迴過濾 password 類 key。

  • 保留 RouterV3 generic channel:users/change_password.json::POST{RequestClass}
  • 保留 generic log context 的 API key、path、subDir、request post data 與 request id
  • current_password / new_password / nested User.passwd 寫入 log 前改成 [FILTERED]
  • 過濾規則在 RouterV3 層,key name 包含 passwordpasswd 時套用;因此也會保護其他 endpoint 的同類 request log

PHP 5.6 -> 8.2 注意

項目legacynew 注意
missing current_passwordPHP 5.6 undefined index notice 後值接近 null;null === '' 不成立new 需保留「missing 不等於空字串」;DB password 空值時,缺欄位不可通過 setup password 分支
missing new_passwordPHP 5.6 會把 null 傳進 getCryptHash(),實際等同用空字串產生 hashnew 用 (string)$newPassword 對齊
empty password setupcurrent_password === '' && users.password === '' 可成功不可被 shared helper 的 old password required 擋掉
password comparecrypt($current_password, $stored_hash) === $stored_hashPro360LoginUtil::verify_password() 或等價 hash_equals()
response code fieldwrong password / missing user 有 code=200success 沒有 code

測試規劃

至少補 PHPUnit:

case驗證
invalid session已補:回 legacy invalid session
blocked user已補:回 HTTP 403 legacy body;不改 password;generic request log password 已過濾
inactive user已補:valid session 可改密碼,不新增 legacy 沒有的 is_active = 1 guard
wrong current password已補:回 error=2, code=200, message=Incorrect current password;不改 password
success existing password已補:current password 正確時更新 users.password,新密碼可登入/verify;不動 session/profile/mobile_app_hash
setup password已補:users.password=''current_password='' 時可建立密碼
missing current_password已補:DB password 空值時缺欄位不等於空字串,回 error=2
response shape已補:success 無 code,錯誤 current password 有 code=200
request log已補:只保留 RouterV3 generic request log,並確認 password/passwd key 會變成 [FILTERED]

測試結果:

docker exec -w /project-data cd63f9147e8d ./vendor/bin/phpunit tests/UsersChangePasswordTest.php
OK (7 tests, 61 assertions)
 
docker exec -w /project-data cd63f9147e8d ./vendor/bin/phpunit tests/UsersDeleteTest.php
OK (3 tests, 99 assertions)
 
docker exec -w /project-data cd63f9147e8d ./vendor/bin/phpunit tests/UsersLoginTest.php
OK (9 tests, 210 assertions)

Staging 實測

2026-07-03,新版 staging api-staging.pro360.com.tw

項目結果
curl response{"error":0,"message":"Success"}
sessionrequest session 對到 user_id=8616
users.password已更新為 bcrypt $2y$12$ hash;legacy / new 都用 crypt(input, stored_hash) 驗證,可互用;更新後密碼可驗證,更新前密碼驗證失敗
users.modified未更新;符合 legacy User->updateAll() 只改 User.password
session side effectusers.iphone_last_access 更新到本次 request 時間
不應新增 side effect未新增 user_loginsquote_activitiesevent_queuetask_event_queue、queue log
request logmonolog_202607.channel = users/change_password.json::POST\RequestHttpcurrent_password / new_password 皆為 [FILTERED]
duplicate log本次時間後無 exact channel users/change_password.json::POST
error logmonolog_error 無本次相關錯誤

待確認

  1. 因已依 reviewer 要求在 RouterV3 過濾 password/passwd,這點與 legacy raw request log 不同,屬明確 migration decision。