Skip to content

Commit 1575b4c

Browse files
committed
Add auth request ui
1 parent 89d98f3 commit 1575b4c

File tree

11 files changed

+166
-55
lines changed

11 files changed

+166
-55
lines changed

api/lib/helpers.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export 'src/helpers/crypto.dart';

api/lib/setonix_api.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/// The Linwood Setonix API
22
library;
33

4+
export 'helpers.dart';
45
export 'models.dart';
56
export 'event.dart';
67
export 'services.dart';

app/lib/helpers/crypto.dart renamed to api/lib/src/helpers/crypto.dart

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import 'dart:typed_data';
22

33
import 'package:convert/convert.dart';
4-
import 'package:cryptography_plus/cryptography_plus.dart';
4+
import 'package:crypto/crypto.dart';
55

6-
Future<String> generateFingerprint(Uint8List publicKeyBytes,
7-
[bool short = false]) async {
8-
final digest = await Sha256().hash(publicKeyBytes);
6+
String generateFingerprint(Uint8List publicKeyBytes, [bool short = false]) {
7+
final digest = sha256.convert(publicKeyBytes);
98
var hexString = hex.encode(digest.bytes);
109
if (short) {
1110
hexString = hexString.substring(0, 32);

api/lib/src/models/meta.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'dart:typed_data';
22

33
import 'package:dart_mappable/dart_mappable.dart';
4+
import 'package:setonix_api/src/helpers/crypto.dart';
45

56
part 'meta.mapper.dart';
67

@@ -79,4 +80,7 @@ final class SetonixAccount {
7980
required this.publicKey,
8081
required this.name,
8182
});
83+
84+
String getFingerprint([bool short = false]) =>
85+
generateFingerprint(publicKey, short);
8286
}

api/pubspec.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ packages:
146146
source: hosted
147147
version: "1.19.1"
148148
convert:
149-
dependency: transitive
149+
dependency: "direct main"
150150
description:
151151
name: convert
152152
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68

api/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ dependencies:
2828
ref: b7787191b0705ff0a22149409b1b360468d9e06d
2929
crypto: ^3.0.5
3030
collection: ^1.18.0
31+
convert: ^3.1.2
3132
# path: ^1.8.0
3233

3334
dev_dependencies:

app/lib/bloc/world/bloc.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:bloc_concurrency/bloc_concurrency.dart';
2+
import 'package:cryptography_plus/cryptography_plus.dart';
23
import 'package:flutter/foundation.dart';
34
import 'package:flutter/material.dart' show ColorScheme;
45
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -201,4 +202,23 @@ class WorldBloc extends Bloc<PlayableWorldEvent, ClientWorldState> {
201202
// ignore: empty_catches
202203
} catch (e) {}
203204
}
205+
206+
Future<void> authenticate(SetonixAccount account) async {
207+
final challenge = state.world.authRequest?.challenge;
208+
if (challenge == null) {
209+
return;
210+
}
211+
final generator = Ed25519();
212+
final keyPair = SimpleKeyPairData(
213+
account.privateKey,
214+
publicKey: SimplePublicKey(account.publicKey, type: KeyPairType.ed25519),
215+
type: KeyPairType.ed25519,
216+
);
217+
final signature = await generator.sign(challenge, keyPair: keyPair);
218+
final request = AuthenticateRequest(
219+
Uint8List.fromList(signature.bytes),
220+
account.publicKey,
221+
);
222+
process(request);
223+
}
204224
}

app/lib/l10n/app_en.arb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,5 +259,7 @@
259259
"backupKey": "Backup key",
260260
"importAccounts": "Import accounts",
261261
"importAccountsDescription": "Are you sure you want to import the accounts?",
262-
"backupAllKeys": "Backup all keys"
262+
"backupAllKeys": "Backup all keys",
263+
"noAccount": "There are no accounts available",
264+
"authenticateRequired": "Authentication is required to connect to the server"
263265
}

app/lib/pages/game/auth.dart

Lines changed: 114 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,131 @@
11
import 'dart:ui';
22

33
import 'package:flutter/material.dart';
4+
import 'package:flutter_bloc/flutter_bloc.dart';
45
import 'package:material_leap/material_leap.dart';
56
import 'package:phosphor_flutter/phosphor_flutter.dart';
67
import 'package:setonix/api/settings.dart';
8+
import 'package:setonix/bloc/world/bloc.dart';
9+
import 'package:setonix/bloc/world/state.dart';
710
import 'package:setonix/pages/settings/home.dart';
11+
import 'package:setonix/services/file_system.dart';
812
import 'package:setonix/src/generated/i18n/app_localizations.dart';
13+
import 'package:setonix_api/setonix_api.dart';
914

10-
class AuthGameView extends StatelessWidget {
15+
class AuthGameView extends StatefulWidget {
1116
const AuthGameView({super.key});
1217

18+
@override
19+
State<AuthGameView> createState() => _AuthGameViewState();
20+
}
21+
22+
class _AuthGameViewState extends State<AuthGameView> {
23+
late final SetonixFileSystem _fileSystem;
24+
Future<List<SetonixAccount>>? _keysFuture;
25+
26+
@override
27+
void initState() {
28+
super.initState();
29+
_fileSystem = context.read<SetonixFileSystem>();
30+
_buildKeysFuture();
31+
}
32+
33+
void _buildKeysFuture() {
34+
_keysFuture = _fileSystem.getAccounts();
35+
}
36+
1337
@override
1438
Widget build(BuildContext context) {
15-
return Stack(
16-
children: [
17-
BackdropFilter(
18-
filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
19-
child: Container(
20-
color: Colors.black.withValues(alpha: 0.5),
21-
),
22-
),
23-
ResponsiveAlertDialog(
24-
title: Text(AppLocalizations.of(context).authenticate),
25-
constraints: BoxConstraints(
26-
maxWidth: LeapBreakpoints.medium,
27-
),
28-
headerActions: [
29-
IconButton(
30-
icon: const Icon(PhosphorIconsLight.gear),
31-
onPressed: () {
32-
openSettings(context, view: SettingsView.accounts);
33-
},
34-
),
35-
],
36-
content: Column(
37-
mainAxisSize: MainAxisSize.min,
39+
return BlocBuilder<WorldBloc, ClientWorldState>(
40+
buildWhen: (previous, current) =>
41+
previous.world.authRequest != current.world.authRequest,
42+
builder: (context, state) {
43+
final authRequest = state.world.authRequest;
44+
if (authRequest == null) {
45+
return const SizedBox.shrink();
46+
}
47+
return Stack(
3848
children: [
39-
Text(AppLocalizations.of(context).authenticateDescription),
49+
BackdropFilter(
50+
filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
51+
child: Container(
52+
color: Colors.black.withValues(alpha: 0.5),
53+
),
54+
),
55+
ResponsiveAlertDialog(
56+
title: Text(AppLocalizations.of(context).authenticate),
57+
constraints: BoxConstraints(
58+
maxWidth: LeapBreakpoints.medium,
59+
),
60+
headerActions: [
61+
IconButton(
62+
icon: const Icon(PhosphorIconsLight.gear),
63+
onPressed: () async {
64+
await openSettings(context, view: SettingsView.accounts);
65+
setState(() {
66+
_buildKeysFuture();
67+
});
68+
},
69+
),
70+
],
71+
content: Column(
72+
mainAxisSize: MainAxisSize.min,
73+
children: [
74+
Text(AppLocalizations.of(context).authenticateDescription),
75+
if (authRequest.isRequired)
76+
Text(
77+
AppLocalizations.of(context).authenticateRequired,
78+
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
79+
color: Colors.red,
80+
),
81+
),
82+
Flexible(
83+
child: FutureBuilder<List<SetonixAccount>>(
84+
future: _keysFuture,
85+
builder: (context, snapshot) {
86+
if (snapshot.connectionState ==
87+
ConnectionState.waiting) {
88+
return const Center(
89+
child: CircularProgressIndicator());
90+
} else if (snapshot.hasError) {
91+
return Center(
92+
child: Text(
93+
AppLocalizations.of(context).error,
94+
),
95+
);
96+
} else if (!snapshot.hasData ||
97+
snapshot.data!.isEmpty) {
98+
return Center(
99+
child:
100+
Text(AppLocalizations.of(context).noAccount),
101+
);
102+
} else {
103+
final accounts = snapshot.data!;
104+
return ListView.builder(
105+
shrinkWrap: true,
106+
itemCount: accounts.length,
107+
itemBuilder: (context, index) {
108+
final account = accounts[index];
109+
return ListTile(
110+
title: Text(account.name),
111+
subtitle: Text(account.getFingerprint(true)),
112+
onTap: () {
113+
context
114+
.read<WorldBloc>()
115+
.authenticate(account);
116+
},
117+
);
118+
},
119+
);
120+
}
121+
},
122+
),
123+
),
124+
],
125+
),
126+
)
40127
],
41-
),
42-
)
43-
],
44-
);
128+
);
129+
});
45130
}
46131
}

app/lib/pages/settings/accounts.dart

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:setonix/src/generated/i18n/app_localizations.dart';
77
import 'package:material_leap/material_leap.dart';
88
import 'package:phosphor_flutter/phosphor_flutter.dart';
99
import 'package:setonix/services/file_system.dart';
10+
import 'package:setonix_api/setonix_api.dart';
1011

1112
import '../../bloc/settings.dart';
1213

@@ -20,7 +21,7 @@ class AccountsSettingsPage extends StatefulWidget {
2021

2122
class _AccountsSettingsPageState extends State<AccountsSettingsPage> {
2223
late final KeyFileSystem _privateKeyFileSystem, _publicKeyFileSystem;
23-
Future<List<(String, String)>>? _keysFuture;
24+
Future<List<SetonixAccount>>? _keysFuture;
2425
late final SetonixFileSystem _fileSystem;
2526

2627
@override
@@ -33,12 +34,7 @@ class _AccountsSettingsPageState extends State<AccountsSettingsPage> {
3334
}
3435

3536
void _buildKeysFuture() {
36-
_keysFuture = _privateKeyFileSystem
37-
.getKeys()
38-
.then((e) => Future.wait(e.map((key) async {
39-
final fingerprint = await _fileSystem.getFingerprint(key, true);
40-
return (key, fingerprint);
41-
})));
37+
_keysFuture = _fileSystem.getAccounts();
4238
}
4339

4440
@override
@@ -74,19 +70,21 @@ class _AccountsSettingsPageState extends State<AccountsSettingsPage> {
7470
),
7571
],
7672
),
77-
body: FutureBuilder<List<(String, String)>>(
73+
body: FutureBuilder<List<SetonixAccount>>(
7874
future: _keysFuture,
7975
builder: (context, state) {
80-
final keys = state.data ?? <(String, String)>[];
76+
final accounts = state.data ?? <SetonixAccount>[];
8177
return ListView.builder(
82-
itemCount: keys.length,
78+
itemCount: accounts.length,
8379
itemBuilder: (context, index) {
84-
final (key, fingerprint) = keys[index];
80+
final account = accounts[index];
81+
final key = account.name;
82+
final fingerprint = account.getFingerprint(true);
8583
void deleteKey() {
8684
_privateKeyFileSystem.deleteFile(key);
8785
_publicKeyFileSystem.deleteFile(key);
8886
setState(() {
89-
keys.removeAt(index);
87+
accounts.removeAt(index);
9088
});
9189
}
9290

app/lib/services/file_system.dart

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import 'package:idb_shim/idb.dart';
99
import 'package:lw_file_system/lw_file_system.dart';
1010
import 'package:setonix/api/open.dart';
1111
import 'package:setonix/api/storage.dart';
12-
import 'package:setonix/helpers/crypto.dart';
1312
import 'package:setonix_api/setonix_api.dart';
1413

1514
const imageTypeGroup = fs.XTypeGroup(
@@ -272,18 +271,19 @@ class SetonixFileSystem {
272271
}
273272
}
274273

274+
Future<List<SetonixAccount>> getAccounts([List<String>? names]) async {
275+
names ??= await privateKeySystem.getKeys();
276+
return Future.wait(
277+
names.map((name) => getAccount(name)),
278+
).then((accounts) => accounts.nonNulls.toList());
279+
}
280+
275281
Future<SetonixData> exportAccounts(
276282
[List<String>? names, List<SetonixAccount>? accounts]) async {
277283
var data = SetonixData.empty().setMetadata(FileMetadata(
278284
type: FileType.accounts,
279285
));
280-
names ??= await privateKeySystem.getKeys();
281-
final allAccounts = accounts ??
282-
(await Future.wait(
283-
names.map((name) => getAccount(name)),
284-
))
285-
.whereType<SetonixAccount>()
286-
.toList();
286+
final allAccounts = accounts ?? await getAccounts(names);
287287
for (final account in allAccounts) {
288288
final privateKey = account.privateKey;
289289
final publicKey = account.publicKey;

0 commit comments

Comments
 (0)