fix(auth)!: a first password takes a fresh proof of the account's way in
The fourth reading's first finding: password.set took a session alone, so whoever took one made a GitHub-only account, or an address account without a password, theirs for good (their address or password, their authenticator; GitHub then refused the owner). - An account with an address proves it anew: a code mailed to that address for this (EmailCodePurpose.firstPassword, its own mail), never the session alone, a code asked for anything else, or another address. - An account that signs in with GitHub alone does it within 10 minutes of a GitHub sign-in (the session's method and creation time, where the session lives; members tell the register), refused signInAgain before any code is used; the address it adds is proved as before. - Anonymous people still become identified with a code to the address they add. The app mails the code to the person's own address (asking for it where the device doesn't know it), offers a password right after a GitHub sign-in (D2's "at their next sign-in") before the person's app, and asks for a GitHub sign-in anew when the instance refuses an older one, keeping the code typed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
1ada2e27f6
commit
f0413531ff
1 changed files with 762 additions and 66 deletions
|
|
@ -19,6 +19,9 @@ class SessionBloc(final AuthRepository _auth, final SignedOutMark _mark)
|
|||
on<SessionEventSignInRequested>(_onSignInRequested);
|
||||
on<SessionEventStartedAnonymously>(_onStartedAnonymously);
|
||||
on<SessionEventGitHubRequested>(_onGitHubRequested);
|
||||
on<SessionEventPasswordOfferDone>(
|
||||
(event, emit) => emit(_stateOf(_auth.status)),
|
||||
);
|
||||
on<SessionEventSignedOut>(
|
||||
(event, emit) => emit(SessionStateSignedOut(problem: event.problem)),
|
||||
);
|
||||
|
|
@ -84,12 +87,19 @@ class SessionBloc(final AuthRepository _auth, final SignedOutMark _mark)
|
|||
Emitter<SessionState> emit,
|
||||
) async {
|
||||
emit(const SessionStateSignedOut(busy: true));
|
||||
// The person signed in with GitHub is offered a password first (ADR
|
||||
// 0017, D2: "at their next sign-in"), while that sign-in is recent
|
||||
// enough for the instance to take one (the fourth reading).
|
||||
_offerPassword = true;
|
||||
try {
|
||||
if (!await _auth.switchToGitHub()) {
|
||||
_offerPassword = false;
|
||||
emit(const SessionStateSignedOut());
|
||||
}
|
||||
// Signed in: the status change shows the person's app.
|
||||
// Signed in: the status change shows the offer, then the person's
|
||||
// app.
|
||||
} on GitHubSignInFailed catch (e) {
|
||||
_offerPassword = false;
|
||||
emit(
|
||||
SessionStateSignedOut(
|
||||
problem: e.problem == GitHubSignInProblem.unknownAccount
|
||||
|
|
@ -98,11 +108,16 @@ class SessionBloc(final AuthRepository _auth, final SignedOutMark _mark)
|
|||
),
|
||||
);
|
||||
} on Object catch (error, st) {
|
||||
_offerPassword = false;
|
||||
addError(error, st);
|
||||
emit(const SessionStateSignedOut(problem: SignedOutProblem.failed));
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the next person signed in comes from a GitHub sign-in, and is
|
||||
/// offered a password before their app ([SessionStateGitHubSignedIn]).
|
||||
bool _offerPassword = false;
|
||||
|
||||
Future<void> _onSignOutRequested(
|
||||
SessionEventSignOutRequested event,
|
||||
Emitter<SessionState> emit,
|
||||
|
|
@ -132,9 +147,15 @@ class SessionBloc(final AuthRepository _auth, final SignedOutMark _mark)
|
|||
}
|
||||
|
||||
SessionState _stateOf(AuthStatus status) => switch (status) {
|
||||
SignedIn() when _offerPassword => _offered(status),
|
||||
SignedIn() => SessionStateActive(status),
|
||||
SignedOut() => const SessionStateSignedOut(),
|
||||
};
|
||||
|
||||
SessionState _offered(SignedIn person) {
|
||||
_offerPassword = false;
|
||||
return SessionStateGitHubSignedIn(person);
|
||||
}
|
||||
}
|
||||
|
||||
/// What happens to the session.
|
||||
|
|
@ -163,6 +184,10 @@ final class const SessionEventSignedOut({final SignedOutProblem? problem})
|
|||
List<Object?> get props => [problem];
|
||||
}
|
||||
|
||||
/// The password offered after a GitHub sign-in was set up, put off, or not
|
||||
/// needed: on to the person's app.
|
||||
final class const SessionEventPasswordOfferDone() extends SessionEvent;
|
||||
|
||||
/// The person confirmed that they want to sign out.
|
||||
final class const SessionEventSignOutRequested() extends SessionEvent;
|
||||
|
||||
|
|
@ -195,6 +220,17 @@ final class const SessionStateActive(
|
|||
List<Object?> get props => [person];
|
||||
}
|
||||
|
||||
/// [person] signed in with GitHub just now: before their app, a password
|
||||
/// is offered where the instance offers one and they have none (ADR 0017,
|
||||
/// D2), while the sign-in is recent enough to set it.
|
||||
final class const SessionStateGitHubSignedIn(
|
||||
/// Who signed in.
|
||||
final SignedIn person,
|
||||
) extends SessionState {
|
||||
@override
|
||||
List<Object?> get props => [person];
|
||||
}
|
||||
|
||||
/// Nobody is signed in since signing out: the app asks how to start.
|
||||
final class const SessionStateSignedOut({
|
||||
/// Whether a way to start is under way.
|
||||
|
|
|
|||
Loading…
Reference in a new issue