Dispose current session when logout

This commit is contained in:
LittleSheep 2025-03-15 21:11:49 +08:00
parent f03d80ba88
commit 1b7e668b3f
7 changed files with 63 additions and 9 deletions

View File

@ -813,5 +813,6 @@
"eventMetadata": "Metadata", "eventMetadata": "Metadata",
"authTicketCreatedAt": "Issued at {}", "authTicketCreatedAt": "Issued at {}",
"authTicketExpiredAt": "Expired at {}", "authTicketExpiredAt": "Expired at {}",
"authTicketLastGrantAt": "Last granted at {}" "authTicketLastGrantAt": "Last granted at {}",
"authTicketCurrent": "Current"
} }

View File

@ -813,5 +813,6 @@
"accountAuthTicketsDescription": "查看和管理你的授权会话。", "accountAuthTicketsDescription": "查看和管理你的授权会话。",
"authTicketCreatedAt": "签发于 {}", "authTicketCreatedAt": "签发于 {}",
"authTicketExpiredAt": "到期于 {}", "authTicketExpiredAt": "到期于 {}",
"authTicketLastGrantAt": "上次刷新于 {}" "authTicketLastGrantAt": "上次刷新于 {}",
"authTicketCurrent": "当前会话"
} }

View File

@ -813,5 +813,6 @@
"accountAuthTicketsDescription": "查看和管理你的授權會話。", "accountAuthTicketsDescription": "查看和管理你的授權會話。",
"authTicketCreatedAt": "簽發於 {}", "authTicketCreatedAt": "簽發於 {}",
"authTicketExpiredAt": "到期於 {}", "authTicketExpiredAt": "到期於 {}",
"authTicketLastGrantAt": "上次刷新於 {}" "authTicketLastGrantAt": "上次刷新於 {}",
"authTicketCurrent": "當前會話"
} }

View File

@ -813,5 +813,6 @@
"accountAuthTicketsDescription": "查看和管理你的授權會話。", "accountAuthTicketsDescription": "查看和管理你的授權會話。",
"authTicketCreatedAt": "簽發於 {}", "authTicketCreatedAt": "簽發於 {}",
"authTicketExpiredAt": "到期於 {}", "authTicketExpiredAt": "到期於 {}",
"authTicketLastGrantAt": "上次刷新於 {}" "authTicketLastGrantAt": "上次刷新於 {}",
"authTicketCurrent": "當前會話"
} }

View File

@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@ -35,6 +37,32 @@ class UserProvider extends ChangeNotifier {
}); });
} }
Future<Map<String, dynamic>?> get atkClaims async {
final tk = (await atk);
if (tk == null) return null;
final atkParts = tk.split('.');
if (atkParts.length != 3) {
throw Exception('invalid format of access token');
}
var rawPayload = atkParts[1].replaceAll('-', '+').replaceAll('_', '/');
switch (rawPayload.length % 4) {
case 0:
break;
case 2:
rawPayload += '==';
break;
case 3:
rawPayload += '=';
break;
default:
throw Exception('illegal format of access token payload');
}
final b64 = utf8.fuse(base64Url);
return jsonDecode(b64.decode(rawPayload));
}
Future<SnAccount?> refreshUser() async { Future<SnAccount?> refreshUser() async {
final resp = await _sn.client.get('/cgi/id/users/me'); final resp = await _sn.client.get('/cgi/id/users/me');
final out = SnAccount.fromJson(resp.data); final out = SnAccount.fromJson(resp.data);
@ -47,7 +75,13 @@ class UserProvider extends ChangeNotifier {
} }
void logoutUser() async { void logoutUser() async {
atkClaims.then((value) async {
if (value != null) {
await _sn.client.delete('/cgi/id/users/me/tickets/${value['sed']}');
logging.info('[Auth] Current session has been destroyed.');
}
_sn.clearTokenPair(); _sn.clearTokenPair();
});
isAuthorized = false; isAuthorized = false;
user = null; user = null;
notifyListeners(); notifyListeners();

View File

@ -13,7 +13,7 @@ import 'package:surface/screens/account/profile_edit.dart';
import 'package:surface/screens/account/publishers/publisher_edit.dart'; import 'package:surface/screens/account/publishers/publisher_edit.dart';
import 'package:surface/screens/account/publishers/publisher_new.dart'; import 'package:surface/screens/account/publishers/publisher_new.dart';
import 'package:surface/screens/account/publishers/publishers.dart'; import 'package:surface/screens/account/publishers/publishers.dart';
import 'package:surface/screens/account/tickets.dart'; import 'package:surface/screens/account/auth_tickets.dart';
import 'package:surface/screens/album.dart'; import 'package:surface/screens/album.dart';
import 'package:surface/screens/auth/login.dart'; import 'package:surface/screens/auth/login.dart';
import 'package:surface/screens/auth/register.dart'; import 'package:surface/screens/auth/register.dart';

View File

@ -5,6 +5,7 @@ import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/userinfo.dart';
import 'package:surface/types/auth.dart'; import 'package:surface/types/auth.dart';
import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart'; import 'package:surface/widgets/loading_indicator.dart';
@ -73,10 +74,18 @@ class _AccountAuthTicketState extends State<AccountAuthTicket> {
} }
} }
int? _currentTicketId;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_fetchAuthTickets(); _fetchAuthTickets();
final ua = context.read<UserProvider>();
ua.atkClaims.then((value) {
if (value == null) return;
_currentTicketId = int.parse(value['sed']);
});
} }
@override @override
@ -142,6 +151,11 @@ class _AccountAuthTicketState extends State<AccountAuthTicket> {
.format(ticket.lastGrantAt!.toLocal())) .format(ticket.lastGrantAt!.toLocal()))
])).fontSize(12).opacity(0.75), ])).fontSize(12).opacity(0.75),
const Gap(4), const Gap(4),
if (_currentTicketId == ticket.id)
Text('authTicketCurrent'.tr())
.fontSize(11)
.bold()
.opacity(0.75),
Text('#${ticket.id}').fontSize(11).opacity(0.75), Text('#${ticket.id}').fontSize(11).opacity(0.75),
], ],
), ),
@ -153,7 +167,9 @@ class _AccountAuthTicketState extends State<AccountAuthTicket> {
constraints: const BoxConstraints(), constraints: const BoxConstraints(),
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
icon: const Icon(Symbols.logout), icon: const Icon(Symbols.logout),
onPressed: () { onPressed: _currentTicketId == ticket.id
? null
: () {
_deleteAuthTicket(ticket); _deleteAuthTicket(ticket);
}, },
), ),