475 lines
17 KiB
Markdown
475 lines
17 KiB
Markdown
# Auth To Profile Flow Implementation Plan
|
||
|
||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||
|
||
**Goal:** Complete the PC registration, login, profile, unauthorized, logout, and endpoint-address flow with one token owner and deterministic redirects.
|
||
|
||
**Architecture:** `config.js` remains the sole runtime owner of the API base address. `utils/ApiClient.js` owns token validation, persistence, and cleanup; `profile-pages.js` decides whether a profile request is allowed and redirects unauthenticated users to the homepage; `profile-common.js` delegates confirmed logout to the API client. The integration tracker records the current address and verified behavior only after verification commands complete.
|
||
|
||
**Tech Stack:** Browser JavaScript (UMD and jQuery), Axios, Node.js `assert` tests, static HTML, Markdown.
|
||
|
||
## Global Constraints
|
||
|
||
- The current API base address is `http://182.61.18.23:8080` for development and production, and `config.js` is its only runtime owner.
|
||
- Runtime-related tests use the same address; only documents explicitly marked historical may retain the former test domain.
|
||
- Successful registration clears `genealogy_auth_token` and redirects to `login.html`; it never persists a registration response token.
|
||
- Only successful password or SMS login with a nonempty response token persists `genealogy_auth_token` and redirects to `profile.html`.
|
||
- `profile.html` and `profile-data.html` redirect to `index.html` before a profile request when no token exists, and after HTTP or business `401` responses.
|
||
- Confirmed logout requests `/genealogy/pc/auth/logout`, clears the token regardless of the request outcome, and redirects to `index.html`.
|
||
- Do not add global Axios redirects, duplicate token storage in pages, alter API paths, or change password-reset, phone-change, account-deactivation, or profile pages that do not load the profile API.
|
||
|
||
---
|
||
|
||
### Task 1: Synchronize the configured API address test
|
||
|
||
**Files:**
|
||
- Modify: `tests/config.test.js:11-28`
|
||
- Modify: `tests/utils.test.js:31`
|
||
|
||
**Interfaces:**
|
||
- Consumes: `configFactory(...).getConfig()` and `getApiBaseUrl()` from `config.js`.
|
||
- Produces: a test that asserts the current `config.js` address for both environments.
|
||
|
||
- [ ] **Step 1: Confirm the existing address-contract test fails**
|
||
|
||
The current test contains these stale assertions:
|
||
|
||
```js
|
||
assert.deepStrictEqual(developmentConfig.getConfig(), {
|
||
environment: 'development',
|
||
apiBaseUrl: 'http://test-genealogy-api.ddxcjp.cn'
|
||
});
|
||
|
||
assert.strictEqual(productionConfig.getApiBaseUrl(), 'http://test-genealogy-api.ddxcjp.cn');
|
||
```
|
||
|
||
Run: `node tests/config.test.js`
|
||
|
||
Expected: failure because `config.js` already returns `http://182.61.18.23:8080`.
|
||
|
||
- [ ] **Step 2: Update the stale test expectations**
|
||
|
||
Replace the two expected URL values with:
|
||
|
||
```js
|
||
assert.deepStrictEqual(developmentConfig.getConfig(), {
|
||
environment: 'development',
|
||
apiBaseUrl: 'http://182.61.18.23:8080'
|
||
});
|
||
|
||
assert.strictEqual(productionConfig.getApiBaseUrl(), 'http://182.61.18.23:8080');
|
||
```
|
||
|
||
Replace the mocked requester address in `tests/utils.test.js` with:
|
||
|
||
```js
|
||
baseUrl: 'http://182.61.18.23:8080',
|
||
```
|
||
|
||
- [ ] **Step 3: Run the focused configuration test**
|
||
|
||
Run: `node tests/config.test.js`
|
||
|
||
Expected: `config tests passed`.
|
||
|
||
Run: `node tests/utils.test.js`
|
||
|
||
Expected: `utils tests passed`.
|
||
|
||
- [ ] **Step 4: Commit the address-test synchronization**
|
||
|
||
```powershell
|
||
git add -- tests/config.test.js tests/utils.test.js
|
||
git commit -m "test: sync configured api address"
|
||
```
|
||
|
||
### Task 2: Enforce login-token and logout-cleanup contracts
|
||
|
||
**Files:**
|
||
- Modify: `utils/ApiClient.js:52-60, 167-191, 270-274`
|
||
- Modify: `tests/api-client.test.js:83-177`
|
||
|
||
**Interfaces:**
|
||
- Consumes: `getTokenFromResponse(data)`, `setToken(token)`, `clearToken()`, and public `client.login`, `client.loginBySms`, and `client.logout` methods.
|
||
- Produces: `client.login(body)` and `client.loginBySms(body)` reject with `ApiError('登录响应缺少 token')` when their response has no token; `client.logout()` clears the configured token key before resolving or rejecting.
|
||
|
||
- [ ] **Step 1: Write failing API-client assertions**
|
||
|
||
Append this after the registration assertions and before `client.sendSmsCode(...)` in `tests/api-client.test.js`:
|
||
|
||
```js
|
||
const missingTokenStore = createStore();
|
||
const missingTokenClient = GenealogyApi.createClient({
|
||
baseUrl: 'http://182.61.18.23:8080',
|
||
clientId: 'client-x',
|
||
tenantId: 'tenant-x',
|
||
tokenKey: 'token-key',
|
||
tokenStore: missingTokenStore,
|
||
axiosInstance: {
|
||
request: async () => ({
|
||
status: 200,
|
||
data: { code: 200, data: {} }
|
||
})
|
||
}
|
||
});
|
||
await assert.rejects(
|
||
missingTokenClient.login({ phone: '13800000000', password: 'md5' }),
|
||
(error) => error.message === '登录响应缺少 token'
|
||
);
|
||
assert.strictEqual(missingTokenStore.getItem('token-key'), null);
|
||
|
||
store.setItem('token-key', 'logout-token');
|
||
await client.logout();
|
||
assert.strictEqual(calls[9].url, '/genealogy/pc/auth/logout');
|
||
assert.strictEqual(store.getItem('token-key'), null);
|
||
|
||
const failedLogoutStore = createStore({
|
||
'token-key': 'logout-token'
|
||
});
|
||
const failedLogoutClient = GenealogyApi.createClient({
|
||
baseUrl: 'http://182.61.18.23:8080',
|
||
clientId: 'client-x',
|
||
tenantId: 'tenant-x',
|
||
tokenKey: 'token-key',
|
||
tokenStore: failedLogoutStore,
|
||
axiosInstance: {
|
||
request: async () => {
|
||
throw new Error('logout failed');
|
||
}
|
||
}
|
||
});
|
||
await assert.rejects(failedLogoutClient.logout(), /logout failed/);
|
||
assert.strictEqual(failedLogoutStore.getItem('token-key'), null);
|
||
```
|
||
|
||
Move the existing SMS-code assertions from `calls[9]` to `calls[10]` and change the final call-count assertion from `10` to `11`.
|
||
|
||
Replace the two existing `baseUrl: 'https://test-genealogy-api.ddxcjp.cn'` fixtures in this file with:
|
||
|
||
```js
|
||
baseUrl: 'http://182.61.18.23:8080',
|
||
```
|
||
|
||
- [ ] **Step 2: Run the focused API-client test and confirm failure**
|
||
|
||
Run: `node tests/api-client.test.js`
|
||
|
||
Expected: failure at the missing-token rejection because the current login method resolves without writing a token, before reaching the logout assertions.
|
||
|
||
- [ ] **Step 3: Add the minimal token validation and logout cleanup**
|
||
|
||
Add this helper after `getTokenFromResponse` in `utils/ApiClient.js`:
|
||
|
||
```js
|
||
function requireLoginToken(data) {
|
||
var token = getTokenFromResponse(data);
|
||
|
||
if (!token) throw new ApiError('登录响应缺少 token');
|
||
return token;
|
||
}
|
||
```
|
||
|
||
Replace the token writes in `login()` and `loginBySms()` with:
|
||
|
||
```js
|
||
setToken(requireLoginToken(data));
|
||
```
|
||
|
||
Replace `logout` with:
|
||
|
||
```js
|
||
logout: async function () {
|
||
try {
|
||
return await request('DELETE', '/genealogy/pc/auth/logout');
|
||
} finally {
|
||
clearToken();
|
||
}
|
||
},
|
||
```
|
||
|
||
- [ ] **Step 4: Run the API-client test and confirm all token paths pass**
|
||
|
||
Run: `node tests/api-client.test.js`
|
||
|
||
Expected: `api-client tests passed`.
|
||
|
||
- [ ] **Step 5: Commit the API-client contract**
|
||
|
||
```powershell
|
||
git add -- utils/ApiClient.js tests/api-client.test.js
|
||
git commit -m "fix: complete auth token lifecycle"
|
||
```
|
||
|
||
### Task 3: Guard profile access and connect confirmed logout
|
||
|
||
**Files:**
|
||
- Modify: `public/js/profile-pages.js:18-22, 165-198`
|
||
- Modify: `public/js/profile-common.js:9-82`
|
||
- Modify: `profile.html:80, 355`
|
||
- Modify: `tests/profile-pages.test.js:1-65`
|
||
- Create: `tests/profile-logout-structure.test.js`
|
||
|
||
**Interfaces:**
|
||
- Consumes: public `api.getToken()`, `api.clearToken()`, `api.currentProfile()`, `api.updateProfile()`, and `api.logout()` methods.
|
||
- Produces: `ProfilePages.shouldRedirectToHome(api, error)` for token and unauthorized classification; profile reads and writes redirect to `index.html` when that helper returns true; the existing logout controls call `api.logout()` before navigating to `index.html`.
|
||
|
||
- [ ] **Step 1: Write failing profile and logout tests**
|
||
|
||
Add these assertions before the final `console.log` in `tests/profile-pages.test.js`:
|
||
|
||
```js
|
||
assert.strictEqual(ProfilePages.shouldRedirectToHome({
|
||
getToken: () => ''
|
||
}), true);
|
||
assert.strictEqual(ProfilePages.shouldRedirectToHome({
|
||
getToken: () => 'token-x'
|
||
}), false);
|
||
assert.strictEqual(ProfilePages.shouldRedirectToHome({
|
||
getToken: () => 'token-x'
|
||
}, { status: 401 }), true);
|
||
assert.strictEqual(ProfilePages.shouldRedirectToHome({
|
||
getToken: () => 'token-x'
|
||
}, { code: 401 }), true);
|
||
assert.strictEqual(ProfilePages.shouldRedirectToHome({
|
||
getToken: () => 'token-x'
|
||
}, { status: 500 }), false);
|
||
```
|
||
|
||
Create `tests/profile-logout-structure.test.js` with:
|
||
|
||
```js
|
||
const assert = require('assert');
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const profileHtml = fs.readFileSync(path.join(__dirname, '..', 'profile.html'), 'utf8');
|
||
const profileCommon = fs.readFileSync(path.join(__dirname, '..', 'public', 'js', 'profile-common.js'), 'utf8');
|
||
|
||
assert.match(profileHtml, /data-logout-confirm/);
|
||
assert.match(profileHtml, /<a class="btn primary magnetic" href="index\.html" data-logout-confirm>/);
|
||
assert.match(profileCommon, /function performLogout\(targetUrl\)/);
|
||
assert.match(profileCommon, /api\.logout\(\)/);
|
||
assert.match(profileCommon, /targetUrl \|\| 'index\.html'/);
|
||
|
||
console.log('profile logout structure tests passed');
|
||
```
|
||
|
||
- [ ] **Step 2: Run the new focused tests and confirm failure**
|
||
|
||
Run: `node tests/profile-pages.test.js`
|
||
|
||
Expected: failure because `shouldRedirectToHome` is not exported.
|
||
|
||
Run: `node tests/profile-logout-structure.test.js`
|
||
|
||
Expected: failure because the logout confirmation markup and `performLogout` function do not exist.
|
||
|
||
- [ ] **Step 3: Add profile token guard and unauthorized redirect**
|
||
|
||
Add these helpers after `getApi()` in `public/js/profile-pages.js`:
|
||
|
||
```js
|
||
function shouldRedirectToHome(api, error) {
|
||
var status = error && (error.status || error.code);
|
||
|
||
return !api || !api.getToken || !api.getToken() || Number(status) === 401;
|
||
}
|
||
|
||
function redirectToHome() {
|
||
if (!root.location) return;
|
||
if (typeof root.location.replace === 'function') {
|
||
root.location.replace('index.html');
|
||
return;
|
||
}
|
||
root.location.href = 'index.html';
|
||
}
|
||
|
||
function redirectUnauthorized(api, error) {
|
||
if (!shouldRedirectToHome(api, error)) return false;
|
||
if (api && api.clearToken) api.clearToken();
|
||
redirectToHome();
|
||
return true;
|
||
}
|
||
```
|
||
|
||
Replace the existing combined early-return condition in `loadProfile()` with these two branches:
|
||
|
||
```js
|
||
if (!api || !documentRef || !documentRef.querySelector('[data-profile-page]')) return;
|
||
if (redirectUnauthorized(api)) return;
|
||
```
|
||
|
||
Update its `catch` block to start with:
|
||
|
||
```js
|
||
if (redirectUnauthorized(api, error)) return;
|
||
```
|
||
|
||
Update `submitProfileForm()` to place `if (redirectUnauthorized(api)) return;` after its existing null guard, and to start its `catch` block with `if (redirectUnauthorized(api, error)) return;` before setting an error status or message.
|
||
|
||
Export the classifier with the existing public methods:
|
||
|
||
```js
|
||
shouldRedirectToHome: shouldRedirectToHome,
|
||
```
|
||
|
||
- [ ] **Step 4: Connect both logout confirmations to the API client**
|
||
|
||
Add this function before `bindLogout()` in `public/js/profile-common.js`:
|
||
|
||
```js
|
||
function performLogout(targetUrl) {
|
||
var api = window.GenealogyApi && window.GenealogyApi.defaultClient;
|
||
var redirect = function () {
|
||
window.location.href = targetUrl || 'index.html';
|
||
};
|
||
|
||
if (!api || !api.logout) {
|
||
redirect();
|
||
return;
|
||
}
|
||
|
||
api.logout().catch(function () {}).then(redirect);
|
||
}
|
||
```
|
||
|
||
In `bindLogout()`, change the default target URL to `index.html`, replace the Layui confirmation callback body with `performLogout(targetUrl);`, and add this event handler before the existing `[data-logout-close]` handler:
|
||
|
||
```js
|
||
$(document).on('click', '[data-logout-confirm]', function (event) {
|
||
event.preventDefault();
|
||
closeLegacyLogout();
|
||
performLogout($(this).attr('href') || 'index.html');
|
||
});
|
||
```
|
||
|
||
In `profile.html`, change the legacy confirmation anchor to:
|
||
|
||
```html
|
||
<a class="btn primary magnetic" href="index.html" data-logout-confirm>确认退出</a>
|
||
```
|
||
|
||
- [ ] **Step 5: Run the focused profile tests and syntax checks**
|
||
|
||
Run: `node tests/profile-pages.test.js`
|
||
|
||
Expected: `profile-pages tests passed`.
|
||
|
||
Run: `node tests/profile-logout-structure.test.js`
|
||
|
||
Expected: `profile logout structure tests passed`.
|
||
|
||
Run: `node --check public/js/profile-pages.js`
|
||
|
||
Expected: exit code `0`.
|
||
|
||
Run: `node --check public/js/profile-common.js`
|
||
|
||
Expected: exit code `0`.
|
||
|
||
- [ ] **Step 6: Commit the profile and logout flow**
|
||
|
||
```powershell
|
||
git add -- public/js/profile-pages.js public/js/profile-common.js profile.html tests/profile-pages.test.js tests/profile-logout-structure.test.js
|
||
git commit -m "fix: guard profile auth and logout"
|
||
```
|
||
|
||
### Task 4: Record the current flow and run end-to-end regression checks
|
||
|
||
**Files:**
|
||
- Modify: `docs/pc-api-page-integration-tracker-2026-07-09.md:1-9, 32, 70, 163`
|
||
- Modify: `docs/superpowers/specs/2026-07-11-auth-to-profile-flow-design.md:3-5`
|
||
|
||
**Interfaces:**
|
||
- Consumes: the verified results of Tasks 1 through 3.
|
||
- Produces: a tracker whose current address and authentication-flow status match the code and tests, and a spec marked approved for implementation.
|
||
|
||
- [ ] **Step 1: Run all focused flow tests**
|
||
|
||
Run:
|
||
|
||
```powershell
|
||
node tests/config.test.js
|
||
node tests/api-client.test.js
|
||
node tests/auth-pages.test.js
|
||
node tests/profile-pages.test.js
|
||
node tests/profile-logout-structure.test.js
|
||
```
|
||
|
||
Expected: each command prints its success message and exits with code `0`.
|
||
|
||
- [ ] **Step 2: Run syntax and complete regression checks**
|
||
|
||
Run:
|
||
|
||
```powershell
|
||
node --check utils/ApiClient.js
|
||
node --check public/js/profile-pages.js
|
||
node --check public/js/profile-common.js
|
||
Get-ChildItem -Path tests -Filter *.test.js | ForEach-Object { & node $_.FullName; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } }
|
||
```
|
||
|
||
Expected: all syntax checks exit `0`; every test script prints its success message; PowerShell exits `0` on the first test failure instead of masking it.
|
||
|
||
- [ ] **Step 3: Update the tracker from verified evidence**
|
||
|
||
At the top of `docs/pc-api-page-integration-tracker-2026-07-09.md`, replace the active base-address line with:
|
||
|
||
```markdown
|
||
接口基础地址:`http://182.61.18.23:8080`
|
||
```
|
||
|
||
Add this section immediately after the introductory historical-record lines:
|
||
|
||
```markdown
|
||
## Current Endpoint Contract
|
||
|
||
- Runtime owner: `config.js`.
|
||
- Current development and production address: `http://182.61.18.23:8080`.
|
||
- Address change record: on 2026-07-11 the active address changed from the historical test domain to this IP address and port. Historical references to `test-genealogy-api.ddxcjp.cn` do not describe the current runtime configuration.
|
||
```
|
||
|
||
Replace the YAML-analysis item that says the project currently uses the test domain with:
|
||
|
||
```markdown
|
||
2. `servers` 仍是本地地址示例;当前运行时基础地址以 `config.js` 为准,为 `http://182.61.18.23:8080`。
|
||
```
|
||
|
||
Replace the PC-010 row with:
|
||
|
||
```markdown
|
||
| PC-010 | 已复核(当前地址已同步) | `config.js`、`tests/config.test.js`、`utils/ApiClient.js` | API 基础地址 | 当前开发和生产地址均为 `http://182.61.18.23:8080`;地址唯一运行时来源为 `config.js`,`tests/config.test.js` 已同步断言。 |
|
||
```
|
||
|
||
After Steps 1 and 2 pass, run `Get-Date -Format "yyyy-MM-dd HH:mm:ss K"` and insert its output as the `完成时间` line immediately after the `状态` line in this record. Append the rest of this record after the existing PC entries:
|
||
|
||
```markdown
|
||
## PC-025 注册、登录到个人资料闭环
|
||
|
||
- 状态:已完成
|
||
- 规划文件:`docs/superpowers/specs/2026-07-11-auth-to-profile-flow-design.md`
|
||
- 页面范围:`register.html`、`login.html`、`profile.html`、`profile-data.html`
|
||
- 接口范围:`/genealogy/pc/auth/register`、`/genealogy/pc/auth/login`、`/genealogy/pc/auth/login/sms`、`/genealogy/pc/auth/profile`、`/genealogy/pc/auth/logout`
|
||
- 地址契约:运行时唯一来源为 `config.js`;当前开发和生产地址均为 `http://182.61.18.23:8080`。
|
||
- 流程结果:注册成功清除 token 并跳转 `login.html`;密码和短信登录必须取得 token 后跳转 `profile.html`;个人资料无 token 或收到 HTTP/业务 `401` 时清 token 并跳转 `index.html`;确认退出无论接口结果都清 token 并跳转 `index.html`。
|
||
- 修改文件:`tests/config.test.js`、`utils/ApiClient.js`、`tests/api-client.test.js`、`public/js/profile-pages.js`、`public/js/profile-common.js`、`profile.html`、`tests/profile-pages.test.js`、`tests/profile-logout-structure.test.js`。
|
||
- 验证结果:`node tests/config.test.js`、`node tests/api-client.test.js`、`node tests/auth-pages.test.js`、`node tests/profile-pages.test.js`、`node tests/profile-logout-structure.test.js`、三个 `node --check` 命令和全量 Node 测试均通过。
|
||
```
|
||
|
||
Change the spec status body to:
|
||
|
||
```markdown
|
||
Approved for implementation.
|
||
```
|
||
|
||
- [ ] **Step 4: Check final whitespace and commit the record**
|
||
|
||
Run: `git diff --check`
|
||
|
||
Expected: exit code `0`; pre-existing CRLF conversion warnings are not whitespace errors.
|
||
|
||
```powershell
|
||
git add -- docs/pc-api-page-integration-tracker-2026-07-09.md docs/superpowers/specs/2026-07-11-auth-to-profile-flow-design.md
|
||
git commit -m "docs: record auth profile flow"
|
||
```
|