web: fix reset password token flow and auth interceptor
This commit is contained in:
55
docs/backend-web-android-parity.md
Normal file
55
docs/backend-web-android-parity.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Backend/Web/Android Parity Snapshot (2026-03-09)
|
||||
|
||||
## 1) Backend vs Web
|
||||
|
||||
Backend покрывает web-функционал почти полностью (`~95-100%`):
|
||||
|
||||
- `auth`: login/refresh/me, register, verify-email, resend verification, request/reset password, sessions, 2FA
|
||||
- `chats`: list/detail, saved, discover, create/join/leave, members/bans, title/profile, pin/archive, invite-link, notifications, clear/delete
|
||||
- `messages`: list/send/edit/delete, status, search/thread, forward/bulk, reactions
|
||||
- `media`: upload-url, attachments create/list
|
||||
- `realtime`: websocket + typing/read/delivered/ping-pong
|
||||
- `users`: search/profile/blocked/contacts
|
||||
- `search`: global search
|
||||
- `notifications`: list
|
||||
|
||||
Вывод: текущие проблемы в основном на стороне клиентской интеграции/UX, не backend-contract.
|
||||
|
||||
## 2) Web endpoints not yet fully used on Android
|
||||
|
||||
- `GET /api/v1/chats/saved`
|
||||
- `PATCH /api/v1/chats/{chat_id}/title`
|
||||
- `PATCH /api/v1/chats/{chat_id}/profile`
|
||||
- `POST /api/v1/chats/{chat_id}/archive`
|
||||
- `POST /api/v1/chats/{chat_id}/unarchive`
|
||||
- `POST /api/v1/chats/{chat_id}/pin-chat`
|
||||
- `POST /api/v1/chats/{chat_id}/unpin-chat`
|
||||
- `DELETE /api/v1/chats/{chat_id}`
|
||||
- `POST /api/v1/chats/{chat_id}/clear`
|
||||
- `GET /api/v1/chats/{chat_id}/notifications`
|
||||
- `PUT /api/v1/chats/{chat_id}/notifications`
|
||||
- `GET /api/v1/messages/{message_id}/thread`
|
||||
- `GET /api/v1/search` (single global endpoint; Android uses composed search calls)
|
||||
- Contacts endpoints:
|
||||
- `GET /api/v1/users/contacts`
|
||||
- `POST /api/v1/users/{user_id}/contacts`
|
||||
- `POST /api/v1/users/contacts/by-email`
|
||||
- `DELETE /api/v1/users/{user_id}/contacts`
|
||||
- `GET /api/v1/notifications`
|
||||
- `POST /api/v1/auth/resend-verification`
|
||||
|
||||
## 3) Practical status
|
||||
|
||||
- Backend readiness vs Web: `high`
|
||||
- Android parity vs Web (feature-level): `~70-80%`
|
||||
|
||||
## 4) Highest-priority Android parity step
|
||||
|
||||
Подключить в Android реальные действия для chats list popup/select:
|
||||
|
||||
- archive/unarchive
|
||||
- pin/unpin chat
|
||||
- delete/clear chat
|
||||
- chat notification settings
|
||||
|
||||
и перевести текущие UI-заглушки на API-вызовы.
|
||||
@@ -14,7 +14,7 @@ export async function requestPasswordResetRequest(email: string): Promise<void>
|
||||
}
|
||||
|
||||
export async function resetPasswordRequest(token: string, password: string): Promise<void> {
|
||||
await http.post("/auth/reset-password", { token, password });
|
||||
await http.post("/auth/reset-password", { token, new_password: password });
|
||||
}
|
||||
|
||||
export async function loginRequest(email: string, password: string, otpCode?: string, recoveryCode?: string): Promise<TokenPair> {
|
||||
|
||||
@@ -21,7 +21,16 @@ let refreshInFlight: Promise<void> | null = null;
|
||||
|
||||
function shouldSkipRefresh(config?: InternalAxiosRequestConfig): boolean {
|
||||
const url = config?.url ?? "";
|
||||
return url.includes("/auth/login") || url.includes("/auth/refresh");
|
||||
return (
|
||||
url.includes("/auth/login") ||
|
||||
url.includes("/auth/refresh") ||
|
||||
url.includes("/auth/register") ||
|
||||
url.includes("/auth/check-email") ||
|
||||
url.includes("/auth/verify-email") ||
|
||||
url.includes("/auth/resend-verification") ||
|
||||
url.includes("/auth/request-password-reset") ||
|
||||
url.includes("/auth/reset-password")
|
||||
);
|
||||
}
|
||||
|
||||
http.interceptors.response.use(
|
||||
|
||||
@@ -76,8 +76,11 @@ export function App() {
|
||||
return;
|
||||
}
|
||||
window.localStorage.setItem(PENDING_RESET_PASSWORD_TOKEN_KEY, resetToken);
|
||||
if (accessToken) {
|
||||
logout();
|
||||
}
|
||||
window.history.replaceState(null, "", "/");
|
||||
}, []);
|
||||
}, [accessToken, logout]);
|
||||
|
||||
useEffect(() => {
|
||||
const nav = extractNotificationNavigationFromLocation();
|
||||
@@ -221,10 +224,28 @@ function extractPasswordResetTokenFromLocation(): string | null {
|
||||
return null;
|
||||
}
|
||||
const url = new URL(window.location.href);
|
||||
if (!/^\/reset-password\/?$/i.test(url.pathname)) {
|
||||
if (!/^\/reset-password(?:\/[^/]+)?\/?$/i.test(url.pathname)) {
|
||||
return null;
|
||||
}
|
||||
return url.searchParams.get("token")?.trim() || null;
|
||||
const tokenFromQuery =
|
||||
url.searchParams.get("token")?.trim() ||
|
||||
url.searchParams.get("reset_token")?.trim();
|
||||
if (tokenFromQuery) {
|
||||
return tokenFromQuery;
|
||||
}
|
||||
const pathMatch = url.pathname.match(/^\/reset-password\/([^/]+)\/?$/i);
|
||||
if (pathMatch?.[1]?.trim()) {
|
||||
return pathMatch[1].trim();
|
||||
}
|
||||
if (url.hash) {
|
||||
const hash = url.hash.replace(/^#/, "");
|
||||
const hashParams = new URLSearchParams(hash);
|
||||
const tokenFromHash = hashParams.get("token")?.trim() || hashParams.get("reset_token")?.trim();
|
||||
if (tokenFromHash) {
|
||||
return tokenFromHash;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function inviteJoinErrorMessage(error: unknown): string {
|
||||
|
||||
Reference in New Issue
Block a user