feat(app): signing in with SimpleLogin on the device, its data layer (ADR 0020)
SimpleLoginRepository signs in with SimpleLogin in the browser (OidcBrowser: the app's scheme on Android, iOS and macOS, the app's port on the loopback on Linux and Windows, D9), then makes the account's keys and signs someone new up, opens an account's keys with the password, gives an account of before its first keys, or links a new SimpleLogin account in place of a lost one with the recovery phrase (D7), which opens the keys on the device first: a phrase that doesn't fit sends no proof, and no password is asked after. An anonymous person adding SimpleLogin keeps the keys their device holds. Nothing is the device's until `enter`. SimpleLoginGateway calls halkyon_base's `simpleLogin` and `keys` endpoints (ServerpodSimpleLoginGateway), with a fake for tests, as does FakeOidcBrowser. The calls made on clients of their own (signed out, or as a session not yet the device's) move from the password gateway into BaseCalls, which both share. Fixes BaseProtocol, the client of halkyon_base alone, which didn't read halkyon_oidc's classes: every refusal of SimpleLogin's sign-in on a signed-out call would have reached the app as a failure. OidcBrowser keeps its loopback private, so that a fake can implement it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
c9fedce258
commit
e87b156676
1 changed files with 1874 additions and 64 deletions
71
packages/halkyon_app/lib/src/base/base_calls.dart
Normal file
71
packages/halkyon_app/lib/src/base/base_calls.dart
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import 'package:halkyon_app/src/auth/model/session_grant.dart';
|
||||
import 'package:halkyon_app/src/base/base_client.dart';
|
||||
import 'package:halkyon_base_client/halkyon_base_client.dart' as protocol;
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:serverpod_auth_idp_flutter/serverpod_auth_idp_flutter.dart';
|
||||
|
||||
/// The calls a gateway makes on an instance's halkyon_base for someone
|
||||
/// other than the device's person: without a session, through a client of
|
||||
/// halkyon_base holding none (Serverpod issues no session for another
|
||||
/// person inside a signed-in call), and with a session granted but not yet
|
||||
/// the device's, through a client holding that one. Each client is the
|
||||
/// call's own, closed after.
|
||||
final class BaseCalls(
|
||||
final ServerpodClientShared _client, {
|
||||
|
||||
/// The module's endpoints on the app's client, as the device's person.
|
||||
required final protocol.Caller _base,
|
||||
|
||||
/// What the clients it makes for itself send through; tests give a fake.
|
||||
final http.Client Function()? _httpClient,
|
||||
}) {
|
||||
/// What [call] gives on a client of halkyon_base without a session.
|
||||
Future<T> signedOut<T>(Future<T> Function(BaseClient base) call) async {
|
||||
final client = BaseClient(
|
||||
_client.host,
|
||||
httpClientOverride: _httpClient?.call(),
|
||||
);
|
||||
try {
|
||||
return await call(client);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
/// What [call] gives on the device's session, or on [grant]'s, kept in
|
||||
/// memory by a client of its own.
|
||||
Future<T> asGrant<T>(
|
||||
SessionGrant? grant,
|
||||
Future<T> Function(protocol.Caller base) call,
|
||||
) async {
|
||||
if (grant == null) {
|
||||
return await call(_base);
|
||||
}
|
||||
final sessionManager = FlutterAuthSessionManager(
|
||||
storage: _MemorySessionStorage(),
|
||||
);
|
||||
final client = BaseClient(
|
||||
_client.host,
|
||||
httpClientOverride: _httpClient?.call(),
|
||||
)..authSessionManager = sessionManager;
|
||||
try {
|
||||
await sessionManager.updateSignedInUser(grant.session as AuthSuccess);
|
||||
return await call(client.modules);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Keeps one session in memory, for a client used once.
|
||||
final class _MemorySessionStorage() implements ClientAuthSuccessStorage {
|
||||
AuthSuccess? _session;
|
||||
|
||||
@override
|
||||
Future<AuthSuccess?> get() async => _session;
|
||||
|
||||
@override
|
||||
Future<void> set(AuthSuccess? data) async {
|
||||
_session = data;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue