privacy/security: add PM privacy levels and improve session visibility
All checks were successful
CI / test (push) Successful in 24s
All checks were successful
CI / test (push) Successful in 24s
This commit is contained in:
@@ -14,6 +14,7 @@ interface UserProfileUpdatePayload {
|
||||
bio?: string | null;
|
||||
avatar_url?: string | null;
|
||||
allow_private_messages?: boolean;
|
||||
privacy_private_messages?: "everyone" | "contacts" | "nobody";
|
||||
privacy_last_seen?: "everyone" | "contacts" | "nobody";
|
||||
privacy_avatar?: "everyone" | "contacts" | "nobody";
|
||||
privacy_group_invites?: "everyone" | "contacts";
|
||||
|
||||
@@ -83,6 +83,7 @@ export interface AuthUser {
|
||||
email_verified: boolean;
|
||||
twofa_enabled?: boolean;
|
||||
allow_private_messages: boolean;
|
||||
privacy_private_messages?: "everyone" | "contacts" | "nobody";
|
||||
privacy_last_seen?: "everyone" | "contacts" | "nobody";
|
||||
privacy_avatar?: "everyone" | "contacts" | "nobody";
|
||||
privacy_group_invites?: "everyone" | "contacts";
|
||||
@@ -101,6 +102,8 @@ export interface AuthSession {
|
||||
created_at: string;
|
||||
ip_address?: string | null;
|
||||
user_agent?: string | null;
|
||||
current?: boolean;
|
||||
token_type?: "access" | "refresh";
|
||||
}
|
||||
|
||||
export interface UserSearchItem {
|
||||
|
||||
@@ -26,7 +26,7 @@ export function SettingsPanel({ open, onClose }: Props) {
|
||||
const [prefs, setPrefs] = useState<AppPreferences>(() => getAppPreferences());
|
||||
const [blockedCount, setBlockedCount] = useState(0);
|
||||
const [savingPrivacy, setSavingPrivacy] = useState(false);
|
||||
const [allowPrivateMessages, setAllowPrivateMessages] = useState(true);
|
||||
const [privacyPrivateMessages, setPrivacyPrivateMessages] = useState<"everyone" | "contacts" | "nobody">("everyone");
|
||||
const [sessions, setSessions] = useState<AuthSession[]>([]);
|
||||
const [sessionsLoading, setSessionsLoading] = useState(false);
|
||||
const [twofaCode, setTwofaCode] = useState("");
|
||||
@@ -52,7 +52,7 @@ export function SettingsPanel({ open, onClose }: Props) {
|
||||
if (!me) {
|
||||
return;
|
||||
}
|
||||
setAllowPrivateMessages(me.allow_private_messages);
|
||||
setPrivacyPrivateMessages(me.privacy_private_messages || (me.allow_private_messages ? "everyone" : "nobody"));
|
||||
setPrivacyLastSeen(me.privacy_last_seen || "everyone");
|
||||
setPrivacyAvatar(me.privacy_avatar || "everyone");
|
||||
setPrivacyGroupInvites(me.privacy_group_invites || "everyone");
|
||||
@@ -314,7 +314,13 @@ export function SettingsPanel({ open, onClose }: Props) {
|
||||
/>
|
||||
<SettingsRow
|
||||
label="Privacy and Security"
|
||||
value={allowPrivateMessages ? "Everybody can message" : "Messages disabled"}
|
||||
value={
|
||||
privacyPrivateMessages === "everyone"
|
||||
? "Everybody can message"
|
||||
: privacyPrivateMessages === "contacts"
|
||||
? "Only contacts can message"
|
||||
: "Messages disabled"
|
||||
}
|
||||
onClick={() => setPage("privacy")}
|
||||
/>
|
||||
</section>
|
||||
@@ -417,6 +423,18 @@ export function SettingsPanel({ open, onClose }: Props) {
|
||||
<section className="rounded border border-slate-700/70 bg-slate-800/50 p-3">
|
||||
<p className="mb-2 text-xs uppercase tracking-wide text-slate-400">Privacy</p>
|
||||
<SettingsTextRow label="Blocked Users" value={String(blockedCount)} />
|
||||
<div className="mb-2 rounded bg-slate-900/50 px-2 py-2">
|
||||
<p className="mb-1 text-xs text-slate-300">Who can send me private messages?</p>
|
||||
<select
|
||||
className="w-full rounded bg-slate-800 px-2 py-1 text-xs"
|
||||
value={privacyPrivateMessages}
|
||||
onChange={(e) => setPrivacyPrivateMessages(e.target.value as "everyone" | "contacts" | "nobody")}
|
||||
>
|
||||
<option value="everyone">Everybody</option>
|
||||
<option value="contacts">My contacts</option>
|
||||
<option value="nobody">Nobody</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="mb-2 rounded bg-slate-900/50 px-2 py-2">
|
||||
<p className="mb-1 text-xs text-slate-300">Who can see my profile photo?</p>
|
||||
<select
|
||||
@@ -458,7 +476,8 @@ export function SettingsPanel({ open, onClose }: Props) {
|
||||
setSavingPrivacy(true);
|
||||
try {
|
||||
const updated = await updateMyProfile({
|
||||
allow_private_messages: allowPrivateMessages,
|
||||
allow_private_messages: privacyPrivateMessages !== "nobody",
|
||||
privacy_private_messages: privacyPrivateMessages,
|
||||
privacy_last_seen: privacyLastSeen,
|
||||
privacy_avatar: privacyAvatar,
|
||||
privacy_group_invites: privacyGroupInvites,
|
||||
@@ -586,19 +605,27 @@ export function SettingsPanel({ open, onClose }: Props) {
|
||||
<div className="space-y-2">
|
||||
{sessions.map((session) => (
|
||||
<div className="rounded bg-slate-900/50 px-2 py-2" key={session.jti}>
|
||||
<p className="truncate text-xs text-slate-300">{session.user_agent || "Unknown device"}</p>
|
||||
<p className="truncate text-xs text-slate-300">
|
||||
{session.user_agent || "Unknown device"}
|
||||
{session.current ? " (current)" : ""}
|
||||
</p>
|
||||
<p className="truncate text-[11px] text-slate-500">Token: {session.token_type || "refresh"}</p>
|
||||
<p className="truncate text-[11px] text-slate-400">{session.ip_address || "Unknown IP"}</p>
|
||||
<p className="text-[11px] text-slate-500">{new Date(session.created_at).toLocaleString()}</p>
|
||||
<button
|
||||
className="mt-1 rounded bg-slate-700 px-2 py-1 text-[11px]"
|
||||
onClick={async () => {
|
||||
await revokeSession(session.jti);
|
||||
setSessions((prev) => prev.filter((item) => item.jti !== session.jti));
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
Revoke
|
||||
</button>
|
||||
{session.token_type === "refresh" ? (
|
||||
<button
|
||||
className="mt-1 rounded bg-slate-700 px-2 py-1 text-[11px]"
|
||||
onClick={async () => {
|
||||
await revokeSession(session.jti);
|
||||
setSessions((prev) => prev.filter((item) => item.jti !== session.jti));
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
Revoke
|
||||
</button>
|
||||
) : (
|
||||
<p className="mt-1 text-[11px] text-slate-500">Use Revoke all to invalidate active access token.</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user