Logout a single session of a authorized device

This commit is contained in:
2025-12-04 01:07:40 +08:00
parent b5262137ad
commit fe365e8c6d
2 changed files with 59 additions and 21 deletions

View File

@@ -1020,6 +1020,8 @@
"uploadFile": "Upload File", "uploadFile": "Upload File",
"authDeviceChallenges": "Device Usage", "authDeviceChallenges": "Device Usage",
"authDeviceHint": "Swipe left to edit label, swipe right to logout device.", "authDeviceHint": "Swipe left to edit label, swipe right to logout device.",
"authSessionLogout": "Logout Session",
"authSessionLogoutHint": "Are you sure you want to logout this session? This will terminate this specific login session.",
"settingsMessageDisplayStyle": "Message Display Style", "settingsMessageDisplayStyle": "Message Display Style",
"auto": "Auto", "auto": "Auto",
"manual": "Manual", "manual": "Manual",

View File

@@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/account.dart'; import 'package:island/models/account.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
@@ -37,11 +38,13 @@ class _DeviceListTile extends StatelessWidget {
final SnAuthDeviceWithSession device; final SnAuthDeviceWithSession device;
final Function(String) updateDeviceLabel; final Function(String) updateDeviceLabel;
final Function(String) logoutDevice; final Function(String) logoutDevice;
final Function(String) logoutSession;
const _DeviceListTile({ const _DeviceListTile({
required this.device, required this.device,
required this.updateDeviceLabel, required this.updateDeviceLabel,
required this.logoutDevice, required this.logoutDevice,
required this.logoutSession,
}); });
@override @override
@@ -118,32 +121,47 @@ class _DeviceListTile extends StatelessWidget {
), ),
...device.sessions ...device.sessions
.map( .map(
(session) => Column( (session) => Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
spacing: 4,
children: [ children: [
InfoRow( Expanded(
label: 'createdAt'.tr( child: Column(
args: [session.createdAt.toLocal().formatSystem()], crossAxisAlignment: CrossAxisAlignment.start,
spacing: 4,
children: [
InfoRow(
label: 'createdAt'.tr(
args: [session.createdAt.toLocal().formatSystem()],
),
icon: Symbols.join,
),
InfoRow(
label: 'lastActiveAt'.tr(
args: [
session.lastGrantedAt.toLocal().formatSystem(),
],
),
icon: Symbols.refresh_rounded,
),
InfoRow(
label:
'${'location'.tr()} ${session.location?.city ?? 'unknown'.tr()}',
icon: Symbols.pin_drop,
),
InfoRow(
label:
'${'ipAddress'.tr()} ${session.ipAddress ?? 'unknown'.tr()}',
icon: Symbols.dns,
),
],
), ),
icon: Symbols.join,
), ),
InfoRow( IconButton(
label: 'lastActiveAt'.tr( icon: Icon(Icons.logout),
args: [session.lastGrantedAt.toLocal().formatSystem()], tooltip: 'authSessionLogout'.tr(),
), onPressed: () => logoutSession(session.id),
icon: Symbols.refresh_rounded,
),
InfoRow(
label:
'${'location'.tr()} ${session.location?.city ?? 'unknown'.tr()}',
icon: Symbols.pin_drop,
),
InfoRow(
label:
'${'ipAddress'.tr()} ${session.ipAddress ?? 'unknown'.tr()}',
icon: Symbols.dns,
), ),
const Gap(4),
], ],
).padding(horizontal: 20, vertical: 8), ).padding(horizontal: 20, vertical: 8),
) )
@@ -178,6 +196,22 @@ class AccountSessionSheet extends HookConsumerWidget {
} }
} }
void logoutSession(String sessionId) async {
final confirm = await showConfirmAlert(
'authSessionLogoutHint'.tr(),
'authSessionLogout'.tr(),
isDanger: true,
);
if (!confirm || !context.mounted) return;
try {
final apiClient = ref.watch(apiClientProvider);
await apiClient.delete('/pass/accounts/me/sessions/$sessionId');
ref.invalidate(authDevicesProvider);
} catch (err) {
showErrorAlert(err);
}
}
void updateDeviceLabel(String sessionId) async { void updateDeviceLabel(String sessionId) async {
final controller = TextEditingController(); final controller = TextEditingController();
final label = await showDialog<String>( final label = await showDialog<String>(
@@ -265,6 +299,7 @@ class AccountSessionSheet extends HookConsumerWidget {
device: device, device: device,
updateDeviceLabel: updateDeviceLabel, updateDeviceLabel: updateDeviceLabel,
logoutDevice: logoutDevice, logoutDevice: logoutDevice,
logoutSession: logoutSession,
); );
} else { } else {
return Dismissible( return Dismissible(
@@ -320,6 +355,7 @@ class AccountSessionSheet extends HookConsumerWidget {
device: device, device: device,
updateDeviceLabel: updateDeviceLabel, updateDeviceLabel: updateDeviceLabel,
logoutDevice: logoutDevice, logoutDevice: logoutDevice,
logoutSession: logoutSession,
), ),
); );
} }