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",
"authDeviceChallenges": "Device Usage",
"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",
"auto": "Auto",
"manual": "Manual",

View File

@@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/account.dart';
import 'package:island/pods/network.dart';
@@ -37,11 +38,13 @@ class _DeviceListTile extends StatelessWidget {
final SnAuthDeviceWithSession device;
final Function(String) updateDeviceLabel;
final Function(String) logoutDevice;
final Function(String) logoutSession;
const _DeviceListTile({
required this.device,
required this.updateDeviceLabel,
required this.logoutDevice,
required this.logoutSession,
});
@override
@@ -118,7 +121,11 @@ class _DeviceListTile extends StatelessWidget {
),
...device.sessions
.map(
(session) => Column(
(session) => Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 4,
children: [
@@ -130,7 +137,9 @@ class _DeviceListTile extends StatelessWidget {
),
InfoRow(
label: 'lastActiveAt'.tr(
args: [session.lastGrantedAt.toLocal().formatSystem()],
args: [
session.lastGrantedAt.toLocal().formatSystem(),
],
),
icon: Symbols.refresh_rounded,
),
@@ -145,6 +154,15 @@ class _DeviceListTile extends StatelessWidget {
icon: Symbols.dns,
),
],
),
),
IconButton(
icon: Icon(Icons.logout),
tooltip: 'authSessionLogout'.tr(),
onPressed: () => logoutSession(session.id),
),
const Gap(4),
],
).padding(horizontal: 20, vertical: 8),
)
.expand((element) => [element, const Divider(height: 1)])
@@ -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 {
final controller = TextEditingController();
final label = await showDialog<String>(
@@ -265,6 +299,7 @@ class AccountSessionSheet extends HookConsumerWidget {
device: device,
updateDeviceLabel: updateDeviceLabel,
logoutDevice: logoutDevice,
logoutSession: logoutSession,
);
} else {
return Dismissible(
@@ -320,6 +355,7 @@ class AccountSessionSheet extends HookConsumerWidget {
device: device,
updateDeviceLabel: updateDeviceLabel,
logoutDevice: logoutDevice,
logoutSession: logoutSession,
),
);
}