feat(base,app)!: GitHub signs nobody in any more (ADR 0020, D36)
Every account signs in with SimpleLogin or a password now, so GitHub goes from the module, the shared app package, Learn and Todo, ahead of ADR 0017's GITHUB_SIGN_IN_ENDS. - halkyon_base: no `gitHubSignIn` endpoint, GitHub identity provider or GitHub refusals; no `githubClientId`/`githubClientSecret`, no `GITHUB_SIGN_IN_ENDS` and no `forgetGitHubAccounts`; the register loses `account-with-github`. `SignInWays.gitHub`, the basics' `gitHubClientId` and `gitHubSignInEnds`, and `PasswordRefusal`'s `signInAgain` and `noAddress` go. A first password takes a code mailed to the person's own address; an account without one sets none (`wrongCredentials`). - Learn's server: `instance.info()` without `gitHubClientId`, the pruning without GitHub, and `minimumAppBuild` 24, as builds up to 23 (v0.20.0) require `SignInWays.gitHub`: the server deploys with the release of build 24. - halkyon_app: no GitHub button or problems on the start screen, no password offered after a GitHub sign-in, and `ServerpodAuthGateway` without the browser half only GitHub used (`(client, base:)`). The account section offers a first password only to an account with an address. `LoopbackWebAuth` and the apps' schemes stay, SimpleLogin's ways back. - Learn's Profile lists no GitHub; the texts go (English, German). - Serverpod's `serverpod_auth_idp_github_account` stays, as its migrations make it; a row goes with its account, and ADR 0020's D36 gives operators the SQL to delete those left. - deploy/: compose.yaml and .env.example without GitHub. The READMEs, AGENTS.md files and ADR 0017 amended. BREAKING CHANGE: builds up to 23 can't read the instance's answers about an account's ways in; minimumAppBuild is 24. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
ab5b7cd015
commit
4e33133733
40 changed files with 564 additions and 3570 deletions
|
|
@ -39,8 +39,7 @@ Written as the apps are: Dart 3.13's primary constructors, Material from
|
|||
the instance in use (`AppDependencies`, handed down by `AppScope`), anew on
|
||||
a switch, the previous ones closed first; `SessionGate` shows the app of
|
||||
whoever is signed in and asks how to start while nobody is
|
||||
(`StartScreen`: SimpleLogin, a password, GitHub for those who have it,
|
||||
another instance; an instance out of reach is said so, with Try again,
|
||||
(`StartScreen`: SimpleLogin, a password, another instance; an instance out of reach is said so, with Try again,
|
||||
and one offering no way to create an account too), at a first start as
|
||||
after signing out; an anonymous account's session, left from before
|
||||
there were none, is deleted as the app starts (ADR 0020, D25), falling
|
||||
|
|
@ -62,15 +61,13 @@ Written as the apps are: Dart 3.13's primary constructors, Material from
|
|||
it `hasSnackBars`). An instance out of reach signs out on the device all
|
||||
the same (Serverpod's `signOutDevice`), so only the device's own storage
|
||||
fails a sign-out.
|
||||
- **Signing in** (`lib/src/auth/`): `AuthRepository` on an `AuthGateway`;
|
||||
`ServerpodAuthGateway` signs in with GitHub those who have it, as it is
|
||||
added to nobody any more (ADR 0020, D25; the system browser and a loopback
|
||||
address on Linux and Windows, halkyon_oidc_flutter's `LoopbackWebAuth`),
|
||||
through the instance's `halkyon_base`, and puts a session granted elsewhere
|
||||
in place (`useSession`); `SessionBloc` and `SignInWaysCubit`;
|
||||
`SignInAddresses`, `PersonFolders`. The start screen says why a GitHub
|
||||
sign-in was refused, and for a GitHub account nobody here has, the way to
|
||||
create an account in its place.
|
||||
- **The session** (`lib/src/auth/`): `AuthRepository` on an `AuthGateway`;
|
||||
`ServerpodAuthGateway` follows the session the app's Serverpod client
|
||||
keeps, signs out, deletes the account through the instance's
|
||||
`halkyon_base`, and puts a session granted elsewhere in place
|
||||
(`useSession`: a password's or SimpleLogin's sign-in; GitHub signs nobody
|
||||
in since ADR 0020, D36); `SessionBloc` and `SignInWaysCubit`;
|
||||
`SignInAddresses`, `PersonFolders`.
|
||||
- **Content sealed with data keys** (`lib/src/privacy/`, ADR 0019):
|
||||
`ContentCipher` (`SodiumContentCipher`, on the isolate it is used on, as an
|
||||
app seals and opens rows one by one; `FakeContentCipher` in tests) makes a
|
||||
|
|
@ -199,10 +196,8 @@ Written as the apps are: Dart 3.13's primary constructors, Material from
|
|||
`GlobalMaterialLocalizations.delegates`.
|
||||
4. **The frame** (`AppFrame`): the app's name and wordmark word, its icon, an
|
||||
example address, its theme, delegates, languages (`resolveAppLocale` as
|
||||
its resolution) and launch screen, and the three sentences only it can
|
||||
say: what the first start's instance keeps of the person's, what a switch
|
||||
to another account leaves behind, and where GitHub is added later (shown
|
||||
only where the instance offers no passwords).
|
||||
its resolution) and launch screen, and the sentence only it can say: what
|
||||
the first start's instance keeps of the person's.
|
||||
5. **Android**: the app's `MainActivity` answers `app.halkyon/screen_guard`
|
||||
(copy Learn's or Todo's `ScreenGuard.kt` and call `ScreenGuard.attach` in
|
||||
`configureFlutterEngine`), or secrets show in screenshots and recents.
|
||||
|
|
@ -214,10 +209,7 @@ Written as the apps are: Dart 3.13's primary constructors, Material from
|
|||
`SodiumAccountCrypto()`, `SecureAccountKeyStore(storage.keysOf(instance))`,
|
||||
the auth gateway and the app's `SignInAddresses`. Its `auth` is an
|
||||
`AuthRepository` on `ServerpodAuthGateway(client, base:
|
||||
client.modules.base, callbackScheme: …, appName: …)`; the scheme is the
|
||||
app's own (`app.halkyon.learn`), registered with each platform and in the
|
||||
instance's GitHub OAuth app, whose loopback address on Linux and Windows is
|
||||
`http://127.0.0.1/github-callback` (port 43823 unless given).
|
||||
client.modules.base)`.
|
||||
7. **The tree**:
|
||||
|
||||
```dart
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@
|
|||
/// - **Signing in**: with SimpleLogin (ADR 0020: `SimpleLoginRepository`,
|
||||
/// its sheet and `SimpleLoginSection`), with a password and TOTP (ADR
|
||||
/// 0017: `PasswordRepository`, its sheets and `AccountSecuritySection`),
|
||||
/// and with GitHub while it lasts (`AuthRepository` on
|
||||
/// `ServerpodAuthGateway`), the account's keys on the device. Every
|
||||
/// account has a way in: there are no anonymous ones (ADR 0020, D4).
|
||||
/// the session on `AuthRepository` and `ServerpodAuthGateway`, the
|
||||
/// account's keys on the device. Every account has a way in: there are no
|
||||
/// anonymous ones (ADR 0020, D4).
|
||||
/// - **Sync**: `SyncService` on triggers, the changes still waiting, and
|
||||
/// where it stands (`SyncStatusBloc`, `SyncStatusCard`, `AppMismatchBanner`,
|
||||
/// `SyncTroubleBadge`).
|
||||
|
|
@ -39,7 +39,6 @@ export 'src/auth/data/auth_repository.dart';
|
|||
export 'src/auth/data/serverpod_auth_gateway.dart';
|
||||
export 'src/auth/data/sign_in_addresses.dart';
|
||||
export 'src/auth/model/auth_status.dart';
|
||||
export 'src/auth/model/github_sign_in.dart';
|
||||
export 'src/auth/model/session_grant.dart';
|
||||
export 'src/auth/model/sign_in_ways.dart';
|
||||
export 'src/auth/widget/account_tiles.dart';
|
||||
|
|
|
|||
|
|
@ -44,13 +44,6 @@
|
|||
"personDataFailed": "Die Daten dieses Geräts konnten nicht geöffnet werden.",
|
||||
"startSignedOut": "Abgemeldet",
|
||||
"startOn": "Starte auf {instance}:",
|
||||
"startWithGitHub": "Mit GitHub anmelden",
|
||||
"startGitHubFailed": "Anmeldung mit GitHub fehlgeschlagen.",
|
||||
"startNoGitHubAccount": "Kein Konto hier meldet sich mit diesem GitHub-Konto an.",
|
||||
"gitHubCreateAccountInstead": "Erstelle stattdessen ein Konto mit deiner E-Mail-Adresse und einem Passwort.",
|
||||
"gitHubSimpleLoginInstead": "Mit SimpleLogin kannst du stattdessen eins erstellen.",
|
||||
"gitHubUsePassword": "Dieses Konto meldet sich jetzt mit seiner E-Mail-Adresse und seinem Passwort an, nicht mehr mit GitHub.",
|
||||
"gitHubEnded": "Die Anmeldung mit GitHub ist auf dieser Instanz beendet.",
|
||||
"startOtherInstance": "Andere Instanz verwenden",
|
||||
"startNoWay": "{instance} bietet keinen Weg, ein Konto zu erstellen.",
|
||||
"syncTitle": "Synchronisierung",
|
||||
|
|
@ -70,7 +63,6 @@
|
|||
"syncMismatchMore": "Mehr",
|
||||
"startSignIn": "Anmelden",
|
||||
"startCreateAccount": "Konto erstellen",
|
||||
"startGitHubEnds": "Die Anmeldung mit GitHub endet am {date}.",
|
||||
"passwordAddress": "E-Mail-Adresse",
|
||||
"passwordPassword": "Passwort",
|
||||
"passwordNewPassword": "Neues Passwort",
|
||||
|
|
@ -158,7 +150,6 @@
|
|||
"passwordResetStarted": "Dein Konto kann ab dem {date} zurückgesetzt werden. Wir haben dir eine E-Mail geschickt: Warst du es nicht, melde dich vorher mit deinem Passwort an, um es zu stoppen.",
|
||||
"passwordSectionTitle": "Passwort und Authenticator",
|
||||
"passwordSectionFailed": "Wie dein Konto sich anmeldet, konnte nicht gelesen werden.",
|
||||
"passwordSectionGitHubEnds": "Die Anmeldung mit GitHub endet am {date}. Richte bis dahin ein Passwort ein, um dein Konto zu behalten.",
|
||||
"passwordSectionSetUp": "Passwort einrichten",
|
||||
"passwordSectionSetUpHint": "Melde dich mit deiner E-Mail-Adresse, einem Passwort und einer Authenticator-App an.",
|
||||
"passwordSectionUnfinished": "Fertig einrichten: Authenticator hinzufügen",
|
||||
|
|
@ -189,7 +180,6 @@
|
|||
"passwordNoReset": "Für diese Adresse wartet kein Zurücksetzen.",
|
||||
"passwordExists": "Das Konto dieser Adresse hat schon ein Passwort. Melde dich damit an oder komm zurück in dein Konto, falls du es vergessen hast.",
|
||||
"passwordAddressTaken": "Diese Adresse gehört zu einem anderen Konto.",
|
||||
"passwordNoAddress": "Dein Konto hat noch keine E-Mail-Adresse.",
|
||||
"passwordNoPassword": "Dieses Konto hat kein Passwort.",
|
||||
"passwordWrongCode": "Dieser Code stimmt nicht. Gib den ein, den deine App gerade zeigt.",
|
||||
"passwordLastAuthenticator": "Dein letzter Authenticator kann nicht weg: Jede Anmeldung braucht einen.",
|
||||
|
|
@ -220,11 +210,6 @@
|
|||
"passwordAddressUnknown": "Gib die E-Mail-Adresse deines Kontos ein: Dorthin geht der Code.",
|
||||
"passwordDeleteIntroByMail": "Dein Konto wird mit allem darin gelöscht, in jedem Dienst dieser Instanz. Keiner seiner Authenticators funktioniert gerade, daher bestätigt es ein Code per E-Mail: Gib dein Passwort ein, dann den Code, den wir an deine Adresse schicken.",
|
||||
"passwordDeleteSendCode": "Code per E-Mail schicken",
|
||||
"passwordSignInAgain": "Um einem Konto, das sich mit GitHub anmeldet, ein Passwort zu geben, melde dich erst wieder mit GitHub an: Es ist mehr als ein paar Minuten her.",
|
||||
"passwordSignInWithGitHubAgain": "Wieder mit GitHub anmelden",
|
||||
"passwordOfferTitle": "Richte ein Passwort ein",
|
||||
"passwordOfferBody": "Die Anmeldung mit GitHub endet. Richte ein Passwort und eine Authenticator-App ein, um dein Konto zu behalten.",
|
||||
"passwordOfferLater": "Später",
|
||||
"passwordAuthenticatorPasswordIntro": "Bestätige zum Hinzufügen mit deinem Passwort, dass du es bist.",
|
||||
"passwordWarningTitle": "Jemand hat dein Passwort ausprobiert",
|
||||
"passwordWarningBody": "Am {date} hat ein bei deinem Konto angemeldetes Gerät zehnmal hintereinander ein falsches Passwort oder einen falschen Code gesendet und wurde abgemeldet. Warst du das nicht, hat vielleicht jemand anderes dieses Gerät oder seine Sitzung: Ändere dein Passwort.",
|
||||
|
|
@ -236,7 +221,6 @@
|
|||
"passwordResetsCallOff": "Absagen",
|
||||
"passwordCallOffTitle": "Zurücksetzen absagen",
|
||||
"passwordCallOffIntro": "Dein Passwort und ein Code sagen jedes anstehende Zurücksetzen ab.",
|
||||
"passwordSectionGitHubEndsDeletes": "Die Anmeldung mit GitHub endet am {date}. Richte bis dahin ein Passwort ein: Ein Konto ohne wird 30 Tage später mit allem darin gelöscht.",
|
||||
"passwordSignedInBefore": "Nur ein seitdem angemeldetes Gerät kann diese Warnung ausblenden: Melde dich auf diesem neu an oder ändere dein Passwort.",
|
||||
"simpleLoginContinue": "Weiter mit SimpleLogin",
|
||||
"simpleLoginNote": "SimpleLogin ist die Anmeldung von Proton, und ein Proton-Konto funktioniert dort auch. SimpleLogin erfährt, dass du {instance} nutzt, und {instance} speichert nur deine SimpleLogin-ID: Dein Name schlägt einmal ein Handle vor, sonst wird nichts gespeichert.",
|
||||
|
|
|
|||
|
|
@ -154,19 +154,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"startWithGitHub": "Sign in with GitHub",
|
||||
"startGitHubFailed": "Couldn't sign in with GitHub.",
|
||||
"startNoGitHubAccount": "No account here signs in with this GitHub account.",
|
||||
"gitHubCreateAccountInstead": "Create an account with your email address and a password instead.",
|
||||
"@gitHubCreateAccountInstead": {
|
||||
"description": "Said after a GitHub account nobody on the instance signs in with, where people sign in with a password and GitHub is added to nobody (ADR 0017)."
|
||||
},
|
||||
"gitHubSimpleLoginInstead": "Continue with SimpleLogin to create one instead.",
|
||||
"@gitHubSimpleLoginInstead": {
|
||||
"description": "Said after a GitHub account nobody on the instance signs in with, where the instance offers SimpleLogin (ADR 0020): GitHub makes no account."
|
||||
},
|
||||
"gitHubUsePassword": "This account signs in with its email address and password now, not with GitHub.",
|
||||
"gitHubEnded": "Sign-in with GitHub has ended on this instance.",
|
||||
"startOtherInstance": "Use another instance",
|
||||
"startNoWay": "{instance} offers no way to create an account.",
|
||||
"@startNoWay": {
|
||||
|
|
@ -229,14 +216,6 @@
|
|||
"syncMismatchMore": "More",
|
||||
"startSignIn": "Sign in",
|
||||
"startCreateAccount": "Create an account",
|
||||
"startGitHubEnds": "Sign-in with GitHub ends on {date}.",
|
||||
"@startGitHubEnds": {
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"passwordAddress": "Email address",
|
||||
"passwordPassword": "Password",
|
||||
"passwordNewPassword": "New password",
|
||||
|
|
@ -394,14 +373,6 @@
|
|||
},
|
||||
"passwordSectionTitle": "Password and authenticator",
|
||||
"passwordSectionFailed": "Couldn't read how your account signs in.",
|
||||
"passwordSectionGitHubEnds": "Sign-in with GitHub ends on {date}. Set up a password before then to keep your account.",
|
||||
"@passwordSectionGitHubEnds": {
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"passwordSectionSetUp": "Set up a password",
|
||||
"passwordSectionSetUpHint": "Sign in with your email address, a password and an authenticator app.",
|
||||
"passwordSectionUnfinished": "Finish setting up: add an authenticator",
|
||||
|
|
@ -446,7 +417,6 @@
|
|||
"passwordNoReset": "No reset waits for this address.",
|
||||
"passwordExists": "This address's account has a password already. Sign in with it, or get back in if you've forgotten it.",
|
||||
"passwordAddressTaken": "This address belongs to another account.",
|
||||
"passwordNoAddress": "Your account has no email address yet.",
|
||||
"passwordNoPassword": "This account has no password.",
|
||||
"passwordWrongCode": "This code isn't right. Enter the one your app shows now.",
|
||||
"passwordLastAuthenticator": "Your last authenticator can't go: every sign-in needs one.",
|
||||
|
|
@ -498,11 +468,6 @@
|
|||
"passwordAddressUnknown": "Enter your account's email address: the code goes there.",
|
||||
"passwordDeleteIntroByMail": "Your account goes with everything in it, in every service of this instance. None of its authenticators works now, so a code by mail confirms it: type your password, then the code we email to your address.",
|
||||
"passwordDeleteSendCode": "Email me a code",
|
||||
"passwordSignInAgain": "To give an account that signs in with GitHub its password, sign in with GitHub again first: it has been more than a few minutes.",
|
||||
"passwordSignInWithGitHubAgain": "Sign in with GitHub again",
|
||||
"passwordOfferTitle": "Set up a password",
|
||||
"passwordOfferBody": "Sign-in with GitHub is ending. Set up a password and an authenticator app to keep your account.",
|
||||
"passwordOfferLater": "Later",
|
||||
"passwordAuthenticatorPasswordIntro": "To add it, confirm it is you with your password.",
|
||||
"passwordWarningTitle": "Someone tried your password",
|
||||
"passwordWarningBody": "On {date}, a device signed in to your account sent a wrong password or code ten times in a row, and was signed out. If that wasn't you, someone else may have that device or its session: change your password.",
|
||||
|
|
@ -535,14 +500,6 @@
|
|||
"passwordResetsCallOff": "Call it off",
|
||||
"passwordCallOffTitle": "Call off the reset",
|
||||
"passwordCallOffIntro": "Your password and a code call off every reset waiting.",
|
||||
"passwordSectionGitHubEndsDeletes": "Sign-in with GitHub ends on {date}. Set up a password before then: an account without one is deleted 30 days later, with everything in it.",
|
||||
"@passwordSectionGitHubEndsDeletes": {
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"passwordSignedInBefore": "Only a device signed in since then can dismiss this warning: sign in again on this one, or change your password.",
|
||||
"simpleLoginContinue": "Continue with SimpleLogin",
|
||||
"simpleLoginNote": "SimpleLogin is Proton's sign-in, and a Proton account works there too. It learns that you use {instance}, which keeps only your SimpleLogin ID: your name suggests a handle once, and nothing else is kept.",
|
||||
|
|
|
|||
|
|
@ -367,48 +367,6 @@ abstract class HalkyonAppLocalizations {
|
|||
/// **'Start on {instance}:'**
|
||||
String startOn(String instance);
|
||||
|
||||
/// No description provided for @startWithGitHub.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Sign in with GitHub'**
|
||||
String get startWithGitHub;
|
||||
|
||||
/// No description provided for @startGitHubFailed.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Couldn\'t sign in with GitHub.'**
|
||||
String get startGitHubFailed;
|
||||
|
||||
/// No description provided for @startNoGitHubAccount.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No account here signs in with this GitHub account.'**
|
||||
String get startNoGitHubAccount;
|
||||
|
||||
/// Said after a GitHub account nobody on the instance signs in with, where people sign in with a password and GitHub is added to nobody (ADR 0017).
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Create an account with your email address and a password instead.'**
|
||||
String get gitHubCreateAccountInstead;
|
||||
|
||||
/// Said after a GitHub account nobody on the instance signs in with, where the instance offers SimpleLogin (ADR 0020): GitHub makes no account.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Continue with SimpleLogin to create one instead.'**
|
||||
String get gitHubSimpleLoginInstead;
|
||||
|
||||
/// No description provided for @gitHubUsePassword.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'This account signs in with its email address and password now, not with GitHub.'**
|
||||
String get gitHubUsePassword;
|
||||
|
||||
/// No description provided for @gitHubEnded.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Sign-in with GitHub has ended on this instance.'**
|
||||
String get gitHubEnded;
|
||||
|
||||
/// No description provided for @startOtherInstance.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -523,12 +481,6 @@ abstract class HalkyonAppLocalizations {
|
|||
/// **'Create an account'**
|
||||
String get startCreateAccount;
|
||||
|
||||
/// No description provided for @startGitHubEnds.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Sign-in with GitHub ends on {date}.'**
|
||||
String startGitHubEnds(String date);
|
||||
|
||||
/// No description provided for @passwordAddress.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -1051,12 +1003,6 @@ abstract class HalkyonAppLocalizations {
|
|||
/// **'Couldn\'t read how your account signs in.'**
|
||||
String get passwordSectionFailed;
|
||||
|
||||
/// No description provided for @passwordSectionGitHubEnds.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Sign-in with GitHub ends on {date}. Set up a password before then to keep your account.'**
|
||||
String passwordSectionGitHubEnds(String date);
|
||||
|
||||
/// No description provided for @passwordSectionSetUp.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -1237,12 +1183,6 @@ abstract class HalkyonAppLocalizations {
|
|||
/// **'This address belongs to another account.'**
|
||||
String get passwordAddressTaken;
|
||||
|
||||
/// No description provided for @passwordNoAddress.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Your account has no email address yet.'**
|
||||
String get passwordNoAddress;
|
||||
|
||||
/// No description provided for @passwordNoPassword.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -1423,36 +1363,6 @@ abstract class HalkyonAppLocalizations {
|
|||
/// **'Email me a code'**
|
||||
String get passwordDeleteSendCode;
|
||||
|
||||
/// No description provided for @passwordSignInAgain.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'To give an account that signs in with GitHub its password, sign in with GitHub again first: it has been more than a few minutes.'**
|
||||
String get passwordSignInAgain;
|
||||
|
||||
/// No description provided for @passwordSignInWithGitHubAgain.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Sign in with GitHub again'**
|
||||
String get passwordSignInWithGitHubAgain;
|
||||
|
||||
/// No description provided for @passwordOfferTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Set up a password'**
|
||||
String get passwordOfferTitle;
|
||||
|
||||
/// No description provided for @passwordOfferBody.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Sign-in with GitHub is ending. Set up a password and an authenticator app to keep your account.'**
|
||||
String get passwordOfferBody;
|
||||
|
||||
/// No description provided for @passwordOfferLater.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Later'**
|
||||
String get passwordOfferLater;
|
||||
|
||||
/// No description provided for @passwordAuthenticatorPasswordIntro.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -1519,12 +1429,6 @@ abstract class HalkyonAppLocalizations {
|
|||
/// **'Your password and a code call off every reset waiting.'**
|
||||
String get passwordCallOffIntro;
|
||||
|
||||
/// No description provided for @passwordSectionGitHubEndsDeletes.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Sign-in with GitHub ends on {date}. Set up a password before then: an account without one is deleted 30 days later, with everything in it.'**
|
||||
String passwordSectionGitHubEndsDeletes(String date);
|
||||
|
||||
/// No description provided for @passwordSignedInBefore.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
|
|||
|
|
@ -175,32 +175,6 @@ class HalkyonAppLocalizationsDe extends HalkyonAppLocalizations {
|
|||
return 'Starte auf $instance:';
|
||||
}
|
||||
|
||||
@override
|
||||
String get startWithGitHub => 'Mit GitHub anmelden';
|
||||
|
||||
@override
|
||||
String get startGitHubFailed => 'Anmeldung mit GitHub fehlgeschlagen.';
|
||||
|
||||
@override
|
||||
String get startNoGitHubAccount =>
|
||||
'Kein Konto hier meldet sich mit diesem GitHub-Konto an.';
|
||||
|
||||
@override
|
||||
String get gitHubCreateAccountInstead =>
|
||||
'Erstelle stattdessen ein Konto mit deiner E-Mail-Adresse und einem Passwort.';
|
||||
|
||||
@override
|
||||
String get gitHubSimpleLoginInstead =>
|
||||
'Mit SimpleLogin kannst du stattdessen eins erstellen.';
|
||||
|
||||
@override
|
||||
String get gitHubUsePassword =>
|
||||
'Dieses Konto meldet sich jetzt mit seiner E-Mail-Adresse und seinem Passwort an, nicht mehr mit GitHub.';
|
||||
|
||||
@override
|
||||
String get gitHubEnded =>
|
||||
'Die Anmeldung mit GitHub ist auf dieser Instanz beendet.';
|
||||
|
||||
@override
|
||||
String get startOtherInstance => 'Andere Instanz verwenden';
|
||||
|
||||
|
|
@ -278,11 +252,6 @@ class HalkyonAppLocalizationsDe extends HalkyonAppLocalizations {
|
|||
@override
|
||||
String get startCreateAccount => 'Konto erstellen';
|
||||
|
||||
@override
|
||||
String startGitHubEnds(String date) {
|
||||
return 'Die Anmeldung mit GitHub endet am $date.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get passwordAddress => 'E-Mail-Adresse';
|
||||
|
||||
|
|
@ -597,11 +566,6 @@ class HalkyonAppLocalizationsDe extends HalkyonAppLocalizations {
|
|||
String get passwordSectionFailed =>
|
||||
'Wie dein Konto sich anmeldet, konnte nicht gelesen werden.';
|
||||
|
||||
@override
|
||||
String passwordSectionGitHubEnds(String date) {
|
||||
return 'Die Anmeldung mit GitHub endet am $date. Richte bis dahin ein Passwort ein, um dein Konto zu behalten.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get passwordSectionSetUp => 'Passwort einrichten';
|
||||
|
||||
|
|
@ -713,9 +677,6 @@ class HalkyonAppLocalizationsDe extends HalkyonAppLocalizations {
|
|||
String get passwordAddressTaken =>
|
||||
'Diese Adresse gehört zu einem anderen Konto.';
|
||||
|
||||
@override
|
||||
String get passwordNoAddress => 'Dein Konto hat noch keine E-Mail-Adresse.';
|
||||
|
||||
@override
|
||||
String get passwordNoPassword => 'Dieses Konto hat kein Passwort.';
|
||||
|
||||
|
|
@ -834,23 +795,6 @@ class HalkyonAppLocalizationsDe extends HalkyonAppLocalizations {
|
|||
@override
|
||||
String get passwordDeleteSendCode => 'Code per E-Mail schicken';
|
||||
|
||||
@override
|
||||
String get passwordSignInAgain =>
|
||||
'Um einem Konto, das sich mit GitHub anmeldet, ein Passwort zu geben, melde dich erst wieder mit GitHub an: Es ist mehr als ein paar Minuten her.';
|
||||
|
||||
@override
|
||||
String get passwordSignInWithGitHubAgain => 'Wieder mit GitHub anmelden';
|
||||
|
||||
@override
|
||||
String get passwordOfferTitle => 'Richte ein Passwort ein';
|
||||
|
||||
@override
|
||||
String get passwordOfferBody =>
|
||||
'Die Anmeldung mit GitHub endet. Richte ein Passwort und eine Authenticator-App ein, um dein Konto zu behalten.';
|
||||
|
||||
@override
|
||||
String get passwordOfferLater => 'Später';
|
||||
|
||||
@override
|
||||
String get passwordAuthenticatorPasswordIntro =>
|
||||
'Bestätige zum Hinzufügen mit deinem Passwort, dass du es bist.';
|
||||
|
|
@ -892,11 +836,6 @@ class HalkyonAppLocalizationsDe extends HalkyonAppLocalizations {
|
|||
String get passwordCallOffIntro =>
|
||||
'Dein Passwort und ein Code sagen jedes anstehende Zurücksetzen ab.';
|
||||
|
||||
@override
|
||||
String passwordSectionGitHubEndsDeletes(String date) {
|
||||
return 'Die Anmeldung mit GitHub endet am $date. Richte bis dahin ein Passwort ein: Ein Konto ohne wird 30 Tage später mit allem darin gelöscht.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get passwordSignedInBefore =>
|
||||
'Nur ein seitdem angemeldetes Gerät kann diese Warnung ausblenden: Melde dich auf diesem neu an oder ändere dein Passwort.';
|
||||
|
|
|
|||
|
|
@ -173,31 +173,6 @@ class HalkyonAppLocalizationsEn extends HalkyonAppLocalizations {
|
|||
return 'Start on $instance:';
|
||||
}
|
||||
|
||||
@override
|
||||
String get startWithGitHub => 'Sign in with GitHub';
|
||||
|
||||
@override
|
||||
String get startGitHubFailed => 'Couldn\'t sign in with GitHub.';
|
||||
|
||||
@override
|
||||
String get startNoGitHubAccount =>
|
||||
'No account here signs in with this GitHub account.';
|
||||
|
||||
@override
|
||||
String get gitHubCreateAccountInstead =>
|
||||
'Create an account with your email address and a password instead.';
|
||||
|
||||
@override
|
||||
String get gitHubSimpleLoginInstead =>
|
||||
'Continue with SimpleLogin to create one instead.';
|
||||
|
||||
@override
|
||||
String get gitHubUsePassword =>
|
||||
'This account signs in with its email address and password now, not with GitHub.';
|
||||
|
||||
@override
|
||||
String get gitHubEnded => 'Sign-in with GitHub has ended on this instance.';
|
||||
|
||||
@override
|
||||
String get startOtherInstance => 'Use another instance';
|
||||
|
||||
|
|
@ -274,11 +249,6 @@ class HalkyonAppLocalizationsEn extends HalkyonAppLocalizations {
|
|||
@override
|
||||
String get startCreateAccount => 'Create an account';
|
||||
|
||||
@override
|
||||
String startGitHubEnds(String date) {
|
||||
return 'Sign-in with GitHub ends on $date.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get passwordAddress => 'Email address';
|
||||
|
||||
|
|
@ -584,11 +554,6 @@ class HalkyonAppLocalizationsEn extends HalkyonAppLocalizations {
|
|||
String get passwordSectionFailed =>
|
||||
'Couldn\'t read how your account signs in.';
|
||||
|
||||
@override
|
||||
String passwordSectionGitHubEnds(String date) {
|
||||
return 'Sign-in with GitHub ends on $date. Set up a password before then to keep your account.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get passwordSectionSetUp => 'Set up a password';
|
||||
|
||||
|
|
@ -697,9 +662,6 @@ class HalkyonAppLocalizationsEn extends HalkyonAppLocalizations {
|
|||
@override
|
||||
String get passwordAddressTaken => 'This address belongs to another account.';
|
||||
|
||||
@override
|
||||
String get passwordNoAddress => 'Your account has no email address yet.';
|
||||
|
||||
@override
|
||||
String get passwordNoPassword => 'This account has no password.';
|
||||
|
||||
|
|
@ -816,23 +778,6 @@ class HalkyonAppLocalizationsEn extends HalkyonAppLocalizations {
|
|||
@override
|
||||
String get passwordDeleteSendCode => 'Email me a code';
|
||||
|
||||
@override
|
||||
String get passwordSignInAgain =>
|
||||
'To give an account that signs in with GitHub its password, sign in with GitHub again first: it has been more than a few minutes.';
|
||||
|
||||
@override
|
||||
String get passwordSignInWithGitHubAgain => 'Sign in with GitHub again';
|
||||
|
||||
@override
|
||||
String get passwordOfferTitle => 'Set up a password';
|
||||
|
||||
@override
|
||||
String get passwordOfferBody =>
|
||||
'Sign-in with GitHub is ending. Set up a password and an authenticator app to keep your account.';
|
||||
|
||||
@override
|
||||
String get passwordOfferLater => 'Later';
|
||||
|
||||
@override
|
||||
String get passwordAuthenticatorPasswordIntro =>
|
||||
'To add it, confirm it is you with your password.';
|
||||
|
|
@ -874,11 +819,6 @@ class HalkyonAppLocalizationsEn extends HalkyonAppLocalizations {
|
|||
String get passwordCallOffIntro =>
|
||||
'Your password and a code call off every reset waiting.';
|
||||
|
||||
@override
|
||||
String passwordSectionGitHubEndsDeletes(String date) {
|
||||
return 'Sign-in with GitHub ends on $date. Set up a password before then: an account without one is deleted 30 days later, with everything in it.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get passwordSignedInBefore =>
|
||||
'Only a device signed in since then can dismiss this warning: sign in again on this one, or change your password.';
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import 'package:equatable/equatable.dart';
|
|||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:halkyon_app/src/auth/data/auth_repository.dart';
|
||||
import 'package:halkyon_app/src/auth/model/auth_status.dart';
|
||||
import 'package:halkyon_app/src/auth/model/github_sign_in.dart';
|
||||
|
||||
/// Keeps a person signed in: follows who is signed in, and signs out on
|
||||
/// request. Nobody signed in (at a first start, after signing out or
|
||||
|
|
@ -16,13 +15,6 @@ class SessionBloc(final AuthRepository _auth)
|
|||
extends Bloc<SessionEvent, SessionState> {
|
||||
this : super(const SessionStateLoading()) {
|
||||
on<SessionEventStarted>(_onStarted);
|
||||
on<SessionEventGitHubRequested>(_onGitHubRequested);
|
||||
on<SessionEventPasswordOfferDone>(
|
||||
(event, emit) => emit(_stateOf(_auth.status)),
|
||||
);
|
||||
on<SessionEventSignedOut>(
|
||||
(event, emit) => emit(SessionStateSignedOut(problem: event.problem)),
|
||||
);
|
||||
on<SessionEventSignOutRequested>(_onSignOutRequested);
|
||||
on<SessionEventDeleteRequested>(_onDeleteRequested);
|
||||
}
|
||||
|
|
@ -44,48 +36,6 @@ class SessionBloc(final AuthRepository _auth)
|
|||
await emit.forEach<AuthStatus>(_auth.statusChanges, onData: _stateOf);
|
||||
}
|
||||
|
||||
/// Signing in with GitHub: the GitHub account's person, if there is one.
|
||||
Future<void> _onGitHubRequested(
|
||||
SessionEventGitHubRequested event,
|
||||
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 offer, then the person's
|
||||
// app.
|
||||
} on GitHubSignInFailed catch (e) {
|
||||
_offerPassword = false;
|
||||
emit(
|
||||
SessionStateSignedOut(
|
||||
problem: switch (e.problem) {
|
||||
GitHubSignInProblem.unknownAccount =>
|
||||
SignedOutProblem.noGitHubAccount,
|
||||
GitHubSignInProblem.usePassword => SignedOutProblem.usePassword,
|
||||
GitHubSignInProblem.ended => SignedOutProblem.gitHubEnded,
|
||||
GitHubSignInProblem.deprecated ||
|
||||
GitHubSignInProblem.refused => SignedOutProblem.failed,
|
||||
},
|
||||
),
|
||||
);
|
||||
} 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,
|
||||
|
|
@ -139,15 +89,9 @@ class SessionBloc(final AuthRepository _auth)
|
|||
// Nothing works in an anonymous account any more (D25): only one that
|
||||
// began before the app started could be signed in, and it has ended.
|
||||
SignedIn(isIdentified: false) => const SessionStateSignedOut(),
|
||||
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.
|
||||
|
|
@ -160,20 +104,6 @@ sealed class const SessionEvent() with Equatable {
|
|||
/// session left from before (ADR 0020, D25).
|
||||
final class const SessionEventStarted() extends SessionEvent;
|
||||
|
||||
/// Sign in with GitHub, once signed out.
|
||||
final class const SessionEventGitHubRequested() extends SessionEvent;
|
||||
|
||||
/// Signed out, [problem] if any saying why the last way to start failed.
|
||||
final class const SessionEventSignedOut({final SignedOutProblem? problem})
|
||||
extends SessionEvent {
|
||||
@override
|
||||
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;
|
||||
|
||||
|
|
@ -229,42 +159,8 @@ enum SessionProblem() {
|
|||
deleteFailed,
|
||||
}
|
||||
|
||||
/// [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: the app asks how to start.
|
||||
final class const SessionStateSignedOut({
|
||||
/// Whether a way to start is under way.
|
||||
final bool busy = false,
|
||||
|
||||
/// Why the last way to start failed; null for none.
|
||||
final SignedOutProblem? problem,
|
||||
}) extends SessionState {
|
||||
final class const SessionStateSignedOut() extends SessionState {
|
||||
@override
|
||||
List<Object?> get props => [busy, problem];
|
||||
}
|
||||
|
||||
/// Why a way to start failed.
|
||||
enum SignedOutProblem() {
|
||||
/// No account has the GitHub account: GitHub makes none (ADR 0020, D25),
|
||||
/// so the person creates one where the instance offers a way.
|
||||
noGitHubAccount,
|
||||
|
||||
/// The GitHub account's person signs in with their address and password
|
||||
/// now (ADR 0017).
|
||||
usePassword,
|
||||
|
||||
/// GitHub signs nobody in here any more.
|
||||
gitHubEnded,
|
||||
|
||||
/// Anything else, as no connection.
|
||||
failed,
|
||||
List<Object?> get props => const [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,15 +18,6 @@ abstract interface class AuthGateway() {
|
|||
/// and so on all their devices, and forgets the session here.
|
||||
Future<void> deleteAccount();
|
||||
|
||||
/// Whether this instance signs people in with GitHub: those who have it
|
||||
/// already, as GitHub is added to nobody any more (ADR 0020, D25).
|
||||
Future<bool> gitHubSignInOffered();
|
||||
|
||||
/// Authorizes with GitHub in the browser and signs this device in as the
|
||||
/// person who has that GitHub account; false when the person cancelled
|
||||
/// on GitHub.
|
||||
Future<bool> switchToGitHub();
|
||||
|
||||
/// How the signed-in person signs in.
|
||||
Future<SignInWays> signInWays();
|
||||
|
||||
|
|
|
|||
|
|
@ -50,12 +50,6 @@ class AuthRepository(
|
|||
/// and all their devices; the UI asks first.
|
||||
Future<void> deleteAccount() => _gateway.deleteAccount();
|
||||
|
||||
/// Whether this instance signs in with GitHub those who have it.
|
||||
Future<bool> canSignInWithGitHub() => _gateway.gitHubSignInOffered();
|
||||
|
||||
/// Signs this device in as the person who has the GitHub account.
|
||||
Future<bool> switchToGitHub() => _gateway.switchToGitHub();
|
||||
|
||||
/// How the signed-in person signs in.
|
||||
Future<SignInWays> signInWays() => _gateway.signInWays();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,10 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_web_auth_2_platform_interface/flutter_web_auth_2_platform_interface.dart';
|
||||
import 'package:halkyon_app/src/auth/data/auth_gateway.dart';
|
||||
import 'package:halkyon_app/src/auth/model/auth_status.dart';
|
||||
import 'package:halkyon_app/src/auth/model/github_sign_in.dart';
|
||||
import 'package:halkyon_app/src/auth/model/session_grant.dart';
|
||||
import 'package:halkyon_app/src/auth/model/sign_in_ways.dart';
|
||||
import 'package:halkyon_app/src/base/base_client.dart';
|
||||
import 'package:halkyon_base_client/halkyon_base_client.dart'
|
||||
show GitHubSignInRefusal, GitHubSignInRefusedException;
|
||||
import 'package:halkyon_base_client/halkyon_base_client.dart' as base_client;
|
||||
import 'package:halkyon_oidc_flutter/halkyon_oidc_flutter.dart'
|
||||
show LoopbackWebAuth;
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:serverpod_auth_idp_flutter/serverpod_auth_idp_flutter.dart';
|
||||
|
||||
/// [AuthGateway] on top of an app's Serverpod client and its session
|
||||
|
|
@ -24,22 +15,6 @@ import 'package:serverpod_auth_idp_flutter/serverpod_auth_idp_flutter.dart';
|
|||
class ServerpodAuthGateway(
|
||||
final ServerpodClientShared _client, {
|
||||
required final base_client.Caller _base,
|
||||
|
||||
/// The app's own URL scheme, which GitHub sends the browser back to on
|
||||
/// Android, iOS and macOS (`app.halkyon.learn`), registered with the
|
||||
/// system for the app and in the instance's GitHub OAuth app.
|
||||
required final String callbackScheme,
|
||||
|
||||
/// The port on 127.0.0.1 the app listens on for GitHub's answer on Linux
|
||||
/// and Windows.
|
||||
final int loopbackPort = 43823,
|
||||
|
||||
/// The app's name, as the browser's page says to go back to it.
|
||||
required final String appName,
|
||||
|
||||
/// What the clients it makes for itself send through (to sign in as
|
||||
/// another person); tests give a fake one.
|
||||
final http.Client Function()? _httpClient,
|
||||
}) implements AuthGateway {
|
||||
this {
|
||||
_client.auth.authInfoListenable.addListener(_onAuthInfoChanged);
|
||||
|
|
@ -51,28 +26,6 @@ class ServerpodAuthGateway(
|
|||
/// an anonymous account's, left from before they went.
|
||||
static const _identifiedScope = 'identified';
|
||||
|
||||
/// Where GitHub sends the browser back after authorizing: the app's own
|
||||
/// scheme on Android, iOS and macOS; on Linux and Windows a loopback address
|
||||
/// the app listens on meanwhile (GitHub accepts any port there, so the
|
||||
/// OAuth app registers `http://127.0.0.1/github-callback`). There,
|
||||
/// [LoopbackWebAuth] takes the whole address, to answer only its path.
|
||||
late final _gitHubCallback = Platform.isLinux || Platform.isWindows
|
||||
? (
|
||||
redirectUri: 'http://127.0.0.1:$loopbackPort/github-callback',
|
||||
urlScheme: 'http://127.0.0.1:$loopbackPort/github-callback',
|
||||
)
|
||||
: (
|
||||
redirectUri: '$callbackScheme://github-callback',
|
||||
urlScheme: callbackScheme,
|
||||
);
|
||||
|
||||
/// The browser half of signing in with GitHub on Linux and Windows, in
|
||||
/// place of flutter_web_auth_2's own, which stops waiting whenever the
|
||||
/// window regains focus. Elsewhere, the package's.
|
||||
late final _loopback = Platform.isLinux || Platform.isWindows
|
||||
? LoopbackWebAuth(appName: appName)
|
||||
: null;
|
||||
|
||||
@override
|
||||
AuthStatus get status => switch (_client.auth.authInfo) {
|
||||
null => const SignedOut(),
|
||||
|
|
@ -98,85 +51,16 @@ class ServerpodAuthGateway(
|
|||
await _client.auth.updateSignedInUser(null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> gitHubSignInOffered() async =>
|
||||
(await _base.instance.basics()).gitHubClientId != null;
|
||||
|
||||
/// The client ID of this instance's GitHub OAuth app; null when GitHub
|
||||
/// sign-in is off on this instance.
|
||||
Future<String?> _gitHubClientId() async =>
|
||||
(await _base.instance.basics()).gitHubClientId;
|
||||
|
||||
@override
|
||||
Future<bool> switchToGitHub() async {
|
||||
final authorization = await _authorizeWithGitHub();
|
||||
if (authorization == null) {
|
||||
return false;
|
||||
}
|
||||
// Serverpod issues no session for another person inside a signed-in
|
||||
// call, so a client without a session asks for it.
|
||||
final signedOut = _clientOfItsOwn();
|
||||
final AuthSuccess person;
|
||||
try {
|
||||
person = await signedOut.modules.gitHubSignIn.signIn(
|
||||
code: authorization.code,
|
||||
codeVerifier: authorization.codeVerifier,
|
||||
redirectUri: authorization.redirectUri,
|
||||
);
|
||||
} on GitHubSignInRefusedException catch (error) {
|
||||
throw _gitHubFailure(error);
|
||||
} finally {
|
||||
signedOut.close();
|
||||
}
|
||||
await _client.auth.updateSignedInUser(person);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// GitHub's authorization in the browser, or null when the person
|
||||
/// cancelled it.
|
||||
Future<GitHubSignInResult?> _authorizeWithGitHub() async {
|
||||
final clientId = await _gitHubClientId();
|
||||
if (clientId == null) {
|
||||
throw StateError('This instance offers no GitHub sign-in.');
|
||||
}
|
||||
await GitHubSignInService.instance.ensureInitialized(
|
||||
clientId: clientId,
|
||||
redirectUri: _gitHubCallback.redirectUri,
|
||||
callbackUrlScheme: _gitHubCallback.urlScheme,
|
||||
// On Linux and Windows the system browser and a loopback address, not
|
||||
// an embedded webview, as the OAuth best practice for native apps
|
||||
// (RFC 8252) asks.
|
||||
useWebview: false,
|
||||
);
|
||||
if (_loopback case final loopback?) {
|
||||
// Here, after the package has put its own in place at startup.
|
||||
FlutterWebAuth2Platform.instance = loopback;
|
||||
}
|
||||
try {
|
||||
return await GitHubSignInService.instance.signIn();
|
||||
} on OAuth2PkceUserCancelledException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<SignInWays> signInWays() async {
|
||||
final ways = await _base.account.signInWays();
|
||||
return SignInWays(gitHub: ways.gitHub, email: ways.email);
|
||||
return SignInWays(email: ways.email);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> useSession(SessionGrant grant) =>
|
||||
_client.auth.updateSignedInUser(grant.session as AuthSuccess);
|
||||
|
||||
/// The instance's refusal of a GitHub sign-in as [GitHubSignInFailed].
|
||||
static GitHubSignInFailed _gitHubFailure(GitHubSignInRefusedException e) =>
|
||||
GitHubSignInFailed(gitHubSignInProblemOf(e.reason));
|
||||
|
||||
/// A client of the instance's halkyon_base, holding no session.
|
||||
BaseClient _clientOfItsOwn() =>
|
||||
BaseClient(_client.host, httpClientOverride: _httpClient?.call());
|
||||
|
||||
/// Stops listening; the owner calls it when the app shuts down.
|
||||
Future<void> dispose() async {
|
||||
_client.auth.authInfoListenable.removeListener(_onAuthInfoChanged);
|
||||
|
|
@ -185,17 +69,3 @@ class ServerpodAuthGateway(
|
|||
|
||||
void _onAuthInfoChanged() => _changes.add(status);
|
||||
}
|
||||
|
||||
/// The instance's refusal [reason] of a GitHub sign-in as the app's problem,
|
||||
/// which the screens put into words.
|
||||
GitHubSignInProblem gitHubSignInProblemOf(GitHubSignInRefusal reason) =>
|
||||
switch (reason) {
|
||||
GitHubSignInRefusal.unknownAccount => GitHubSignInProblem.unknownAccount,
|
||||
GitHubSignInRefusal.usePassword => GitHubSignInProblem.usePassword,
|
||||
GitHubSignInRefusal.deprecated => GitHubSignInProblem.deprecated,
|
||||
GitHubSignInRefusal.ended => GitHubSignInProblem.ended,
|
||||
GitHubSignInRefusal.unavailable ||
|
||||
GitHubSignInRefusal.takenByAnotherPerson ||
|
||||
GitHubSignInRefusal.otherGitHubAccountLinked =>
|
||||
GitHubSignInProblem.refused,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
/// Why signing in with GitHub didn't go through, beyond a cancel.
|
||||
enum GitHubSignInProblem() {
|
||||
/// No person has this GitHub account: GitHub makes no account (ADR 0020,
|
||||
/// D25).
|
||||
unknownAccount,
|
||||
|
||||
/// The account signs in with its address and password now, not with
|
||||
/// GitHub (ADR 0017).
|
||||
usePassword,
|
||||
|
||||
/// GitHub is added to nobody where people sign in with a password
|
||||
/// (ADR 0017, D2).
|
||||
deprecated,
|
||||
|
||||
/// GitHub signs nobody in here any more: its time ended.
|
||||
ended,
|
||||
|
||||
/// The instance refused it otherwise: it offers no GitHub sign-in, or the
|
||||
/// person already has a different GitHub account.
|
||||
refused,
|
||||
}
|
||||
|
||||
/// Signing in with GitHub didn't go through, for [problem].
|
||||
final class const GitHubSignInFailed(final GitHubSignInProblem problem)
|
||||
implements Exception {
|
||||
@override
|
||||
String toString() => 'GitHubSignInFailed(${problem.name})';
|
||||
}
|
||||
|
|
@ -1,15 +1,12 @@
|
|||
import 'package:equatable/equatable.dart';
|
||||
|
||||
/// How a person signs in, besides the sessions of their devices: with
|
||||
/// GitHub, with an email address, both, or neither (with SimpleLogin alone,
|
||||
/// which `SimpleLoginStandingCubit` tells).
|
||||
final class const SignInWays({
|
||||
required final bool gitHub,
|
||||
required final bool email,
|
||||
}) with Equatable {
|
||||
/// Neither, as long as nothing was read.
|
||||
static const none = SignInWays(gitHub: false, email: false);
|
||||
/// How a person signs in, besides the sessions of their devices: with an
|
||||
/// email address, or not (with SimpleLogin alone, which
|
||||
/// `SimpleLoginStandingCubit` tells).
|
||||
final class const SignInWays({required final bool email}) with Equatable {
|
||||
/// No way, as long as nothing was read.
|
||||
static const none = SignInWays(email: false);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [gitHub, email];
|
||||
List<Object?> get props => [email];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,9 @@
|
|||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:halkyon_app/l10n/halkyon_app_localizations.dart';
|
||||
import 'package:halkyon_app/src/app/app_frame.dart';
|
||||
import 'package:halkyon_app/src/app/app_scope.dart';
|
||||
import 'package:halkyon_app/src/auth/bloc/session_bloc.dart';
|
||||
import 'package:halkyon_app/src/auth/model/auth_status.dart';
|
||||
import 'package:halkyon_app/src/auth/widget/start_screen.dart';
|
||||
import 'package:halkyon_app/src/instance/instance_scope.dart';
|
||||
import 'package:halkyon_app/src/password/bloc/account_setup_cubit.dart';
|
||||
import 'package:halkyon_app/src/password/model/account_security.dart';
|
||||
import 'package:halkyon_app/src/password/widget/account_setup_sheet.dart';
|
||||
import 'package:halkyon_app/src/password/widget/password_texts.dart';
|
||||
import 'package:halkyon_ui/halkyon_ui.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
||||
/// Shows the app of whoever is signed in, and asks how to start while
|
||||
|
|
@ -49,13 +42,9 @@ class const SessionGate({
|
|||
look: look(context),
|
||||
home: AppFrameScope.of(context).launchScreen,
|
||||
),
|
||||
final SessionStateSignedOut signedOut => PlainApp(
|
||||
SessionStateSignedOut() => PlainApp(
|
||||
look: look(context),
|
||||
home: StartScreen(state: signedOut),
|
||||
),
|
||||
SessionStateGitHubSignedIn() => PlainApp(
|
||||
look: look(context),
|
||||
home: const _PasswordOffer(),
|
||||
home: const StartScreen(),
|
||||
),
|
||||
SessionStateActive(:final person) => KeyedSubtree(
|
||||
key: ValueKey(person.authUserId),
|
||||
|
|
@ -65,110 +54,3 @@ class const SessionGate({
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// After a GitHub sign-in, a password for an account that has none, where
|
||||
/// the instance offers them (ADR 0017, D2: GitHub sign-in ends), while the
|
||||
/// sign-in is recent enough for the instance to take one; else, or once
|
||||
/// set up or put off, on to the person's app.
|
||||
class const _PasswordOffer() extends StatefulWidget {
|
||||
@override
|
||||
State<_PasswordOffer> createState() => _PasswordOfferState();
|
||||
}
|
||||
|
||||
class _PasswordOfferState() extends State<_PasswordOffer> {
|
||||
late final Future<AccountSecurity?> _security =
|
||||
AppScope.of(
|
||||
context,
|
||||
).passwords.security().then<AccountSecurity?>(
|
||||
(security) => security,
|
||||
onError: (Object _) => null,
|
||||
);
|
||||
|
||||
void _done() =>
|
||||
context.read<SessionBloc>().add(const SessionEventPasswordOfferDone());
|
||||
|
||||
Future<void> _setUp(AccountSecurity security) async {
|
||||
final address = security.hasAddress
|
||||
? await AppScope.of(context).auth.emailAddress()
|
||||
: null;
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
await showAccountSetupSheet(
|
||||
context,
|
||||
mode: AccountSetupMode.setPassword,
|
||||
instance:
|
||||
InstanceScope.maybeOf(context)?.choice.current?.name ??
|
||||
AppScope.of(context).serverName,
|
||||
hasAddress: security.hasAddress,
|
||||
hasHandle: security.hasHandle,
|
||||
address: address,
|
||||
);
|
||||
if (mounted) {
|
||||
_done();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = HalkyonAppLocalizations.of(context);
|
||||
final theme = Theme.of(context);
|
||||
return FutureBuilder<AccountSecurity?>(
|
||||
future: _security,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState != ConnectionState.done) {
|
||||
return const Scaffold(body: Center(child: AppLoadingIndicator()));
|
||||
}
|
||||
final security = snapshot.data;
|
||||
if (security == null || !security.offered || security.hasPassword) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
_done();
|
||||
}
|
||||
});
|
||||
return const Scaffold(body: Center(child: AppLoadingIndicator()));
|
||||
}
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 480),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
l10n.passwordOfferTitle,
|
||||
style: theme.textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(switch (security.gitHubEnds) {
|
||||
final ends? =>
|
||||
(security.hasAddress
|
||||
? l10n.passwordSectionGitHubEnds
|
||||
: l10n.passwordSectionGitHubEndsDeletes)(
|
||||
passwordMoment(context, ends),
|
||||
),
|
||||
null => l10n.passwordOfferBody,
|
||||
}),
|
||||
const SizedBox(height: 24),
|
||||
FilledButton(
|
||||
onPressed: () => _setUp(security),
|
||||
child: Text(l10n.passwordSectionSetUp),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextButton(
|
||||
onPressed: _done,
|
||||
child: Text(l10n.passwordOfferLater),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:halkyon_app/l10n/halkyon_app_localizations.dart';
|
||||
import 'package:halkyon_app/src/app/app_frame.dart';
|
||||
import 'package:halkyon_app/src/app/app_scope.dart';
|
||||
import 'package:halkyon_app/src/auth/bloc/session_bloc.dart';
|
||||
import 'package:halkyon_app/src/instance/instance_scope.dart';
|
||||
import 'package:halkyon_app/src/instance/widget/account_switcher_sheet.dart';
|
||||
import 'package:halkyon_app/src/password/bloc/account_setup_cubit.dart';
|
||||
|
|
@ -10,7 +8,6 @@ import 'package:halkyon_app/src/password/model/password_failed.dart';
|
|||
import 'package:halkyon_app/src/password/widget/account_recovery_sheet.dart';
|
||||
import 'package:halkyon_app/src/password/widget/account_setup_sheet.dart';
|
||||
import 'package:halkyon_app/src/password/widget/password_sign_in_sheet.dart';
|
||||
import 'package:halkyon_app/src/password/widget/password_texts.dart';
|
||||
import 'package:halkyon_app/src/simplelogin/model/simplelogin_failed.dart';
|
||||
import 'package:halkyon_app/src/simplelogin/widget/simplelogin_sheet.dart';
|
||||
import 'package:halkyon_ui/halkyon_ui.dart';
|
||||
|
|
@ -18,25 +15,16 @@ import 'package:material_ui/material_ui.dart';
|
|||
|
||||
/// How to start while nobody is signed in: on the instance in use, with
|
||||
/// SimpleLogin where it offers it (ADR 0020), signing in with a password or
|
||||
/// creating an account where it offers password sign-in (ADR 0017), with
|
||||
/// GitHub where it offers it (saying when it ends), those who have it
|
||||
/// alone; or on another instance. There is no starting without an account
|
||||
/// (ADR 0020, D4): an instance offering no way to create one says so, and
|
||||
/// one out of reach is asked again. Nothing is made until the person
|
||||
/// chooses.
|
||||
class const StartScreen({required final SessionStateSignedOut state, super.key})
|
||||
extends StatefulWidget {
|
||||
/// creating an account where it offers password sign-in (ADR 0017); or on
|
||||
/// another instance. There is no starting without an account (ADR 0020,
|
||||
/// D4): an instance offering no way to create one says so, and one out of
|
||||
/// reach is asked again. Nothing is made until the person chooses.
|
||||
class const StartScreen({super.key}) extends StatefulWidget {
|
||||
@override
|
||||
State<StartScreen> createState() => _StartScreenState();
|
||||
}
|
||||
|
||||
typedef _Offers = ({
|
||||
bool reached,
|
||||
bool simpleLogin,
|
||||
bool gitHub,
|
||||
bool password,
|
||||
DateTime? gitHubEnds,
|
||||
});
|
||||
typedef _Offers = ({bool reached, bool simpleLogin, bool password});
|
||||
|
||||
class _StartScreenState() extends State<StartScreen> {
|
||||
late Future<_Offers> _offers = _ask();
|
||||
|
|
@ -45,7 +33,7 @@ class _StartScreenState() extends State<StartScreen> {
|
|||
/// nothing, and an instance none of them reached is out of reach.
|
||||
Future<_Offers> _ask() async {
|
||||
final dependencies = AppScope.of(context);
|
||||
Future<({bool offered, DateTime? gitHubEnds})?> passwords() async {
|
||||
Future<bool?> passwords() async {
|
||||
try {
|
||||
return await dependencies.passwords.offers();
|
||||
} on PasswordFailed {
|
||||
|
|
@ -61,33 +49,17 @@ class _StartScreenState() extends State<StartScreen> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<bool?> gitHub() async {
|
||||
try {
|
||||
return await dependencies.auth.canSignInWithGitHub();
|
||||
} on Exception {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
final (simpleLoginOffered, gitHubOffered, password) = await (
|
||||
final (simpleLoginOffered, passwordOffered) = await (
|
||||
simpleLogin(),
|
||||
gitHub(),
|
||||
passwords(),
|
||||
).wait;
|
||||
return (
|
||||
reached:
|
||||
simpleLoginOffered != null ||
|
||||
gitHubOffered != null ||
|
||||
password != null,
|
||||
reached: simpleLoginOffered != null || passwordOffered != null,
|
||||
simpleLogin: simpleLoginOffered ?? false,
|
||||
gitHub: gitHubOffered ?? false,
|
||||
password: password?.offered ?? false,
|
||||
gitHubEnds: password?.gitHubEnds,
|
||||
password: passwordOffered ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
void _send(SessionEvent event) => context.read<SessionBloc>().add(event);
|
||||
|
||||
/// Signs in with a password; a forgotten one leads to getting back in,
|
||||
/// and a reset to finish to the account's setup. Signed in, the person's
|
||||
/// app takes this screen's place.
|
||||
|
|
@ -116,7 +88,6 @@ class _StartScreenState() extends State<StartScreen> {
|
|||
final instance =
|
||||
InstanceScope.maybeOf(context)?.choice.current?.name ??
|
||||
AppScope.of(context).serverName;
|
||||
final busy = widget.state.busy;
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
|
|
@ -144,9 +115,7 @@ class _StartScreenState() extends State<StartScreen> {
|
|||
if (InstanceScope.maybeOf(context) != null) ...[
|
||||
const SizedBox(height: 24),
|
||||
TextButton(
|
||||
onPressed: busy
|
||||
? null
|
||||
: () => showAccountSwitcher(context),
|
||||
onPressed: () => showAccountSwitcher(context),
|
||||
child: Text(l10n.startOtherInstance),
|
||||
),
|
||||
],
|
||||
|
|
@ -162,13 +131,7 @@ class _StartScreenState() extends State<StartScreen> {
|
|||
],
|
||||
);
|
||||
}
|
||||
final (
|
||||
:reached,
|
||||
:simpleLogin,
|
||||
:gitHub,
|
||||
:password,
|
||||
:gitHubEnds,
|
||||
) = asked;
|
||||
final (:reached, :simpleLogin, :password) = asked;
|
||||
if (!reached) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
|
@ -204,12 +167,8 @@ class _StartScreenState() extends State<StartScreen> {
|
|||
],
|
||||
if (simpleLogin) ...[
|
||||
FilledButton(
|
||||
onPressed: busy
|
||||
? null
|
||||
: () => showSimpleLoginSheet(
|
||||
context,
|
||||
instance: instance,
|
||||
),
|
||||
onPressed: () =>
|
||||
showSimpleLoginSheet(context, instance: instance),
|
||||
child: Text(l10n.simpleLoginContinue),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
|
@ -223,85 +182,25 @@ class _StartScreenState() extends State<StartScreen> {
|
|||
if (password) ...[
|
||||
if (simpleLogin)
|
||||
OutlinedButton(
|
||||
onPressed: busy ? null : () => _signIn(instance),
|
||||
onPressed: () => _signIn(instance),
|
||||
child: Text(l10n.startSignIn),
|
||||
)
|
||||
else
|
||||
FilledButton(
|
||||
onPressed: busy ? null : () => _signIn(instance),
|
||||
onPressed: () => _signIn(instance),
|
||||
child: Text(l10n.startSignIn),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
OutlinedButton(
|
||||
onPressed: busy
|
||||
? null
|
||||
: () => showAccountSetupSheet(
|
||||
context,
|
||||
mode: AccountSetupMode.signUp,
|
||||
instance: instance,
|
||||
),
|
||||
onPressed: () => showAccountSetupSheet(
|
||||
context,
|
||||
mode: AccountSetupMode.signUp,
|
||||
instance: instance,
|
||||
),
|
||||
child: Text(l10n.startCreateAccount),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
if (gitHub) ...[
|
||||
if (password || simpleLogin)
|
||||
OutlinedButton(
|
||||
onPressed: busy
|
||||
? null
|
||||
: () => _send(
|
||||
const SessionEventGitHubRequested(),
|
||||
),
|
||||
child: busy
|
||||
? const ButtonLoadingIndicator()
|
||||
: Text(l10n.startWithGitHub),
|
||||
)
|
||||
else
|
||||
FilledButton(
|
||||
onPressed: busy
|
||||
? null
|
||||
: () => _send(
|
||||
const SessionEventGitHubRequested(),
|
||||
),
|
||||
child: busy
|
||||
? const ButtonLoadingIndicator()
|
||||
: Text(l10n.startWithGitHub),
|
||||
),
|
||||
if (gitHubEnds case final ends?) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
l10n.startGitHubEnds(passwordMoment(context, ends)),
|
||||
textAlign: TextAlign.center,
|
||||
style: note,
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
if (widget.state.problem case final problem?) ...[
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
switch (problem) {
|
||||
// Whole sentences: GitHub makes no account (ADR
|
||||
// 0020, D25), so one is created in its place,
|
||||
// where the instance offers a way.
|
||||
SignedOutProblem.noGitHubAccount => [
|
||||
l10n.startNoGitHubAccount,
|
||||
if (simpleLogin)
|
||||
l10n.gitHubSimpleLoginInstead
|
||||
else if (password)
|
||||
l10n.gitHubCreateAccountInstead,
|
||||
].join(' '),
|
||||
SignedOutProblem.usePassword =>
|
||||
l10n.gitHubUsePassword,
|
||||
SignedOutProblem.gitHubEnded => l10n.gitHubEnded,
|
||||
SignedOutProblem.failed => l10n.startGitHubFailed,
|
||||
},
|
||||
textAlign: TextAlign.center,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.error,
|
||||
),
|
||||
),
|
||||
],
|
||||
...otherInstance,
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -15,10 +15,9 @@ enum AccountSetupMode() {
|
|||
/// and no password yet): put in place once its authenticator is set up.
|
||||
signUp,
|
||||
|
||||
/// A password for the signed-in person (signing in with GitHub): they
|
||||
/// keep everything. A session alone sets
|
||||
/// none: an address of theirs takes a code mailed to it, GitHub a recent
|
||||
/// sign-in (the fourth reading).
|
||||
/// A password for the signed-in person, who has an address: they keep
|
||||
/// everything. A session alone sets none: the address takes a code mailed
|
||||
/// to it.
|
||||
setPassword,
|
||||
|
||||
/// The reset of a whole account, once due: a new password, and new keys
|
||||
|
|
@ -138,11 +137,6 @@ class AccountSetupCubit(
|
|||
/// person's own); null where the setup asks for it.
|
||||
final String? address,
|
||||
|
||||
/// Whether the signed-in person has an address already, which the code
|
||||
/// then goes to as proof that they are its owner
|
||||
/// ([AccountSetupMode.setPassword], [MailCodePurpose.firstPassword]).
|
||||
final bool _hasAddress = false,
|
||||
|
||||
/// Whether the signed-in person has a handle already
|
||||
/// ([AccountSetupMode.setPassword]); one without takes one with their
|
||||
/// first password (ADR 0020, D28).
|
||||
|
|
@ -303,9 +297,9 @@ class AccountSetupCubit(
|
|||
/// What the codes mailed here are for.
|
||||
MailCodePurpose get _codePurpose => switch (_mode) {
|
||||
AccountSetupMode.finishReset => MailCodePurpose.accountReset,
|
||||
AccountSetupMode.setPassword when _hasAddress =>
|
||||
MailCodePurpose.firstPassword,
|
||||
_ => MailCodePurpose.signUp,
|
||||
// The signed-in person's own address, the code their proof of it.
|
||||
AccountSetupMode.setPassword => MailCodePurpose.firstPassword,
|
||||
AccountSetupMode.signUp => MailCodePurpose.signUp,
|
||||
};
|
||||
|
||||
/// Mails a new code.
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ final class const PasswordSignedIn({
|
|||
|
||||
/// How the signed-in person signs in, as the instance knows it.
|
||||
typedef PasswordWays = ({
|
||||
bool gitHub,
|
||||
bool email,
|
||||
bool password,
|
||||
|
||||
|
|
@ -61,9 +60,8 @@ abstract interface class PasswordGateway() {
|
|||
/// gets nothing the real one takes.
|
||||
String get instanceUrl;
|
||||
|
||||
/// Whether the instance offers password sign-in, and until when GitHub
|
||||
/// sign-in lasts there (null for no end set).
|
||||
Future<({bool offered, DateTime? gitHubEnds})> offers();
|
||||
/// Whether the instance offers password sign-in.
|
||||
Future<bool> offers();
|
||||
|
||||
/// Mails a code for [purpose] to [address], worded in [language]; the
|
||||
/// request it answers. Only [purpose] takes it.
|
||||
|
|
|
|||
|
|
@ -132,9 +132,8 @@ class PasswordRepository(
|
|||
unawaited(_changes.close());
|
||||
}
|
||||
|
||||
/// Whether the instance offers password sign-in, and when GitHub sign-in
|
||||
/// ends there (null for no end set, or no GitHub sign-in).
|
||||
Future<({bool offered, DateTime? gitHubEnds})> offers() => _gateway.offers();
|
||||
/// Whether the instance offers password sign-in.
|
||||
Future<bool> offers() => _gateway.offers();
|
||||
|
||||
/// Mails a code for [purpose] to [address] in [language]; the request it
|
||||
/// answers.
|
||||
|
|
@ -280,10 +279,10 @@ class PasswordRepository(
|
|||
needsAuthenticator: true,
|
||||
);
|
||||
|
||||
/// Gives the signed-in person [material]'s password, with [address]
|
||||
/// proved by a [code] mailed for [request] where they have none (one who
|
||||
/// signs in with GitHub, ADR 0017, D2), and [handle] where they have none
|
||||
/// (ADR 0020, D28), keeping everything. Their keys go into the keyring.
|
||||
/// Gives the signed-in person [material]'s password, their [address]
|
||||
/// proved by a [code] mailed for [request], and [handle] where they have
|
||||
/// none (ADR 0020, D28), keeping everything. Their keys go into the
|
||||
/// keyring.
|
||||
Future<void> setPassword(
|
||||
NewAccountMaterial material, {
|
||||
String? address,
|
||||
|
|
@ -460,14 +459,12 @@ class PasswordRepository(
|
|||
// `wait` would wrap the two in a ParallelWaitError, which no caller
|
||||
// takes for a refusal (the account section then showed nothing offline).
|
||||
final waysRead = _gateway.ways()..ignore();
|
||||
final offers = await _gateway.offers();
|
||||
final offered = await _gateway.offers();
|
||||
final ways = await waysRead;
|
||||
return AccountSecurity(
|
||||
offered: offers.offered,
|
||||
offered: offered,
|
||||
hasAddress: ways.email,
|
||||
hasHandle: ways.handle,
|
||||
gitHub: ways.gitHub,
|
||||
gitHubEnds: offers.gitHubEnds,
|
||||
hasPassword: ways.password,
|
||||
secondFactorWorks: ways.secondFactorWorks,
|
||||
sessionEndedAt: ways.sessionEndedForProofsAt,
|
||||
|
|
|
|||
|
|
@ -32,15 +32,8 @@ class ServerpodPasswordGateway(
|
|||
String get instanceUrl => srp.canonicalInstanceUrl(_client.host);
|
||||
|
||||
@override
|
||||
Future<({bool offered, DateTime? gitHubEnds})> offers() => _failing(() async {
|
||||
final basics = await _base.instance.basics();
|
||||
return (
|
||||
offered: basics.passwordSignIn,
|
||||
gitHubEnds: basics.gitHubClientId == null
|
||||
? null
|
||||
: basics.gitHubSignInEnds,
|
||||
);
|
||||
});
|
||||
Future<bool> offers() =>
|
||||
_failing(() async => (await _base.instance.basics()).passwordSignIn);
|
||||
|
||||
@override
|
||||
Future<String> sendCode(
|
||||
|
|
@ -234,7 +227,6 @@ class ServerpodPasswordGateway(
|
|||
Future<PasswordWays> ways() => _failing(() async {
|
||||
final ways = await _base.account.signInWays();
|
||||
return (
|
||||
gitHub: ways.gitHub,
|
||||
email: ways.email,
|
||||
password: ways.password,
|
||||
handle: ways.handle,
|
||||
|
|
@ -477,7 +469,6 @@ PasswordProblem passwordProblemOf(
|
|||
protocol.PasswordRefusal.setupIncomplete => PasswordProblem.setupIncomplete,
|
||||
protocol.PasswordRefusal.invalidSetup => PasswordProblem.invalidSetup,
|
||||
protocol.PasswordRefusal.passwordExists => PasswordProblem.passwordExists,
|
||||
protocol.PasswordRefusal.noAddress => PasswordProblem.noAddress,
|
||||
protocol.PasswordRefusal.addressTaken => PasswordProblem.addressTaken,
|
||||
protocol.PasswordRefusal.noPassword => PasswordProblem.noPassword,
|
||||
protocol.PasswordRefusal.unknownAuthenticator =>
|
||||
|
|
@ -495,6 +486,5 @@ PasswordProblem passwordProblemOf(
|
|||
protocol.PasswordRefusal.otherInstance => PasswordProblem.instanceUnproven,
|
||||
protocol.PasswordRefusal.codesWait => PasswordProblem.codesWait,
|
||||
protocol.PasswordRefusal.sessionEnded => PasswordProblem.sessionEnded,
|
||||
protocol.PasswordRefusal.signInAgain => PasswordProblem.signInAgain,
|
||||
protocol.PasswordRefusal.signedInBefore => PasswordProblem.signedInBefore,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,12 +14,6 @@ final class const AccountSecurity({
|
|||
/// one where they have none (D28).
|
||||
required final bool hasHandle,
|
||||
|
||||
/// Whether the person signs in with GitHub.
|
||||
required final bool gitHub,
|
||||
|
||||
/// When GitHub sign-in ends on the instance; null for no end set.
|
||||
final DateTime? gitHubEnds,
|
||||
|
||||
/// Whether the person has a password.
|
||||
required final bool hasPassword,
|
||||
|
||||
|
|
@ -54,8 +48,6 @@ final class const AccountSecurity({
|
|||
offered,
|
||||
hasAddress,
|
||||
hasHandle,
|
||||
gitHub,
|
||||
gitHubEnds,
|
||||
hasPassword,
|
||||
secondFactorWorks,
|
||||
sessionEndedAt,
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue