diff --git a/.vscode/settings.json b/.vscode/settings.json index 068916b..2ff533e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,6 +10,7 @@ "msgtype", "muks", "prefs", - "unban" + "unban", + "unredact" ] } diff --git a/gomuks b/gomuks index 23638a8..d750602 160000 --- a/gomuks +++ b/gomuks @@ -1 +1 @@ -Subproject commit 23638a8d2b5ad7ed9f72a0ec39f56cac119c45fb +Subproject commit d7506029a62299515356f72061d36ae35e9c9590 diff --git a/lib/controllers/client.dart b/lib/controllers/client.dart index befaf21..6f17caa 100644 --- a/lib/controllers/client.dart +++ b/lib/controllers/client.dart @@ -20,8 +20,8 @@ import "package:nexus/models/requests/get_event.dart"; import "package:nexus/models/requests/get_related_events.dart"; import "package:nexus/models/requests/get_room_state.dart"; import "package:nexus/models/requests/join_room.dart"; -import "package:nexus/models/requests/login.dart"; import "package:nexus/models/profile.dart"; +import "package:nexus/models/requests/oauth/register_client.dart"; import "package:nexus/models/requests/paginate.dart"; import "package:nexus/models/requests/redact_event.dart"; import "package:nexus/models/requests/report.dart"; @@ -275,14 +275,11 @@ class ClientController extends AsyncNotifier { }); } - Future login(LoginRequest login) async { - try { - await _sendCommand("login", login.toJson()); - return null; - } catch (error) { - return error.toString(); - } - } + Future registerClient(OAuthRegisterClientRequest request) async => + (await _sendCommand( + "oauth_register_client", + request.toJson(), + ))["client_id"]; Future discoverHomeserver(Uri homeserver) async { try { diff --git a/lib/controllers/client_id_controller.dart b/lib/controllers/client_id_controller.dart new file mode 100644 index 0000000..252de52 --- /dev/null +++ b/lib/controllers/client_id_controller.dart @@ -0,0 +1,33 @@ +import "package:flutter_riverpod/flutter_riverpod.dart"; +import "package:nexus/controllers/client.dart"; +import "package:nexus/controllers/shared_prefs.dart"; + +class ClientIdController extends AsyncNotifier { + static const prefString = "client_id"; + + @override + Future build() async { + final prefs = await ref.watch(SharedPrefsController.provider.future); + final fromPref = prefs.getString(prefString); + + if (fromPref != null) return fromPref; + + final fetched = await ref + .watch(ClientController.provider.notifier) + .registerClient( + .new( + homeserverUrl: Uri.https("matrix.federated.nexus"), + clientUri: Uri.https("nexus.federated.nexus"), + ), + ); + + await prefs.setString(prefString, fetched); + + return fetched; + } + + static final provider = + AsyncNotifierProvider.autoDispose( + ClientIdController.new, + ); +} diff --git a/lib/models/requests/get_event.dart b/lib/models/requests/get_event.dart index 0e8f0a5..2665a2a 100644 --- a/lib/models/requests/get_event.dart +++ b/lib/models/requests/get_event.dart @@ -2,9 +2,8 @@ import "package:freezed_annotation/freezed_annotation.dart"; part "get_event.freezed.dart"; part "get_event.g.dart"; -@Freezed() +@freezed abstract class GetEventRequest with _$GetEventRequest { - const GetEventRequest._(); const factory GetEventRequest({ required String roomId, required String eventId, diff --git a/lib/models/requests/login.dart b/lib/models/requests/login.dart deleted file mode 100644 index 37ca819..0000000 --- a/lib/models/requests/login.dart +++ /dev/null @@ -1,15 +0,0 @@ -import "package:freezed_annotation/freezed_annotation.dart"; -part "login.freezed.dart"; -part "login.g.dart"; - -@freezed -abstract class LoginRequest with _$LoginRequest { - const factory LoginRequest({ - required String username, - required String password, - required String homeserverUrl, - }) = _LoginRequest; - - factory LoginRequest.fromJson(Map json) => - _$LoginRequestFromJson(json); -} diff --git a/lib/models/requests/oauth/get_auth_url.dart b/lib/models/requests/oauth/get_auth_url.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/models/requests/oauth/register_client.dart b/lib/models/requests/oauth/register_client.dart new file mode 100644 index 0000000..74d9562 --- /dev/null +++ b/lib/models/requests/oauth/register_client.dart @@ -0,0 +1,47 @@ +import "package:fast_immutable_collections/fast_immutable_collections.dart"; +import "package:freezed_annotation/freezed_annotation.dart"; +part "register_client.freezed.dart"; +part "register_client.g.dart"; + +@freezed +abstract class OAuthRegisterClientRequest with _$OAuthRegisterClientRequest { + const factory OAuthRegisterClientRequest({ + required Uri homeserverUrl, + ApplicationType? applicationType, + String? clientName, + required Uri clientUri, + Uri? logoUri, + Uri? policyUri, + Uri? tosUri, + IList? grantTypes, + IList? redirectUris, + IList? responseTypes, + AuthMethod? authMethod, + }) = _OAuthRegisterClientRequest; + + factory OAuthRegisterClientRequest.fromJson(Map json) => + _$OAuthRegisterClientRequestFromJson(json); +} + +enum ApplicationType { native, web } + +@JsonEnum(fieldRename: .snake) +enum ResponseType { code, idToken } + +@JsonEnum(fieldRename: .snake) +enum AuthMethod { + clientSecretPost, + clientSecretBasic, + clientSecretJwt, + privateKeyJwt, + none, +} + +@JsonEnum(fieldRename: .snake) +enum GrantType { + authorizationCode, + refreshToken, + clientCredentials, + @JsonValue("urn:ietf:params:oauth:grant-type:device_code") + deviceCode, +} diff --git a/lib/pages/login.dart b/lib/pages/login.dart index 5e1e8e8..0d52afd 100644 --- a/lib/pages/login.dart +++ b/lib/pages/login.dart @@ -2,6 +2,8 @@ import "package:flutter/material.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/client.dart"; +import "package:nexus/controllers/client_id_controller.dart"; +import "package:nexus/helpers/extensions/better_when.dart"; import "package:nexus/widgets/appbar.dart"; import "package:nexus/helpers/required_validator_helper.dart"; @@ -10,90 +12,9 @@ class LoginPage extends HookConsumerWidget { const LoginPage({super.key, required this.homeserver}); @override - Widget build(BuildContext context, WidgetRef ref) { - final client = ref.watch(ClientController.provider.notifier); - - final isLoading = useState(false); - final username = useTextEditingController(); - final password = useTextEditingController(); - - final inputError = useState(null); - final formKey = useRef(GlobalKey()); - - Future tryLogin() async { - isLoading.value = true; - - try { - if (formKey.value.currentState?.validate() != true) return; - - final error = await client.login( - .new( - username: username.text, - password: password.text, - homeserverUrl: homeserver.origin, - ), - ); - - if (error != null) { - inputError.value = error; - isLoading.value = false; - } else { - if (context.mounted) Navigator.of(context).pop(); - } - } finally { - isLoading.value = false; - } - } - - return Scaffold( - appBar: Appbar( - leading: IconButton( - icon: Icon(Icons.arrow_back), - onPressed: Navigator.of(context).pop, - ), - ), - body: AlertDialog( - title: Text("Login to ${homeserver.host}"), - content: Form( - key: formKey.value, - child: Column( - mainAxisSize: .min, - crossAxisAlignment: .start, - children: [ - TextFormField( - autofocus: true, - textInputAction: .next, - autovalidateMode: .onUserInteraction, - validator: requiredValidator, - decoration: .new(label: Text("Username")), - controller: username, - ), - SizedBox(height: 12), - TextFormField( - textInputAction: .done, - decoration: .new( - label: Text("Password"), - errorText: inputError.value, - errorMaxLines: 5, - ), - autovalidateMode: .onUserInteraction, - validator: requiredValidator, - controller: password, - obscureText: true, - onFieldSubmitted: (_) => tryLogin(), - // Don't defocus on submit - onEditingComplete: () {}, - ), - ], - ), - ), - actions: [ - TextButton( - onPressed: isLoading.value ? null : tryLogin, - child: Text("Sign In"), - ), - ], - ), - ); - } + Widget build(BuildContext context, WidgetRef ref) => Scaffold( + body: ref + .watch(ClientIdController.provider) + .betterWhen(data: (value) => Text(value)), + ); }