mirror of
https://github.com/fromchat-messenger/web.git
synced 2026-09-22 19:15:08 +03:00
Compare commits
3 Commits
@@ -71,10 +71,10 @@ export async function checkAuth(token: string): Promise<CheckAuthResponse> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs in a user with username and password
|
||||
* Logs in a user with username and password (step-based auth).
|
||||
*/
|
||||
export async function login(request: LoginRequest): Promise<LoginResponse> {
|
||||
const res = await fetch(`${API_BASE_URL}/login`, {
|
||||
const res = await fetch(`${API_BASE_URL}/auth/steps/password`, {
|
||||
method: "POST",
|
||||
headers: getAuthHeaders(null, true),
|
||||
body: JSON.stringify(request)
|
||||
@@ -83,17 +83,28 @@ export async function login(request: LoginRequest): Promise<LoginResponse> {
|
||||
const error = await res.json().catch(() => ({ detail: "Login failed" }));
|
||||
throw new Error(error.detail || "Login failed");
|
||||
}
|
||||
return await res.json();
|
||||
const data = await res.json();
|
||||
if (data.status === "needs_register") {
|
||||
throw new Error("Account not found");
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new user
|
||||
* Registers a new user (step-based auth; Yandex proof required when enabled on server).
|
||||
*/
|
||||
export async function register(request: RegisterRequest): Promise<LoginResponse> {
|
||||
const res = await fetch(`${API_BASE_URL}/register`, {
|
||||
export async function register(request: RegisterRequest & { registration_proof?: string }): Promise<LoginResponse> {
|
||||
const res = await fetch(`${API_BASE_URL}/auth/steps/register/confirm`, {
|
||||
method: "POST",
|
||||
headers: getAuthHeaders(null, true),
|
||||
body: JSON.stringify(request)
|
||||
body: JSON.stringify({
|
||||
display_name: request.display_name,
|
||||
username: request.username,
|
||||
password: request.password,
|
||||
confirm_password: request.confirm_password,
|
||||
bio: request.bio,
|
||||
registration_proof: request.registration_proof,
|
||||
})
|
||||
});
|
||||
if (!res.ok) {
|
||||
const error = await res.json().catch(() => ({ detail: "Registration failed" }));
|
||||
|
||||
@@ -5,7 +5,7 @@ import { request } from "@/core/websocket";
|
||||
import type { DmEnvelope, User } from "@/core/types";
|
||||
import { ub64 } from "@/utils/utils";
|
||||
import { fetchUserPublicKey } from "../crypto/identity";
|
||||
import { fetchUsers, searchUsers } from "../user/search";
|
||||
import { searchUsers } from "../user/search";
|
||||
import { deriveWrappingKey, importAesGcmKey, aesGcmDecrypt } from "@fromchat/protocol";
|
||||
import tweetnacl from "tweetnacl";
|
||||
|
||||
@@ -320,4 +320,4 @@ export async function editMessage(
|
||||
}
|
||||
|
||||
// Re-export user functions for convenience
|
||||
export { fetchUsers, searchUsers, fetchUserPublicKey };
|
||||
export { searchUsers, fetchUserPublicKey };
|
||||
@@ -3,7 +3,7 @@ import { getAuthHeaders } from "./account";
|
||||
import { request } from "@/core/websocket";
|
||||
import type { DmEnvelope, User } from "@/core/types";
|
||||
import { fetchUserPublicKey } from "./crypto";
|
||||
import { fetchUsers, searchUsers } from "./users";
|
||||
import { searchUsers } from "./users";
|
||||
|
||||
/**
|
||||
* Decrypt a DM envelope using client-side MEK unwrapping.
|
||||
@@ -25,7 +25,7 @@ export async function fetchDMHistory(userId: number, token: string, limit: numbe
|
||||
}
|
||||
|
||||
// Re-export user functions for convenience
|
||||
export { fetchUsers, searchUsers, fetchUserPublicKey };
|
||||
export { searchUsers, fetchUserPublicKey };
|
||||
|
||||
/**
|
||||
* Send DM via WebSocket using transport encryption.
|
||||
|
||||
@@ -3,7 +3,7 @@ import { getAuthHeaders } from "./account";
|
||||
import { request } from "@/core/websocket";
|
||||
import type { DmEnvelope, User } from "@/core/types";
|
||||
import { fetchUserPublicKey } from "./crypto";
|
||||
import { fetchUsers, searchUsers } from "./users";
|
||||
import { searchUsers } from "./users";
|
||||
|
||||
export async function decryptDm(envelope: DmEnvelope): Promise<string> {
|
||||
const { decrypt } = await import("./chats/dm");
|
||||
@@ -20,7 +20,7 @@ export async function fetchDMHistory(userId: number, token: string, limit: numbe
|
||||
}
|
||||
|
||||
// Re-export user functions for convenience
|
||||
export { fetchUsers, searchUsers, fetchUserPublicKey };
|
||||
export { searchUsers, fetchUserPublicKey };
|
||||
|
||||
export async function sendDMViaWebSocket(recipientId: number, recipientPublicKeyB64: string, plaintext: string, authToken: string, replyToId?: number): Promise<void> {
|
||||
const { send } = await import("./chats/dm");
|
||||
|
||||
@@ -70,11 +70,25 @@ export async function checkAuth(token: string): Promise<CheckAuthResponse> {
|
||||
return await res.json();
|
||||
}
|
||||
|
||||
export interface RegisterConfirmRequest {
|
||||
display_name: string;
|
||||
username: string;
|
||||
password: string;
|
||||
confirm_password: string;
|
||||
bio?: string;
|
||||
registration_proof?: string;
|
||||
}
|
||||
|
||||
export interface AuthPasswordNeedsRegister {
|
||||
status: "needs_register";
|
||||
yandex_required: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs in a user with username and password
|
||||
* Logs in a user with username and password (step-based auth).
|
||||
*/
|
||||
export async function login(request: LoginRequest): Promise<LoginResponse> {
|
||||
const res = await fetch(`${API_BASE_URL}/login`, {
|
||||
const res = await fetch(`${API_BASE_URL}/auth/steps/password`, {
|
||||
method: "POST",
|
||||
headers: getAuthHeaders(null, true),
|
||||
body: JSON.stringify(request)
|
||||
@@ -83,17 +97,28 @@ export async function login(request: LoginRequest): Promise<LoginResponse> {
|
||||
const error = await res.json().catch(() => ({ detail: "Login failed" }));
|
||||
throw new Error(error.detail || "Login failed");
|
||||
}
|
||||
return await res.json();
|
||||
const data = await res.json();
|
||||
if (data.status === "needs_register") {
|
||||
throw new Error("Account not found");
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new user
|
||||
* Registers a new user (step-based auth; Yandex proof required when enabled on server).
|
||||
*/
|
||||
export async function register(request: RegisterRequest): Promise<LoginResponse> {
|
||||
const res = await fetch(`${API_BASE_URL}/register`, {
|
||||
export async function register(request: RegisterRequest & { registration_proof?: string }): Promise<LoginResponse> {
|
||||
const res = await fetch(`${API_BASE_URL}/auth/steps/register/confirm`, {
|
||||
method: "POST",
|
||||
headers: getAuthHeaders(null, true),
|
||||
body: JSON.stringify(request)
|
||||
body: JSON.stringify({
|
||||
display_name: request.display_name,
|
||||
username: request.username,
|
||||
password: request.password,
|
||||
confirm_password: request.confirm_password,
|
||||
bio: request.bio,
|
||||
registration_proof: request.registration_proof,
|
||||
})
|
||||
});
|
||||
if (!res.ok) {
|
||||
const error = await res.json().catch(() => ({ detail: "Registration failed" }));
|
||||
|
||||
@@ -2,16 +2,6 @@ import { API_BASE_URL } from "@/core/config";
|
||||
import { getAuthHeaders } from "./auth";
|
||||
import type { User } from "@/core/types";
|
||||
|
||||
/**
|
||||
* Fetches a list of all users (excluding current user)
|
||||
*/
|
||||
export async function fetchUsers(token: string): Promise<User[]> {
|
||||
const res = await fetch(`${API_BASE_URL}/users`, { headers: getAuthHeaders(token, true) });
|
||||
if (!res.ok) return [];
|
||||
const data = await res.json();
|
||||
return data.users || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for users by username query
|
||||
*/
|
||||
|
||||
@@ -2,16 +2,6 @@ import { API_BASE_URL } from "@/core/config";
|
||||
import { getAuthHeaders } from "./account";
|
||||
import type { User } from "@/core/types";
|
||||
|
||||
/**
|
||||
* Fetches a list of all users (excluding current user)
|
||||
*/
|
||||
export async function fetchUsers(token: string): Promise<User[]> {
|
||||
const res = await fetch(`${API_BASE_URL}/users`, { headers: getAuthHeaders(token, true) });
|
||||
if (!res.ok) return [];
|
||||
const data = await res.json();
|
||||
return data.users || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for users by username query
|
||||
*/
|
||||
|
||||
Vendored
+1
@@ -182,6 +182,7 @@ export interface RegisterRequest {
|
||||
display_name: string;
|
||||
password: string;
|
||||
confirm_password: string;
|
||||
bio?: string;
|
||||
}
|
||||
|
||||
export interface UploadPublicKeyRequest {
|
||||
|
||||
@@ -22,6 +22,16 @@ export function isDeletedPeer(user: {
|
||||
return isDeletedUser(user) || isDeletedAccountUsername(user.username);
|
||||
}
|
||||
|
||||
/** Peers should treat suspended accounts like deleted (no PII). */
|
||||
export function isRedactedPeer(user: {
|
||||
id?: number;
|
||||
deleted?: boolean;
|
||||
suspended?: boolean;
|
||||
username?: string | null;
|
||||
}): boolean {
|
||||
return isDeletedPeer(user) || isSuspendedUser(user);
|
||||
}
|
||||
|
||||
export const DELETED_ACCOUNT_LABEL = "Deleted account";
|
||||
|
||||
export function deletedUserLabel(): string {
|
||||
@@ -33,8 +43,9 @@ export function displayNameForUser(user: {
|
||||
display_name?: string | null;
|
||||
username?: string | null;
|
||||
deleted?: boolean;
|
||||
suspended?: boolean;
|
||||
}): string {
|
||||
if (isDeletedPeer(user)) {
|
||||
if (isRedactedPeer(user)) {
|
||||
return deletedUserLabel();
|
||||
}
|
||||
return user.display_name?.trim() || user.username?.trim() || "";
|
||||
|
||||
@@ -10,7 +10,7 @@ import { onlineStatusManager } from "@/core/onlineStatusManager";
|
||||
import { OnlineIndicator } from "@/pages/chat/ui/right/OnlineIndicator";
|
||||
import defaultAvatar from "@/images/default-avatar.png";
|
||||
import { MaterialBadge, MaterialCircularProgress, MaterialIcon, MaterialList, MaterialListItem } from "@/utils/material";
|
||||
import { displayNameForUser, isDeletedPeer } from "@/core/userDisplay";
|
||||
import { displayNameForUser, isRedactedPeer } from "@/core/userDisplay";
|
||||
import { DeletedUserAvatar } from "@/core/DeletedUserAvatar";
|
||||
import styles from "@/pages/chat/css/left-panel.module.scss";
|
||||
|
||||
@@ -229,7 +229,7 @@ export function UnifiedChatsList() {
|
||||
);
|
||||
}
|
||||
|
||||
const isDeletedDm = isDeletedPeer(chat);
|
||||
const isDeletedDm = isRedactedPeer(chat);
|
||||
const displayName = displayNameForUser({ ...chat, id: chat.id });
|
||||
|
||||
return (
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useImmer } from "use-immer";
|
||||
import { createPortal } from "react-dom";
|
||||
import { parseProfileLink } from "@/core/profileLinks";
|
||||
import { MaterialCircularProgress, MaterialIconButton, MaterialList, MaterialListItem } from "@/utils/material";
|
||||
import { displayNameForUser, isDeletedPeer } from "@/core/userDisplay";
|
||||
import { displayNameForUser, isRedactedPeer } from "@/core/userDisplay";
|
||||
import { DeletedUserAvatar } from "@/core/DeletedUserAvatar";
|
||||
import styles from "@/pages/chat/css/Message.module.scss";
|
||||
import replyPreviewStyles from "@/pages/chat/css/reply-preview.module.scss";
|
||||
@@ -519,7 +519,7 @@ export function Message({ message, isAuthor, onContextMenu, onReactionClick, isD
|
||||
return messageText.length > 0 && emojiRegex.test(messageText) && messageText.length <= 4; // Most emojis are 1-4 characters
|
||||
}, [messageText]);
|
||||
|
||||
const isDeletedSender = isDeletedPeer({ id: message.user_id, username: message.username });
|
||||
const isDeletedSender = isRedactedPeer({ id: message.user_id, username: message.username });
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user