Files
sepot/app/lib/core/push/push_notifier.dart
T
Julius Joska 717aeed4ff 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.
2026-10-23 10:50:42 +02:00

104 lines
3.6 KiB
Dart

/// Lokální notifikace pro push budíčky.
///
/// Zásada: notifikace NIKDY nenese obsah, odesílatele ani počet zpráv —
/// při killed-state budíčku je DB zamčená (PIN → Argon2id), takže ani
/// technicky NELZE nic dešifrovat. Generické „máš nové zprávy" je tudíž
/// jediná poctivá (a metadata-čistá) možnost — stejný model jako
/// zamčený Threema/Signal.
library;
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:sepot/core/l10n/l10n.dart';
import 'package:sepot/providers/locale_provider.dart';
class PushNotifier {
PushNotifier._();
static final _plugin = FlutterLocalNotificationsPlugin();
static bool _ready = false;
/// Jedno pevné id — opakované budíčky aktualizují touž notifikaci,
/// žádné hromadění (počet by sám o sobě byl metadata leak).
static const int _notificationId = 1;
static Future<void> _ensureInit() async {
if (_ready) return;
final l10n = l10nFor(await LocaleNotifier.readStored());
await _plugin.initialize(
InitializationSettings(
android: const AndroidInitializationSettings('@mipmap/ic_launcher'),
linux: LinuxInitializationSettings(
defaultActionName: l10n.pushActionOpen,
),
),
);
_ready = true;
}
/// Sdílené detaily notifikace — channel ID 'zpravy' je NEMĚNNÉ
/// (Android by při změně vytvořil nový kanál), name/description se
/// lokalizují (systém je čte při vytvoření kanálu).
static NotificationDetails _details(AppLocalizations l10n) {
return NotificationDetails(
android: AndroidNotificationDetails(
'zpravy',
l10n.pushChannelMessagesName,
channelDescription: l10n.pushChannelMessagesDescription,
importance: Importance.high,
priority: Priority.high,
category: AndroidNotificationCategory.message,
),
linux: const LinuxNotificationDetails(),
);
}
/// Android 13+ runtime permission — volat při zapnutí pushe v nastavení.
static Future<void> requestPermission() async {
await _ensureInit();
await _plugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.requestNotificationsPermission();
}
/// Notifikace o přijaté zprávě, když appka běží, ale není v popředí.
/// Jen jméno odesílatele — obsah zprávy do notifikace NIKDY (šifrovaný
/// obsah nepatří do notification logu OS ani na zamčenou obrazovku).
static Future<void> showMessageNotification(String senderName) async {
await _ensureInit();
final l10n = l10nFor(await LocaleNotifier.readStored());
await _plugin.show(
_notificationId,
l10n.commonAppName,
l10n.pushNewMessageFrom(senderName),
_details(l10n),
);
}
/// Zpráva od neznámého odesílatele (žádost o zprávu): bez jména, které
/// si zvolil sám.
static Future<void> showMessageRequestNotification() async {
await _ensureInit();
final l10n = l10nFor(await LocaleNotifier.readStored());
await _plugin.show(
_notificationId,
l10n.commonAppName,
l10n.pushMessageRequest,
_details(l10n),
);
}
/// Generická notifikace bez obsahu. Klepnutí otevře appku (unlock →
/// WS připojení → fronta se vyleje normální cestou).
static Future<void> showWakeNotification() async {
await _ensureInit();
final l10n = l10nFor(await LocaleNotifier.readStored());
await _plugin.show(
_notificationId,
l10n.commonAppName,
l10n.pushNewMessages,
_details(l10n),
);
}
}