feat(base): SimpleLogin's sign-in comes back by the app's scheme or the loopback (ADR 0020, D9 corrected)
simpleLogin.begin takes the way back (SimpleLoginReturn: loopback, scheme) rather than the platform, so macOS can come back through its scheme as iOS and Android do; the markers become learn-loopback, learn-scheme, todo-loopback and todo-scheme. Not breaking: the endpoint has never been released. D9 corrected by the developer after research: Android, iOS and macOS come back through the app's scheme, Linux and Windows through the loopback. Verified links protected nothing here: a code caught on its way is worthless without the attempt's ID, and a hostile app beginning an attempt of its own can ask for a way back that other platforms need open. The ADR notes the residual risk and the check of prompt=consent still to do. assetlinks.json stays served. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
7eb1961b9f
commit
7ea9f16904
9 changed files with 201 additions and 182 deletions
|
|
@ -31,8 +31,8 @@ import 'package:halkyon_base_server/src/generated/password/password_setup.dart'
|
|||
as _ikhyee47;
|
||||
import 'package:halkyon_base_server/src/generated/password/wrapped_keys.dart'
|
||||
as _iji0wmu8;
|
||||
import 'package:halkyon_base_server/src/generated/simplelogin/simplelogin_platform.dart'
|
||||
as _ixrtiuh0;
|
||||
import 'package:halkyon_base_server/src/generated/simplelogin/simplelogin_return.dart'
|
||||
as _igmh0yku;
|
||||
import 'package:halkyon_oidc_server/halkyon_oidc_server.dart' as _ipoqfiqr;
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
import 'package:serverpod_auth_core_server/serverpod_auth_core_server.dart'
|
||||
|
|
@ -1283,9 +1283,9 @@ class Endpoints extends _is.EndpointDispatch {
|
|||
'begin': _is.MethodConnector(
|
||||
name: 'begin',
|
||||
params: {
|
||||
'platform': _is.ParameterDescription(
|
||||
name: 'platform',
|
||||
type: _is.getType<_ixrtiuh0.SimpleLoginPlatform>(),
|
||||
'back': _is.ParameterDescription(
|
||||
name: 'back',
|
||||
type: _is.getType<_igmh0yku.SimpleLoginReturn>(),
|
||||
nullable: false,
|
||||
),
|
||||
},
|
||||
|
|
@ -1297,7 +1297,7 @@ class Endpoints extends _is.EndpointDispatch {
|
|||
(endpoints['simpleLogin'] as _ia8c9alv.SimpleLoginEndpoint)
|
||||
.begin(
|
||||
session,
|
||||
platform: params['platform'],
|
||||
back: params['back'],
|
||||
),
|
||||
),
|
||||
'complete': _is.MethodConnector(
|
||||
|
|
|
|||
|
|
@ -69,8 +69,8 @@ import 'register/account_deletion_notice.dart' as _i2lyzpm9;
|
|||
import 'register/sessions_ended_notice.dart' as _io5c3tod;
|
||||
import 'simplelogin/simplelogin_completed.dart' as _i51ry5s2;
|
||||
import 'simplelogin/simplelogin_outcome.dart' as _i5ar1hzl;
|
||||
import 'simplelogin/simplelogin_platform.dart' as _ivny5pkt;
|
||||
import 'simplelogin/simplelogin_proof.dart' as _iauqtzog;
|
||||
import 'simplelogin/simplelogin_return.dart' as _i8dcjd27;
|
||||
export 'auth/email_code_purpose.dart';
|
||||
export 'auth/email_code_request.dart';
|
||||
export 'auth/email_identity.dart';
|
||||
|
|
@ -120,8 +120,8 @@ export 'register/account_deletion_notice.dart';
|
|||
export 'register/sessions_ended_notice.dart';
|
||||
export 'simplelogin/simplelogin_completed.dart';
|
||||
export 'simplelogin/simplelogin_outcome.dart';
|
||||
export 'simplelogin/simplelogin_platform.dart';
|
||||
export 'simplelogin/simplelogin_proof.dart';
|
||||
export 'simplelogin/simplelogin_return.dart';
|
||||
|
||||
class Protocol extends _is.DatabaseSerializationManager {
|
||||
Protocol._();
|
||||
|
|
@ -1219,12 +1219,12 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
if (t == _i5ar1hzl.SimpleLoginOutcome) {
|
||||
return _i5ar1hzl.SimpleLoginOutcome.fromJson(data) as T;
|
||||
}
|
||||
if (t == _ivny5pkt.SimpleLoginPlatform) {
|
||||
return _ivny5pkt.SimpleLoginPlatform.fromJson(data) as T;
|
||||
}
|
||||
if (t == _iauqtzog.SimpleLoginProof) {
|
||||
return _iauqtzog.SimpleLoginProof.fromJson(data) as T;
|
||||
}
|
||||
if (t == _i8dcjd27.SimpleLoginReturn) {
|
||||
return _i8dcjd27.SimpleLoginReturn.fromJson(data) as T;
|
||||
}
|
||||
if (t == _is.getType<_ifpka2on.EmailCodePurpose?>()) {
|
||||
return (data != null ? _ifpka2on.EmailCodePurpose.fromJson(data) : null)
|
||||
as T;
|
||||
|
|
@ -1435,16 +1435,14 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
return (data != null ? _i5ar1hzl.SimpleLoginOutcome.fromJson(data) : null)
|
||||
as T;
|
||||
}
|
||||
if (t == _is.getType<_ivny5pkt.SimpleLoginPlatform?>()) {
|
||||
return (data != null
|
||||
? _ivny5pkt.SimpleLoginPlatform.fromJson(data)
|
||||
: null)
|
||||
as T;
|
||||
}
|
||||
if (t == _is.getType<_iauqtzog.SimpleLoginProof?>()) {
|
||||
return (data != null ? _iauqtzog.SimpleLoginProof.fromJson(data) : null)
|
||||
as T;
|
||||
}
|
||||
if (t == _is.getType<_i8dcjd27.SimpleLoginReturn?>()) {
|
||||
return (data != null ? _i8dcjd27.SimpleLoginReturn.fromJson(data) : null)
|
||||
as T;
|
||||
}
|
||||
if (t == Set<String>) {
|
||||
return (data as List).map((e) => deserialize<String>(e)).toSet() as T;
|
||||
}
|
||||
|
|
@ -1532,8 +1530,8 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
_io5c3tod.SessionsEndedNotice => 'SessionsEndedNotice',
|
||||
_i51ry5s2.SimpleLoginCompleted => 'SimpleLoginCompleted',
|
||||
_i5ar1hzl.SimpleLoginOutcome => 'SimpleLoginOutcome',
|
||||
_ivny5pkt.SimpleLoginPlatform => 'SimpleLoginPlatform',
|
||||
_iauqtzog.SimpleLoginProof => 'SimpleLoginProof',
|
||||
_i8dcjd27.SimpleLoginReturn => 'SimpleLoginReturn',
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
|
@ -1649,10 +1647,10 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
return 'SimpleLoginCompleted';
|
||||
case _i5ar1hzl.SimpleLoginOutcome():
|
||||
return 'SimpleLoginOutcome';
|
||||
case _ivny5pkt.SimpleLoginPlatform():
|
||||
return 'SimpleLoginPlatform';
|
||||
case _iauqtzog.SimpleLoginProof():
|
||||
return 'SimpleLoginProof';
|
||||
case _i8dcjd27.SimpleLoginReturn():
|
||||
return 'SimpleLoginReturn';
|
||||
}
|
||||
className = _isp.Protocol().getClassNameForObject(data);
|
||||
if (className != null) {
|
||||
|
|
@ -1814,12 +1812,12 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
if (dataClassName == 'SimpleLoginOutcome') {
|
||||
return deserialize<_i5ar1hzl.SimpleLoginOutcome>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'SimpleLoginPlatform') {
|
||||
return deserialize<_ivny5pkt.SimpleLoginPlatform>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'SimpleLoginProof') {
|
||||
return deserialize<_iauqtzog.SimpleLoginProof>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'SimpleLoginReturn') {
|
||||
return deserialize<_i8dcjd27.SimpleLoginReturn>(data['data']);
|
||||
}
|
||||
if (dataClassName.startsWith('serverpod.')) {
|
||||
data['className'] = dataClassName.substring(10);
|
||||
return _isp.Protocol().deserializeByClassName(data);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
|
||||
/* To generate run: "serverpod generate" */
|
||||
|
||||
// ignore_for_file: implementation_imports
|
||||
// ignore_for_file: library_private_types_in_public_api
|
||||
// ignore_for_file: non_constant_identifier_names
|
||||
// ignore_for_file: public_member_api_docs
|
||||
// ignore_for_file: type_literal_in_constant_pattern
|
||||
// ignore_for_file: use_super_parameters
|
||||
// ignore_for_file: invalid_use_of_internal_member
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
|
||||
/// How an app comes back from a SimpleLogin sign-in (ADR 0020, 3 and D9),
|
||||
/// which says where the forwarding page sends it: the loopback (Linux,
|
||||
/// Windows) or the app's scheme (Android, iOS, macOS).
|
||||
enum SimpleLoginReturn implements _is.SerializableModel {
|
||||
loopback,
|
||||
scheme;
|
||||
|
||||
static SimpleLoginReturn fromJson(String name) {
|
||||
switch (name) {
|
||||
case 'loopback':
|
||||
return SimpleLoginReturn.loopback;
|
||||
case 'scheme':
|
||||
return SimpleLoginReturn.scheme;
|
||||
default:
|
||||
throw ArgumentError(
|
||||
'Value "$name" cannot be converted to "SimpleLoginReturn"',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toJson() => name;
|
||||
|
||||
@override
|
||||
String toString() => name;
|
||||
}
|
||||
|
|
@ -32,8 +32,8 @@ OidcIdp _simpleLoginOn(Session session) {
|
|||
return AuthServices.instance.oidcIdp;
|
||||
}
|
||||
|
||||
/// Begins a SimpleLogin sign-in for the app and platform [marker] names
|
||||
/// (`learn-desktop`, one of [simpleLoginReturns]), the state beginning with
|
||||
/// Begins a SimpleLogin sign-in for the app and way back [marker] names
|
||||
/// (`learn-loopback`, one of [simpleLoginReturns]), the state beginning with
|
||||
/// it for the forwarding page; [caller] began it signed in, to add
|
||||
/// SimpleLogin to their account, a member's person becoming a person here
|
||||
/// under their ID, as linking would make them (ADR 0016). At most 30 an hour
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import 'package:serverpod/serverpod.dart';
|
|||
// (`oidcCallbackForwarding`, ADR 0023, 7) with Halkyon's apps and look: the
|
||||
// browser comes back here with SimpleLogin's code (or its refusal) and the
|
||||
// state the sign-in began with, and the page sends both on to the app that
|
||||
// began it, the desktop's loopback or the phone's scheme, by the marker its
|
||||
// state begins with. It keeps and logs nothing: a code caught on its way is
|
||||
// began it, the loopback or the app's scheme, by the marker its state begins
|
||||
// with. It keeps and logs nothing: a code caught on its way is
|
||||
// worthless without the attempt the app holds (ADR 0020, 4).
|
||||
|
||||
/// Where SimpleLogin sends the browser back to, on the register's web
|
||||
|
|
@ -17,16 +17,14 @@ import 'package:serverpod/serverpod.dart';
|
|||
const simpleLoginCallbackPath = '/simplelogin-callback';
|
||||
|
||||
/// Where each Halkyon app takes a sign-in back, by the marker its state
|
||||
/// begins with (`<app>-<platform>`): the desktop's loopback, each app on a
|
||||
/// port of its own (RFC 8252, 7.3), and the phone's scheme, the app's ID. A
|
||||
/// phone with Halkyon Learn and learn.halkyon.eu never reaches the page:
|
||||
/// Android hands the page's address to the app itself (App Links,
|
||||
/// `assetlinks.json`).
|
||||
/// begins with (`<app>-<way back>`): the loopback on Linux and Windows, each
|
||||
/// app on a port of its own (RFC 8252, 7.3), and the app's scheme, its ID, on
|
||||
/// Android, iOS and macOS, where the system's browser sheet catches it.
|
||||
const simpleLoginReturns = {
|
||||
'learn-desktop': 'http://127.0.0.1:43823/simplelogin-callback',
|
||||
'learn-phone': 'app.halkyon.learn://simplelogin-callback',
|
||||
'todo-desktop': 'http://127.0.0.1:43824/simplelogin-callback',
|
||||
'todo-phone': 'app.halkyon.todo://simplelogin-callback',
|
||||
'learn-loopback': 'http://127.0.0.1:43823/simplelogin-callback',
|
||||
'learn-scheme': 'app.halkyon.learn://simplelogin-callback',
|
||||
'todo-loopback': 'http://127.0.0.1:43824/simplelogin-callback',
|
||||
'todo-scheme': 'app.halkyon.todo://simplelogin-callback',
|
||||
};
|
||||
|
||||
final _returns = {
|
||||
|
|
|
|||
|
|
@ -161,11 +161,11 @@ OidcIdpConfig? simpleLoginIdpConfig(Serverpod pod) {
|
|||
);
|
||||
}
|
||||
|
||||
/// The marker a sign-in's state begins with for [app] on [platform]
|
||||
/// (`learn-desktop`), by which the forwarding page sends it back
|
||||
/// The marker a sign-in's state begins with for [app] coming [back]
|
||||
/// (`learn-loopback`), by which the forwarding page sends it back
|
||||
/// ([simpleLoginReturns]).
|
||||
String simpleLoginMarker(String app, SimpleLoginPlatform platform) =>
|
||||
'$app-${platform.name}';
|
||||
String simpleLoginMarker(String app, SimpleLoginReturn back) =>
|
||||
'$app-${back.name}';
|
||||
|
||||
/// Puts [app] in place of [pod]'s SimpleLogin app, null meaning none
|
||||
/// whatever the passwords hold, asking SimpleLogin through [client]: tests
|
||||
|
|
|
|||
|
|
@ -15,18 +15,19 @@ import 'package:serverpod_auth_idp_server/core.dart';
|
|||
/// and checks SimpleLogin's codes (ADR 0016); a session is this server's.
|
||||
/// Refusals are `OidcRefusedException`s.
|
||||
class SimpleLoginEndpoint extends Endpoint {
|
||||
/// Begins a sign-in from this server's app on [platform]: the attempt,
|
||||
/// whose ID the device keeps to itself, and SimpleLogin's address to open
|
||||
/// in the browser, which comes back through the forwarding page with a
|
||||
/// code for [complete]. Refusals as `beginSimpleLogin`'s.
|
||||
/// Begins a sign-in from this server's app, coming [back] by the loopback
|
||||
/// or the app's scheme: the attempt, whose ID the device keeps to itself,
|
||||
/// and SimpleLogin's address to open in the browser, which comes back
|
||||
/// through the forwarding page with a code for [complete]. Refusals as
|
||||
/// `beginSimpleLogin`'s.
|
||||
Future<OidcStart> begin(
|
||||
Session session, {
|
||||
required SimpleLoginPlatform platform,
|
||||
required SimpleLoginReturn back,
|
||||
}) {
|
||||
final pod = session.serverpod;
|
||||
return AccountRegister.of(pod).beginSimpleLogin(
|
||||
session,
|
||||
marker: simpleLoginMarker(HalkyonBase.of(pod).app, platform),
|
||||
marker: simpleLoginMarker(HalkyonBase.of(pod).app, back),
|
||||
caller: session.authenticated?.authUserId,
|
||||
network: networkOf(session),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
### Where an app begins a SimpleLogin sign-in (ADR 0020, 3), which says where
|
||||
### the forwarding page sends it back: the desktop's loopback, or the phone's
|
||||
### scheme (or, with learn.halkyon.eu's App Links, the phone's app itself).
|
||||
enum: SimpleLoginPlatform
|
||||
serialized: byName
|
||||
values:
|
||||
- desktop
|
||||
- phone
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
### How an app comes back from a SimpleLogin sign-in (ADR 0020, 3 and D9),
|
||||
### which says where the forwarding page sends it: the loopback (Linux,
|
||||
### Windows) or the app's scheme (Android, iOS, macOS).
|
||||
enum: SimpleLoginReturn
|
||||
serialized: byName
|
||||
values:
|
||||
- loopback
|
||||
- scheme
|
||||
Loading…
Reference in a new issue