fix(security): treat a new identity key as a message request, never as a known contact
A stranger could pick the display name of an existing contact ("Mom")
and appear as that contact. Messages from a key that is not in the
contact list now open a message request labelled "(unknown)": no
replies, read receipts, typing indicators, calls or name in
notifications until the user accepts it and names the contact
themselves. Names that look like an existing contact (case, diacritics,
Cyrillic/Greek homoglyphs, invisible characters) also show a warning in
the real contact's chat.
When the relay account of a known contact shows up with a different
key, the chat shows "safety number changed", verification is cleared
and the session that now belongs to the other key is dropped before
sending, so a reply never goes to the new key.
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
/// Bezpečnostní upozornění uprostřed konverzace. Vypadá jinak než bublina
|
||||
/// (nikdo ho neposlal), aby nešlo zaměnit se zprávou protistrany.
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sepot/core/l10n/l10n.dart';
|
||||
import 'package:sepot/core/theme/app_theme.dart';
|
||||
import 'package:sepot/models/message.dart';
|
||||
|
||||
class SafetyNoticeRow extends StatelessWidget {
|
||||
const SafetyNoticeRow({
|
||||
super.key,
|
||||
required this.notice,
|
||||
required this.contactName,
|
||||
this.claimedName,
|
||||
this.onVerify,
|
||||
});
|
||||
|
||||
final MessageNotice notice;
|
||||
final String contactName;
|
||||
|
||||
/// Jméno, které si zvolil neznámý odesílatel ([MessageNotice.lookalikeRequest]).
|
||||
final String? claimedName;
|
||||
|
||||
/// Odkaz na porovnání bezpečnostního kódu (null = bez tlačítka).
|
||||
final VoidCallback? onVerify;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final text = switch (notice) {
|
||||
MessageNotice.identityChanged => l10n.noticeIdentityChanged(contactName),
|
||||
MessageNotice.lookalikeRequest =>
|
||||
l10n.noticeLookalikeRequest(claimedName ?? '', contactName),
|
||||
};
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.fromLTRB(14, 12, 14, 6),
|
||||
decoration: BoxDecoration(
|
||||
color: SepotColors.surfaceVariant,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: SepotColors.warning),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Icon(Icons.gpp_maybe_rounded,
|
||||
size: 18, color: SepotColors.warning),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
text,
|
||||
style: const TextStyle(
|
||||
fontSize: 13, color: SepotColors.textPrimary),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (onVerify != null &&
|
||||
notice == MessageNotice.identityChanged)
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: TextButton(
|
||||
onPressed: onVerify,
|
||||
child: Text(l10n.noticeVerifyAction),
|
||||
),
|
||||
)
|
||||
else
|
||||
const SizedBox(height: 6),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user