Skip to content

Commit 5baeea4

Browse files
Your NameCopilot
andcommitted
feat: Authentication system optimization with token management
- Add auth.rs module for secure token management with system keyring storage - Implement TokenData struct with automatic expiry detection (5-minute buffer) - Add token refresh mechanism with automatic re-authentication fallback - Support interactive password input when credentials are missing - Enhance client.rs with token refresh and 401 error handling - Integrate auth flow in main.rs with complete initialization pipeline - Add keyring dependency for cross-platform secure storage (Linux/macOS/Windows) - Update all documentation (README, CHANGELOG, docs/) - API compatibility fix: handle optional refresh_token field from API responses Features: - First run: authenticate with credentials, save token to system keyring - Subsequent runs: automatically load token from keyring - Token expiry: automatic detection with 5-minute buffer - Token refresh: automatic refresh on expiry, fallback to re-authentication - Cross-platform: Linux Secret Service, macOS Keychain, Windows Credential Manager Verified: - All cargo checks passed (fmt, clippy -D warnings) - Release build successful (4.9 MB binary) - Actual runtime test passed: token saved, articles synced Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent a0fb7db commit 5baeea4

12 files changed

Lines changed: 561 additions & 31 deletions

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,28 @@ All notable changes to werss-cli will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
66

7+
## [Unreleased]
8+
9+
### Added
10+
11+
- Complete authentication token management with system keyring storage
12+
- Token refresh mechanism with automatic expiration detection (5-minute buffer)
13+
- Interactive password input when credentials unavailable
14+
- Graceful fallback authentication flow: saved token → refresh → credentials → interactive input
15+
- Cross-platform support for secure token storage (Linux Secret Service, macOS Keychain, Windows Credential Manager)
16+
17+
### Changed
18+
19+
- Authentication system refactored to use `keyring` crate for secure token storage
20+
- Login process now automatically saves tokens for reuse on subsequent runs
21+
- Bearer token handling improved with optional refresh token support
22+
- API compatibility enhanced to handle optional `refresh_token` field in login responses
23+
24+
### Fixed
25+
26+
- Handle APIs that don't provide `refresh_token` field in login response
27+
- Proper error handling when refresh token operations fail
28+
729
## [0.1.0] - 2026-04-07
830

931
### Added

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "werss-cli"
3-
version = "0.1.0"
3+
version = "0.2.0"
44
edition = "2021"
55

66
[dependencies]
@@ -15,6 +15,7 @@ dotenvy = "0.15"
1515
anyhow = "1"
1616
regex = "1"
1717
toml = "0.8"
18+
keyring = "3"
1819

1920
[profile.release]
2021
opt-level = "z"

README.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,32 @@ Fetch articles from WeChat public accounts (微信公众号) via a WeRSS API ser
1212
- Configurable via CLI flags, environment variables, or TOML config file
1313
- Optional workspace publishing with cover image downloads
1414
- Date range filtering and article count limits
15+
- **Secure token management** — automatic credential storage and refresh with system keyring
16+
- Interactive authentication — falls back to password prompt when needed
17+
18+
## Authentication
19+
20+
### First Run
21+
```bash
22+
./target/release/werss-cli --api-base http://your-server:8001 \
23+
--username your-username \
24+
--password your-password \
25+
--mp all
26+
```
27+
→ Credentials are validated and token is automatically saved to system keyring
28+
29+
### Subsequent Runs
30+
```bash
31+
./target/release/werss-cli --mp all
32+
```
33+
→ Token is automatically loaded from keyring, no credentials needed
34+
35+
### How It Works
36+
1. Check system keyring for existing token
37+
2. If found and valid → use immediately
38+
3. If found but expired → automatically refresh
39+
4. If refresh fails or no token → use provided credentials or prompt for password
40+
5. Token is automatically saved for next run
1541

1642
## Quick start
1743

@@ -46,7 +72,7 @@ target_mps = "all" # or ["MP_WXS_123", "MP_WXS_456"]
4672
## Examples
4773

4874
```bash
49-
werss-cli # fetch all (uses werss.toml)
75+
werss-cli # fetch all (uses werss.toml + saved token)
5076
werss-cli --mp MP_WXS_123,MP_WXS_456 # specific accounts
5177
werss-cli --output ./data # custom output directory
5278
werss-cli --since 2026-01-01 --until 2026-03-31 # date range
@@ -87,6 +113,7 @@ Each file has YAML frontmatter (title, author, coverImage, url, mp_id, descripti
87113
- [chrono](https://crates.io/crates/chrono) — date/time handling
88114
- [toml](https://crates.io/crates/toml) — config file parsing
89115
- [anyhow](https://crates.io/crates/anyhow) — error handling
116+
- [keyring](https://crates.io/crates/keyring) — secure credential storage (cross-platform)
90117

91118
> **Note:** `html2md` requires the `panic_unwind` runtime, so `panic = "abort"` cannot be used in the release profile.
92119

docs/api-reference.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,22 @@ username=admin&password=secret
2424
"code": 0,
2525
"message": "success",
2626
"data": {
27-
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
27+
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
28+
"expires_in": 3600,
29+
"refresh_token": "optional-refresh-token"
2830
}
2931
}
3032
```
3133

32-
All subsequent requests use `Authorization: Bearer <access_token>`. werss-cli automatically re-authenticates on 401 responses.
34+
**Fields:**
35+
- `access_token` (required): Bearer token for subsequent API requests
36+
- `expires_in` (optional): Token lifetime in seconds
37+
- `refresh_token` (optional): Refresh token for token renewal (may not be present in all API implementations)
38+
39+
All subsequent requests use `Authorization: Bearer <access_token>`. werss-cli automatically:
40+
- Validates token expiry before each use (5-minute buffer)
41+
- Attempts token refresh on 401 responses (if `refresh_token` is provided)
42+
- Falls back to re-authentication with stored credentials if refresh fails
3343

3444
## Public accounts
3545

docs/architecture.md

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ src/
77
├── main.rs # CLI parsing, orchestration, config resolution, graceful shutdown
88
├── config.rs # Config: TOML parsing, TomlVecOrString type, defaults, example generation
99
├── client.rs # WeClient: HTTP client, auth, API methods, token refresh
10+
├── auth.rs # Token management: keyring storage, validation, refresh
1011
├── convert.rs # HTML-to-Markdown conversion, slug generation, frontmatter, cleanup
1112
└── state.rs # StateStore: JSONL state file, record/lookup/compact logic
1213
```
@@ -26,12 +27,22 @@ Entry point. Responsibilities:
2627

2728
HTTP client for the WeRSS API. Key type: `WeClient`.
2829

29-
- Manages authentication with Bearer token stored in `Mutex<String>`
30-
- Auto-refreshes token on 401 responses (re-login transparently)
30+
- Manages authentication with Bearer token stored in `TokenData`
31+
- Auto-refreshes token on 401 responses (attempts refresh, falls back to re-login)
3132
- Methods: `list_mps`, `update_mp`, `list_articles`, `refresh_article`, `poll_task`, `get_article_detail`, `download_image`
3233
- All API responses are JSON, validated before returning
3334
- Connection errors, timeouts, and non-JSON responses are handled with descriptive errors
3435

36+
### auth.rs
37+
38+
Token management and storage. Key type: `TokenData`.
39+
40+
- Securely stores tokens in system keyring (Linux Secret Service, macOS Keychain, Windows Credential Manager)
41+
- Parses API responses to extract `access_token`, optional `refresh_token`, and `expires_in`
42+
- Validates token expiry with 5-minute buffer to prevent edge cases
43+
- Methods: `from_response()` (parse API response), `is_valid()` (expiry check), `save()` (to keyring), `load()` (from keyring), `delete()` (from keyring)
44+
- Supports automatic token refresh before expiry to maintain session continuity
45+
3546
### config.rs
3647

3748
Configuration management. Key types: `Config`, `ApiConfig`, `SyncConfig`, `TomlVecOrString`.
@@ -97,10 +108,31 @@ werss-cli
97108

98109
## Authentication and token management
99110

100-
1. On startup, `WeClient::new()` calls `POST /api/v1/wx/auth/login` with form-encoded credentials
101-
2. The returned `access_token` is stored in `Mutex<String>`
102-
3. All subsequent API requests include `Authorization: Bearer <token>`
103-
4. On 401 responses, `WeClient::req()` automatically re-logs in and retries the request with the new token
111+
### First Run
112+
1. Check if valid token exists in system keyring
113+
2. If not found or expired, attempt to load credentials from:
114+
- CLI flags (`--username`, `--password`)
115+
- Environment variables (`WE_API_USERNAME`, `WE_API_PASSWORD`)
116+
- Config file (`werss.toml`)
117+
- Interactive prompt (if no credentials provided)
118+
3. Call `POST /api/v1/wx/auth/login` with credentials
119+
4. Extract `access_token` from response (may include optional `refresh_token`)
120+
5. Store token in system keyring with expiry time
121+
6. All subsequent requests use `Authorization: Bearer <token>`
122+
123+
### Subsequent Runs
124+
1. Check keyring for existing token
125+
2. If found and still valid (with 5-minute buffer), use it immediately
126+
3. If expired, attempt automatic refresh:
127+
- If `refresh_token` exists and is valid, call token refresh endpoint
128+
- If refresh fails or no `refresh_token`, fall back to re-authentication with stored/provided credentials
129+
4. Token is automatically saved to keyring for next run
130+
131+
### Token Expiry Handling
132+
- 401 responses trigger automatic token refresh (if `refresh_token` exists)
133+
- Failed refresh falls back to re-authentication with credentials
134+
- Tokens are validated before each use with 5-minute buffer
135+
- System keyring handles cross-platform secure storage (Linux, macOS, Windows)
104136

105137
## Retry and resilience
106138

@@ -125,6 +157,7 @@ werss-cli
125157
| `anyhow` | Error handling with context |
126158
| `regex` | HTML header stripping patterns |
127159
| `toml` | Config file parsing |
160+
| `keyring` | Cross-platform secure token storage (Linux/macOS/Windows) |
128161

129162
### Build note
130163

docs/configuration.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,40 @@ CLI flags > Environment variables > werss.toml > .env > Built-in defaults
88

99
Higher-priority sources override lower ones. All settings are optional — you only need to provide what differs from the defaults.
1010

11+
## Authentication
12+
13+
werss-cli uses a secure token management system for authentication:
14+
15+
### First Run
16+
On the first run, you must provide credentials (username and password). These can be provided via:
17+
- CLI flags: `werss-cli --username admin --password secret`
18+
- Environment variables: `WE_API_USERNAME=admin WE_API_PASSWORD=secret`
19+
- Config file: set `api.username` and `api.password` in `werss.toml`
20+
- Interactive prompt: if no credentials are found, werss-cli will prompt for them
21+
22+
### Subsequent Runs
23+
After the first successful authentication:
24+
1. werss-cli automatically loads the saved token from system keyring
25+
2. The token is reused for all API requests
26+
3. No credentials need to be provided (though you can override them if desired)
27+
28+
### Token Storage
29+
- **Linux**: Stored in GNOME Secret Service or KDE Wallet
30+
- **macOS**: Stored in Keychain
31+
- **Windows**: Stored in Credential Manager
32+
33+
This is more secure than file-based storage as the system handles encryption.
34+
35+
### Token Expiry and Refresh
36+
- If a token expires, werss-cli automatically attempts to refresh it
37+
- If refresh fails, the tool falls back to re-authentication using your credentials
38+
- This ensures uninterrupted operation even when tokens expire
39+
40+
### Configuration Priority for Auth
41+
```
42+
CLI flags > Environment variables > werss.toml > .env > System keyring > Interactive prompt
43+
```
44+
1145
## CLI options
1246

1347
### API Connection

docs/troubleshooting.md

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
| `Cannot connect to API at <url>` | API server is not running or URL is wrong | Check the server is running and verify `api.base` in config |
88
| `API returned non-JSON (HTTP ...)` | URL points to a non-API endpoint | Confirm the API base URL is correct (e.g., `http://host:8001`) |
99
| `Login failed (code=x)` | Wrong username or password | Check `api.username` and `api.password` |
10+
| `No credentials provided and none found in config` | Credentials missing from all sources | Provide via CLI, environment, config file, or respond to the interactive prompt |
11+
| `Failed to save token to keyring` | Keyring service unavailable or access denied | Ensure your system keyring service is running (GNOME Keyring on Linux, Keychain on macOS, Credential Manager on Windows) |
12+
| `Failed to load token from keyring` | Keyring service unavailable or corruption | Try re-authenticating with `--username` and `--password` to resave the token |
13+
| `Token refresh failed, re-authenticating` | Token expired and refresh was unsuccessful | The tool will automatically re-authenticate. This is normal behavior. |
1014
| `No matching public accounts found` | MP IDs don't exist on the server | Use `target_mps = "all"` to discover available IDs |
1115
| `No write permission to output directory` | Output directory is not writable | Change permissions with `chmod` or set a different `output_dir` |
1216
| `Refresh task timeout` | WeChat content fetch took longer than 3 minutes | Re-run to retry. The article may become available later |
@@ -51,10 +55,41 @@ State files are automatically compacted when the line count exceeds 2× the numb
5155

5256
### Token expiry
5357

54-
werss-cli automatically re-authenticates on 401 responses. No configuration or manual intervention needed.
58+
werss-cli automatically manages token expiry:
59+
1. Checks token validity before each run (with 5-minute buffer)
60+
2. If token is expired, attempts automatic refresh (if `refresh_token` is available)
61+
3. If refresh fails, falls back to re-authentication using stored credentials
62+
4. New token is automatically saved to system keyring
63+
64+
No manual intervention needed. The process is transparent to you.
65+
66+
### Credentials not saved between runs
67+
68+
If werss-cli prompts for credentials every run:
69+
1. Check your system keyring service is running:
70+
- **Linux**: `systemctl status gnome-keyring-daemon` or `systemctl status kwalletd`
71+
- **macOS**: Keychain is typically always running
72+
- **Windows**: Credential Manager should be available
73+
2. Try explicitly providing credentials to force resave:
74+
```bash
75+
werss-cli --username admin --password secret
76+
```
77+
3. The token should now be saved and available on next run
5578

5679
## FAQ
5780

81+
**Q: Do I need to provide credentials every time?**
82+
83+
No. On the first run, you provide credentials once. They are used to obtain a token, which is then saved to your system keyring. On subsequent runs, werss-cli loads the token automatically and doesn't need credentials.
84+
85+
**Q: What if my keyring is not available?**
86+
87+
werss-cli will prompt you for credentials whenever the keyring is unavailable. You can also explicitly provide credentials via CLI flags or environment variables, which will always work.
88+
89+
**Q: Can I use werss-cli without a keyring system?**
90+
91+
The keyring integration is automatic and transparent. If your system doesn't have a keyring, werss-cli will fall back to prompting for credentials, but will still function normally.
92+
5893
**Q: Can I run werss-cli without a WeRSS server?**
5994

6095
No. werss-cli is a client for the WeRSS API. You need a running WeRSS server that handles WeChat scraping.

docs/usage.md

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,31 @@
1616

1717
3. **Edit** `werss.toml` — set your API base URL, username, and password.
1818

19-
4. **Run**:
19+
4. **Run** (first time requires credentials):
2020

2121
```bash
2222
./target/release/werss-cli
2323
```
24+
25+
On the first run:
26+
- werss-cli reads credentials from `werss.toml`, environment variables, or CLI flags
27+
- If no credentials are found, it will prompt you to enter them interactively
28+
- After successful authentication, the token is automatically saved to your system keyring
29+
- The tool then proceeds to fetch articles as normal
30+
31+
## Subsequent runs
32+
33+
Simply run the tool again:
34+
35+
```bash
36+
./target/release/werss-cli
37+
```
38+
39+
On subsequent runs:
40+
- werss-cli automatically loads the saved token from your system keyring
41+
- No credentials are needed
42+
- The token is reused for all API requests
43+
- If the token expires, werss-cli automatically refreshes it or prompts for re-authentication if needed
2444

2545
## Common workflows
2646

@@ -104,15 +124,16 @@ werss-cli --config /etc/werss.toml
104124
## What happens when you run werss-cli
105125

106126
1. **Config resolution** — merges CLI flags, env vars, werss.toml, and defaults
107-
2. **Preflight checks** — validates MP list, output directory write permissions
108-
3. **Login** — authenticates with the WeRSS API, obtains a Bearer token
109-
4. **MP resolution**`"all"` fetches all accounts; comma-separated IDs are filtered
110-
5. **Per-MP processing**:
127+
2. **Authentication** — checks keyring for valid token; prompts for credentials if needed
128+
3. **Preflight checks** — validates MP list, output directory write permissions
129+
4. **Token management** — saves token to keyring for next run
130+
5. **MP resolution**`"all"` fetches all accounts; comma-separated IDs are filtered
131+
6. **Per-MP processing**:
111132
- Triggers MP sync from WeChat (`update_mp`, retried 3 times)
112133
- Lists articles with date filtering
113134
- Checks state: skips fetched, identifies exhausted, queues pending
114135
- Fetches pending articles in parallel (max 3 concurrent)
115-
6. **Output** — writes Markdown files and updates state
136+
7. **Output** — writes Markdown files and updates state
116137

117138
### Summary line
118139

0 commit comments

Comments
 (0)