314 lines
6.8 KiB
Markdown
314 lines
6.8 KiB
Markdown
|
|
# Backend fixes required for Wallet Login + Favorites production readiness
|
||
|
|
|
||
|
|
Date: 2026-06-04
|
||
|
|
Environment tested: `https://arkie-library-stag.com/apnew/api`
|
||
|
|
|
||
|
|
## Summary
|
||
|
|
|
||
|
|
Frontend has been updated to the new backend contract:
|
||
|
|
|
||
|
|
- Wallet login: `POST /api/auth/wallet/login` with `{ address }`
|
||
|
|
- Wallet session check: `GET /api/auth/wallet/me` with `Authorization: Bearer <token>`
|
||
|
|
- Favorites list/status: `GET /api/favorites` and `GET /api/favorites?ids=...`
|
||
|
|
- Favorite mutation: `POST /api/posts/{id}/favorite` with `{ add: true|false }`
|
||
|
|
|
||
|
|
Staging confirms the new login endpoint works, but favorite mutation currently accepts an invalid Bearer token. This must be fixed before production trust.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Priority 0 — Fix favorite mutation authentication
|
||
|
|
|
||
|
|
### Current staging behavior
|
||
|
|
|
||
|
|
The following request currently returns `200 OK` even with an invalid token:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
curl -i -X POST \
|
||
|
|
"https://arkie-library-stag.com/apnew/api/posts/8f4a571c-3477-4b05-91be-d85907048de5/favorite" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-H "Authorization: Bearer invalid-token" \
|
||
|
|
--data '{"add":true}'
|
||
|
|
```
|
||
|
|
|
||
|
|
Observed response:
|
||
|
|
|
||
|
|
```http
|
||
|
|
HTTP 200
|
||
|
|
{"ok":true}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Required behavior
|
||
|
|
|
||
|
|
`POST /api/posts/{id}/favorite` must require a valid wallet JWT.
|
||
|
|
|
||
|
|
Invalid, missing, expired, malformed, or unverifiable tokens must return:
|
||
|
|
|
||
|
|
```http
|
||
|
|
HTTP 401 Unauthorized
|
||
|
|
```
|
||
|
|
|
||
|
|
Recommended response body:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"error": "unauthorized"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Acceptance tests
|
||
|
|
|
||
|
|
#### Missing token
|
||
|
|
|
||
|
|
```bash
|
||
|
|
curl -i -X POST \
|
||
|
|
"https://arkie-library-stag.com/apnew/api/posts/{postId}/favorite" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
--data '{"add":true}'
|
||
|
|
```
|
||
|
|
|
||
|
|
Expected:
|
||
|
|
|
||
|
|
```http
|
||
|
|
HTTP 401
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Invalid token
|
||
|
|
|
||
|
|
```bash
|
||
|
|
curl -i -X POST \
|
||
|
|
"https://arkie-library-stag.com/apnew/api/posts/{postId}/favorite" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-H "Authorization: Bearer invalid-token" \
|
||
|
|
--data '{"add":true}'
|
||
|
|
```
|
||
|
|
|
||
|
|
Expected:
|
||
|
|
|
||
|
|
```http
|
||
|
|
HTTP 401
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Valid token
|
||
|
|
|
||
|
|
```bash
|
||
|
|
TOKEN=$(curl -s -X POST \
|
||
|
|
"https://arkie-library-stag.com/apnew/api/auth/wallet/login" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
--data '{"address":"0x0000000000000000000000000000000000000001"}' \
|
||
|
|
| jq -r .token)
|
||
|
|
|
||
|
|
curl -i -X POST \
|
||
|
|
"https://arkie-library-stag.com/apnew/api/posts/{postId}/favorite" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-H "Authorization: Bearer $TOKEN" \
|
||
|
|
--data '{"add":true}'
|
||
|
|
```
|
||
|
|
|
||
|
|
Expected:
|
||
|
|
|
||
|
|
```http
|
||
|
|
HTTP 200
|
||
|
|
```
|
||
|
|
|
||
|
|
Response should include at least:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"ok": true,
|
||
|
|
"favorited": true
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Then cancel:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
curl -i -X POST \
|
||
|
|
"https://arkie-library-stag.com/apnew/api/posts/{postId}/favorite" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-H "Authorization: Bearer $TOKEN" \
|
||
|
|
--data '{"add":false}'
|
||
|
|
```
|
||
|
|
|
||
|
|
Expected:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"ok": true,
|
||
|
|
"favorited": false
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Priority 1 — Confirm wallet login security model
|
||
|
|
|
||
|
|
### Current contract
|
||
|
|
|
||
|
|
```http
|
||
|
|
POST /api/auth/wallet/login
|
||
|
|
Content-Type: application/json
|
||
|
|
|
||
|
|
{
|
||
|
|
"address": "0x..."
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Response:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"token": "<jwt>",
|
||
|
|
"wallet": "0x..."
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
This is what the frontend now uses.
|
||
|
|
|
||
|
|
### Production risk
|
||
|
|
|
||
|
|
This flow does not prove wallet ownership. Any client can submit any wallet address and receive a token for that address.
|
||
|
|
|
||
|
|
If wallet identity is only used for low-risk favorites, this may be acceptable as an MVP. If wallet identity will be used for user identity, permissions, membership, rewards, asset ownership, admin behavior, or anything security-sensitive, backend should require signature verification.
|
||
|
|
|
||
|
|
### Recommended secure production flow
|
||
|
|
|
||
|
|
If stronger security is required, backend should use nonce + signature:
|
||
|
|
|
||
|
|
1. `POST /api/auth/wallet/nonce` with `{ address }`
|
||
|
|
2. Backend returns a one-time message / nonce.
|
||
|
|
3. Frontend asks wallet to sign the message.
|
||
|
|
4. `POST /api/auth/wallet/verify` with `{ address, message, signature }`
|
||
|
|
5. Backend verifies recovered address equals requested address.
|
||
|
|
6. Backend issues JWT.
|
||
|
|
|
||
|
|
If backend decides to keep the simplified `{ address }` login, please explicitly confirm that this is an accepted production risk.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Priority 2 — Normalize favorites response contract
|
||
|
|
|
||
|
|
Frontend currently supports the staging response shape, but the response should be made explicit.
|
||
|
|
|
||
|
|
### Favorites list
|
||
|
|
|
||
|
|
```http
|
||
|
|
GET /api/favorites?lang=&limit=&page=&sort=&category=&q=
|
||
|
|
Authorization: Bearer <token>
|
||
|
|
```
|
||
|
|
|
||
|
|
Current staging response observed:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"items": [
|
||
|
|
{
|
||
|
|
"id": "...",
|
||
|
|
"postType": "image",
|
||
|
|
"categoryId": 11,
|
||
|
|
"categorySlug": "official-assets",
|
||
|
|
"language": "zh",
|
||
|
|
"title": "..."
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Recommended production response:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"items": [],
|
||
|
|
"page": 1,
|
||
|
|
"limit": 24,
|
||
|
|
"total": 0
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
`page`, `limit`, and `total` are needed for correct pagination.
|
||
|
|
|
||
|
|
### Favorite status by ids
|
||
|
|
|
||
|
|
```http
|
||
|
|
GET /api/favorites?ids=id1,id2,id3
|
||
|
|
Authorization: Bearer <token>
|
||
|
|
```
|
||
|
|
|
||
|
|
Current staging response observed:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"items": []
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
This works, but for frontend performance and clarity, recommended response is:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"ids": ["id1", "id3"]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Meaning: only IDs that are already favorited by the current wallet user.
|
||
|
|
|
||
|
|
Frontend currently accepts both:
|
||
|
|
|
||
|
|
- `{ ids: string[] }`
|
||
|
|
- `{ items: Resource[] }`
|
||
|
|
|
||
|
|
But backend should document and standardize one shape.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Priority 3 — Required status codes
|
||
|
|
|
||
|
|
Please standardize these responses:
|
||
|
|
|
||
|
|
| Case | Expected status |
|
||
|
|
| --- | --- |
|
||
|
|
| Missing Bearer token on protected endpoint | `401` |
|
||
|
|
| Invalid/expired Bearer token | `401` |
|
||
|
|
| Valid token but post ID does not exist | `404` |
|
||
|
|
| Invalid JSON body | `400` |
|
||
|
|
| Invalid `add` value | `400` |
|
||
|
|
| Successful favorite add/remove | `200` |
|
||
|
|
|
||
|
|
Protected endpoints:
|
||
|
|
|
||
|
|
- `GET /api/auth/wallet/me`
|
||
|
|
- `GET /api/favorites`
|
||
|
|
- `GET /api/favorites?ids=...`
|
||
|
|
- `POST /api/posts/{id}/favorite`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Frontend compatibility notes
|
||
|
|
|
||
|
|
The frontend currently calls these staging paths through the same-origin prefix:
|
||
|
|
|
||
|
|
```txt
|
||
|
|
/apnew/api/auth/wallet/login
|
||
|
|
/apnew/api/auth/wallet/me
|
||
|
|
/apnew/api/favorites
|
||
|
|
/apnew/api/favorites?ids=...
|
||
|
|
/apnew/api/posts/{id}/favorite
|
||
|
|
```
|
||
|
|
|
||
|
|
In frontend source this is written as `/api/...`; staging build uses `VITE_API_PREFIX=/apnew`.
|
||
|
|
|
||
|
|
Please keep backend routes under `/api/...` behind the proxy.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Final production checklist
|
||
|
|
|
||
|
|
Backend should confirm all of the following before production release:
|
||
|
|
|
||
|
|
- [ ] `POST /api/posts/{id}/favorite` rejects missing token with `401`.
|
||
|
|
- [ ] `POST /api/posts/{id}/favorite` rejects invalid token with `401`.
|
||
|
|
- [ ] `POST /api/posts/{id}/favorite` only changes favorites for the wallet from the validated JWT.
|
||
|
|
- [ ] `GET /api/favorites` requires a valid Bearer token.
|
||
|
|
- [ ] `GET /api/favorites?ids=...` requires a valid Bearer token, unless explicitly declared public/legacy.
|
||
|
|
- [ ] `GET /api/auth/wallet/me` validates token and returns the wallet address from the token.
|
||
|
|
- [ ] Backend explicitly confirms whether simplified `{ address }` login is acceptable for production, or switches to nonce/signature verification.
|