Files
jiapu/docs/superpowers/plans/2026-07-11-auth-to-profile-flow.md
T
2026-07-11 09:36:02 +08:00

475 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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"
```