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:
Tizian 「ティツィアーン」 2026-10-25 12:52:52 +01:00
commit 4e33133733
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
40 changed files with 564 additions and 3570 deletions

View file

@ -20,15 +20,13 @@ Endpoints, under the nickname `base` in a host's client (`client.modules.base.em
| `accountRecovery` | `resetAuthenticator`, `beginKeyProof`, `resetPassword` (with the recovery phrase), `startAccountReset`, `completeAccountReset` | without a session |
| `simpleLogin` | `begin(platform)`: an attempt and SimpleLogin's address to open, at most 30 an hour per network; `complete(attempt, code)`: signed out, a session for the account with that SimpleLogin ID, or for someone new the handle suggested from their name; signed in (`identified` only, `wrongCaller` otherwise), SimpleLogin added to the caller's account; for an account, whether it has its keys, whether its password signs in too and has the keys' proof key (D34), when a reset of its keys takes effect (D31) and its handle, the attempt standing ten minutes for the sign-in; `checkHandle(attempt, handle)`: whether someone new could take a handle, at most 30 an hour per attempt and 60 per network; `signUp(attempt, handle, setup)`, without a session: someone new's account, `identified`, with the handle and the keys the device made; a session; `beginMend(attempt, handle)`, `mend(attempt, proof)`, without a session: a broken link mended with the handle and the recovery phrase, the new identity in the old one's place; a session | anyone |
| `keys` | for an account that signs in with SimpleLogin (Halkyon Learn's ADR 0020, D19 and D20): `begin`, `unlock`: the caller's keys for their device, once the password is proven with its own proof key; `set`: the first keys (and handle) of an account SimpleLogin was added to, with a SimpleLogin sign-in of the last ten minutes; `changePassword`, `beginPhrase` and `resetPassword` (a lost password, with the recovery phrase), `setRecoveryKeys`: each with the password's proof (the phrase's) and such a sign-in (`KeysOwnerProof`), a new password ending every other session; the password and the phrase lost (D31–D33), `startReset` with such a sign-in, `completeReset` 72 hours later with another (new keys, every other session ending), `callOffResets` with the password's proof, which any proof of the password or the phrase also does; only an account that signs in with SimpleLogin alone changes here, one whose password signs in only unlocking (D34); the tenth wrong proof in a row ends the session, as ADR 0017's do | signed in |
| `gitHubSignIn` | `signIn`: a session for whoever has that GitHub account, unless they sign in with a password; none once `GITHUB_SIGN_IN_ENDS` has come | without a session |
| `emailCode` | `sendCode`: a code by mail, which proves the address and signs nobody in | anyone |
| `account` | `delete` (with the password's proof where the account has one, `keysProof` for one that signs in with SimpleLogin), `signInWays` | signed in |
| `instance` | `basics`: the app it serves, GitHub's client ID if any and when GitHub sign-in ends, whether codes by mail, password sign-in and SimpleLogin are on, the source link, the oldest app build, where data and backups are | anyone |
| `instance` | `basics`: the app it serves, whether codes by mail, password sign-in and SimpleLogin are on, the source link, the oldest app build, where data and backups are | anyone |
| `profile` | `mine`, `save`, `checkHandle` (with every answer about a new handle, at most 30 an hour per person and 60 per network), `checkLinks` (at most 10 an hour), `setAvatar`, `removeAvatar`, `remove` (what it shows goes, the handle stays, private again: Halkyon Learn's ADR 0020, D27): the caller's own public profile | `identified` |
| `profiles` | `byHandle`: anyone's public profile once they went public, whatever the handle's case | signed in |
- GitHub keeps only the numeric GitHub ID, in Serverpod's own table; email keeps only
HMAC-SHA256 of the address, keyed with `emailSignInPepper` (`email_identity`), and its
- Email keeps only HMAC-SHA256 of the address, keyed with `emailSignInPepper` (`email_identity`), and its
codes as Argon2id hashes (`email_code_request`). Codes are eight Crockford characters,
valid 15 minutes, 3 tries; up to 3 valid at once for an address (a fourth voids the
oldest, so codes others ask for don't void the one its owner is typing); at most 3 an
@ -103,14 +101,14 @@ Endpoints, under the nickname `base` in a host's client (`client.modules.base.em
itself. Never the `Forwarded` header, which Caddy passes on as the visitor wrote it,
so never Serverpod's `remoteInfo`. An IPv6 visitor counts by their /64
(`networkKeyOf`), which one line or server holds whole.
- Refusals are `PasswordRefusedException`, `GitHubSignInRefusedException`,
`EmailSignInRefusedException` and `ProfileRefusedException`, each with a reason;
`ProfileNotFoundException` where there is no profile.
- Refusals are `PasswordRefusedException`, `EmailSignInRefusedException` and
`ProfileRefusedException`, each with a reason; `ProfileNotFoundException` where there
is no profile.
- For the host's own code, `package:halkyon_base_server/base.dart`: the configuration,
`tokenManagerBuilders` and `identityProviderBuilders`, the `identified` scope and
`grantIdentified`, `deletePerson`, the pruning (`pruneAnonymousPeople`,
`forgetOldSignUpAttempts`, `forgetExpiredEmailCodes`, `forgetUnconfirmedAuthenticators`,
`forgetGitHubAccounts`, `forgetLapsedResets`), short codes, `sourceCodeUrl`,
`forgetLapsedResets`), short codes, `sourceCodeUrl`,
`dataLocation`, `backupLocation`, people's profiles as the host's code reads them
(`profileOf`, `profilesOf`, `personWithHandle`, `fixHandle`; `saveProfile` for its
tests), `accountDomain`, `profilePageUrl`, `addProfilePageRoute`, the register
@ -121,10 +119,10 @@ Endpoints, under the nickname `base` in a host's client (`client.modules.base.em
## The instance's register (ADR 0016)
One operator's services share one account per person: the same address, GitHub account
and handle reach the same person in each, under one account ID (each service's
One operator's services share one account per person: the same address, SimpleLogin
account and handle reach the same person in each, under one account ID (each service's
`AuthUser` has it), and deleting the account deletes it everywhere. One service holds
the register, its database keeping the ways in (`email_identity`, GitHub's IDs), the
the register, its database keeping the ways in (`email_identity`, `halkyon_oidc_identity`), the
handles and profiles; the others are its members and ask it. An anonymous account left
from before stays its service's own. Sessions stay each service's own: every app signs
in itself.
@ -149,7 +147,7 @@ in itself.
host's Caddyfile never forwards them).
- **Signing in on a member:** the app calls the same endpoints in both modes. The member
asks the register to mail a code (the device's network passed on, as the register
counts codes per network) and to check it, or to say whose GitHub ID it is, and opens a
counts codes per network) and to check it, and opens a
session of its own for that account (`issueSession`, which makes them a person there
under the account's ID the first time). Adding a way in makes the person identified on
the register and then on the member; a switch token signs in as the account the
@ -238,12 +236,10 @@ Built on the three parts above, on the register (members forward every step thro
3–16 passes, 64 MiB–1 GiB), verifier (1 < v < N, as long as N), public keys (32 bytes
each) and both wrappings (24-byte nonce, 112 bytes). It needs an address, and a
session alone sets none (ADR 0017's fourth reading): an account with an address proves
it anew by a code mailed to it for this (`firstPassword`); an account that signs in
with GitHub alone does that within 10 minutes of a GitHub sign-in on the session asking
(`gitHubSignInFreshFor`, the session row's method and creation time, a member telling
the register; else `signInAgain`, before any code is used), the address it adds proved
by a mailed code (`signUp`). Anyone else, an anonymous account left from before among
them, sets none (`wrongCredentials`, before any code is used). It signs in once an
it anew by a code mailed to it for this (`firstPassword`). Anyone else, an account
that signs in with SimpleLogin alone or one left from before with an anonymous account
or a GitHub ID among them (Halkyon Learn's ADR 0020, D36), sets none
(`wrongCredentials`, before any code is used). It signs in once an
authenticator is confirmed; until then a new setup replaces it, never once one was,
even while a reset leaves the account without one (a new password comes through the
recovery phrase or the account's reset alone).
@ -388,16 +384,11 @@ Built on the three parts above, on the register (members forward every step thro
day). The owner's warning of a session ended for its proofs is
dismissed only by a session made since (`signedInBefore`). A refusal for a reset not due says when it takes effect
(`takesEffectAt`).
- **Older ways:** an account that signs in with a password (it had a confirmed
authenticator, which a reset may have taken since: `passwordOnly` in its ways in) no
longer signs in with GitHub (`usePassword`); no account gets GitHub any more (D25),
so it signs in only those who have it; from `GITHUB_SIGN_IN_ENDS` on GitHub signs nobody in
(`ended`), the basics stop naming the day (`gitHubSignInEnds`, which apps show until
then) and, 30 days later (`gitHubAccountsKeptAfterEnd`, so a date set wrong can be put
right first), `forgetGitHubAccounts` deletes every GitHub ID on the register, first
deleting the accounts that sign in with GitHub alone (no address, never a password),
each on its own (one failing is tried again the next day; one deleted whose host step
after failed counts as deleted), the members told once after all of them.
- **Older ways:** GitHub signs nobody in (Halkyon Learn's ADR 0020, D36, ending ADR
0017's D2 sooner): the module has no GitHub endpoint and reads no GitHub settings. The
IDs left in Serverpod's `serverpod_auth_idp_github_account` table, which stays as
Serverpod's migrations make it, go with their accounts (`ON DELETE CASCADE`); an
operator deletes them before with the SQL of D36.
## Taking it into a host
@ -452,8 +443,7 @@ Built on the three parts above, on the register (members forward every step thro
after any literal routes of its own.
Then schedule the pruning in a future call of the host's own: daily
`pruneAnonymousPeople`, `forgetGitHubAccounts` (the register's),
`forgetLapsedResets` and `forgetPeopleTheRegisterForgot` (a member's), hourly
`pruneAnonymousPeople`, `forgetLapsedResets` and `forgetPeopleTheRegisterForgot` (a member's), hourly
`forgetOldSignUpAttempts`, `forgetExpiredEmailCodes`,
`forgetUnconfirmedAuthenticators`, `forgetUnfinishedSimpleLogins` (the
register's), `deliverDeletionNotices` and `deliverSessionNotices` (the register's). Each marked as one mode's does nothing in
@ -471,8 +461,7 @@ Built on the three parts above, on the register (members forward every step thro
version, with no SQL besides. Every service has the module's tables, whichever its
mode: a member's stay empty but for its people's sessions.
The passwords it reads: `serverSideSessionKeyHashPepper`; for GitHub `githubClientId` and
`githubClientSecret`; for email `emailSignInPepper` and `emailSmtpHost`,
The passwords it reads: `serverSideSessionKeyHashPepper`; for email `emailSignInPepper` and `emailSmtpHost`,
`emailSmtpPort`, `emailSmtpUser`, `emailSmtpPassword`, `emailFrom`; for passwords
`totpKey`, `recoveryCodeKey` and `signInDecoyKey` (each at least 32 characters, or
`addInstanceRoutes` stops the server; all three and email sign-in, or no password
@ -483,14 +472,13 @@ and without it: mending answers decoys); between the
instance's services `instanceSecret`. Never change either pepper
once people sign in: every session or address hash would be lost; changing `totpKey`
voids every authenticator, `recoveryCodeKey` every recovery code. A member needs no email settings of its
own (the register mails the codes), but GitHub's (its own OAuth app's, whose callback is
its app's). The environment: `ACCOUNT_REGISTER_URL` and `INSTANCE_MEMBERS` (above);
own (the register mails the codes). The environment: `ACCOUNT_REGISTER_URL` and `INSTANCE_MEMBERS` (above);
`ACCOUNT_DOMAIN`, the domain handles are on, which can never change once anyone follows
one; `SIMPLELOGIN_CALLBACK_URL`, the https address the SimpleLogin app registered, where
it isn't the register's own page (`/simplelogin-callback` on its web server's public
address; development names learn.halkyon.eu's); `SIGN_IN_WAYS`, the ways in the
register offers of those set up, `simplelogin`, `password` or both (unset: every one), a
name it doesn't know stopping the server (`addInstanceRoutes`); `GITHUB_SIGN_IN_ENDS`, the day GitHub stops signing anyone in (`2027-01-22`);
name it doesn't know stopping the server (`addInstanceRoutes`);
`SOURCE_URL`, `DATA_LOCATION`, `BACKUP_LOCATION`: where the instance keeps people's data
and its backups, each `EU`, a country's code (`DE`) or a subdivision's (ISO 3166-2,
`US-CA`, `DE-BY`), the backups also `none`; where the disks are, not which law governs;

View file

@ -11,8 +11,6 @@ export 'src/auth/account.dart';
export 'src/auth/auth_config.dart';
export 'src/auth/code_mail.dart';
export 'src/auth/email.dart';
export 'src/auth/github.dart';
export 'src/auth/github_sign_in_endpoint.dart' show gitHubAccountToSignIn;
export 'src/auth/identified.dart';
export 'src/auth/offered_ways.dart';
export 'src/auth/scopes.dart';
@ -47,7 +45,6 @@ export 'src/password/key_proof.dart'
export 'src/password/password_config.dart';
export 'src/password/passwords.dart'
show
gitHubSignInFreshFor,
isKeysPassword,
isKeysSetup,
isNewPassword,

View file

@ -127,8 +127,8 @@ Future<void> deletePerson(
where: (t) => t.authUserId.equals(authUserId),
transaction: transaction,
);
// Their GitHub ID and SimpleLogin identity go with them, and an
// anonymous account left from before (ON DELETE CASCADE).
// Their SimpleLogin identity goes with them, and what is left from
// before, an anonymous account or a GitHub ID (ON DELETE CASCADE).
// Gone already on a member the register told first (ADR 0016).
// DELETE FROM "serverpod_auth_core_user" WHERE "id" = $1
if (person != null) {

View file

@ -92,7 +92,7 @@ class AccountEndpoint extends Endpoint {
).knownAccounts(session, {person})).contains(person);
/// How the caller signs in besides their sessions ([SignInWays]): with
/// SimpleLogin, a password, an email address or GitHub.
/// SimpleLogin, a password or an email address.
Future<SignInWays> signInWays(Session session) => AccountRegister.of(
session.serverpod,
).signInWaysOf(session, session.authenticated!.authUserId);

View file

@ -1,8 +1,6 @@
import 'package:halkyon_base_server/src/auth/github.dart';
import 'package:halkyon_base_server/src/simplelogin/simplelogin_config.dart';
import 'package:serverpod/serverpod.dart';
import 'package:serverpod_auth_idp_server/core.dart';
import 'package:serverpod_auth_idp_server/providers/github.dart';
// How people authenticate. The server and its tests use this same
// configuration, so the tests check what production runs.
@ -19,14 +17,9 @@ final List<TokenManagerBuilder> tokenManagerBuilders = [
/// which are the module's own (`passwords.dart`, `email.dart`). No anonymous
/// accounts (ADR 0020, D25): every account has a way in.
///
/// GitHub only where the operator registered a GitHub OAuth app (see
/// [gitHubSignInAvailable]); its helpers serve our own slim login
/// (`github.dart`), and Serverpod's stock GitHub login endpoint isn't exposed.
///
/// SimpleLogin (ADR 0020) on the register, where it has the instance's
/// SimpleLogin app ([simpleLoginIdpConfig]): halkyon_oidc's sign-in, whose
/// parts `simplelogin.dart` uses; its stock endpoint isn't exposed either.
/// parts `simplelogin.dart` uses; its stock endpoint isn't exposed.
List<IdentityProviderBuilder> identityProviderBuilders(Serverpod pod) => [
if (gitHubSignInAvailable(pod)) GitHubIdpConfigFromPasswords(),
?simpleLoginIdpConfig(pod),
];

View file

@ -1,212 +0,0 @@
import 'dart:io';
import 'package:clock/clock.dart';
import 'package:halkyon_base_server/src/auth/account.dart';
import 'package:halkyon_base_server/src/auth/membership.dart';
import 'package:halkyon_base_server/src/generated/protocol.dart';
import 'package:halkyon_base_server/src/register/deletion_notices.dart';
import 'package:serverpod/serverpod.dart';
import 'package:serverpod_auth_idp_server/core.dart';
import 'package:serverpod_auth_idp_server/providers/github.dart';
// Signing in with GitHub keeps only the numeric GitHub ID (decided 2026-09-30).
// So the login is our own, on Serverpod's GitHub helpers: Serverpod's stock
// login also stores the name and the email in a profile and downloads the
// avatar to the server.
/// Serverpod's name for GitHub sign-in on session rows (`GitHubIdp.method`).
const gitHubMethod = 'github';
/// Whether this instance's operator registered a GitHub OAuth app: its client
/// ID and secret are in the passwords (`githubClientId`,
/// `githubClientSecret`). Without them, GitHub sign-in is off and the app
/// hides it. Empty values count as missing: Compose passes unset variables on
/// as empty strings.
bool gitHubSignInAvailable(Serverpod pod) =>
(pod.getPassword('githubClientId') ?? '').isNotEmpty &&
(pod.getPassword('githubClientSecret') ?? '').isNotEmpty;
/// When GitHub stops signing anyone in here (ADR 0017, D2: three months
/// after the release that brings passwords): the operator's
/// `GITHUB_SIGN_IN_ENDS`, a day (`2027-01-22`) from its start in UTC; null
/// while unset. From then on GitHub signs nobody in and links nothing, the
/// instance offers it no more, and pruning forgets every GitHub ID
/// ([forgetGitHubAccounts]). Throws a [StateError] for a value that is no
/// such day, which hosts check as they start (`addInstanceRoutes`).
DateTime? gitHubSignInEnds(Serverpod pod) =>
_endsOf[pod] ??
gitHubSignInEndsFrom(Platform.environment['GITHUB_SIGN_IN_ENDS']);
/// [value] (`GITHUB_SIGN_IN_ENDS`) as the moment GitHub sign-in ends: the
/// day's start in UTC; null for none (unset or empty, as Compose passes an
/// unset variable). Throws a [StateError] for anything but a day.
DateTime? gitHubSignInEndsFrom(String? value) {
final day = value?.trim() ?? '';
if (day.isEmpty) {
return null;
}
final parsed = RegExp(r'^\d{4}-\d{2}-\d{2}$').hasMatch(day)
? DateTime.tryParse('${day}T00:00:00Z')
: null;
if (parsed == null || parsed.toIso8601String().substring(0, 10) != day) {
throw StateError(
'GITHUB_SIGN_IN_ENDS is "$day": it must be a day, such as 2027-01-22.',
);
}
return parsed;
}
/// Whether GitHub sign-in has ended on [pod] ([gitHubSignInEnds]).
bool gitHubSignInEnded(Serverpod pod) => switch (gitHubSignInEnds(pod)) {
final ends? => !clock.now().isBefore(ends),
null => false,
};
/// Puts [ends] in place of the operator's `GITHUB_SIGN_IN_ENDS` on [pod], or
/// with null theirs back: tests do.
void useGitHubSignInEnds(Serverpod pod, DateTime? ends) => _endsOf[pod] = ends;
final _endsOf = Expando<DateTime>('GitHub sign-in ends');
/// How long after GitHub sign-in ended ([gitHubSignInEnds]) the accounts
/// that signed in with it alone and every GitHub ID are kept before they
/// are deleted ([forgetGitHubAccounts]): a date the operator set wrong can
/// be put right meanwhile, before anything is lost. The app's texts name it
/// (halkyon_app's `passwordSectionGitHubEndsDeletes`): change them with it.
const gitHubAccountsKeptAfterEnd = Duration(days: 30);
/// Deletes every GitHub ID the instance keeps once GitHub sign-in has ended
/// ([gitHubSignInEnds]) and [gitHubAccountsKeptAfterEnd] passed at [now],
/// and returns how many: nobody signs in with them any more, so nothing is
/// kept of them. Daily, with the pruning; on the register, which keeps them
/// (ADR 0016): a member does nothing (the reading of the fixes' D1), as it
/// keeps no addresses, so everyone there would look as if GitHub were their
/// only way in.
///
/// First the accounts that sign in with GitHub alone (no address, so never
/// a password) go, with everything they have, every member told
/// (`deletePerson`): no way in is left to them, and an account nobody can
/// enter would stay for good, or be claimed by whoever holds a session of
/// it (the full reading's M1 and L6). The app tells them for the three
/// months before (ADR 0017, D2). One whose deletion fails holds back nobody
/// else and keeps its GitHub ID, so the next day's run tries it again; the
/// members are told once, after all of them (the reading of the fixes' D2).
Future<int> forgetGitHubAccounts(
Session session, {
required DateTime now,
}) async {
final pod = session.serverpod;
final ends = gitHubSignInEnds(pod);
if (isMember(pod) ||
ends == null ||
now.isBefore(ends.add(gitHubAccountsKeptAfterEnd))) {
return 0;
}
// SELECT * FROM "serverpod_auth_idp_github_account"
final gitHubAccounts = await GitHubAccount.db.find(session);
final gitHubPeople = {
for (final account in gitHubAccounts) account.authUserId,
};
final failed = <UuidValue>{};
if (gitHubPeople.isNotEmpty) {
// SELECT * FROM "email_identity" WHERE "authUserId" = ANY($1)
final withAddress = {
for (final identity in await EmailIdentity.db.find(
session,
where: (t) => t.authUserId.inSet(gitHubPeople),
))
identity.authUserId,
};
final gitHubOnly = gitHubPeople.difference(withAddress);
for (final person in gitHubOnly) {
try {
await deletePerson(session, person, tellMembers: false);
} on Object catch (error) {
// A step after its transaction may have failed, the account gone
// with its GitHub ID all the same (the short reading): only one
// still here is tried again.
// SELECT … FROM "serverpod_auth_core_user" WHERE "id" = $1
final gone = await AuthUser.db.findById(session, person) == null;
// Its type only: whose stays out of the log.
session.log(
gone
? 'Deleted an account that signed in with GitHub alone, but a '
'step after failed: ${error.runtimeType}.'
: 'Deleting an account that signed in with GitHub alone '
'failed: ${error.runtimeType}; tried again tomorrow.',
level: LogLevel.warning,
);
if (!gone) {
failed.add(person);
}
}
}
session.log(
'Deleted ${gitHubOnly.length - failed.length} accounts that signed in '
'with GitHub alone, at its end.',
);
await deliverDeletionNotices(session);
}
// DELETE FROM "serverpod_auth_idp_github_account"
// WHERE "authUserId" <> ALL($1)
await GitHubAccount.db.deleteWhere(
session,
where: (t) =>
failed.isEmpty ? t.id.notEquals(null) : t.authUserId.notInSet(failed),
);
// Those deleted with their accounts counted too.
return gitHubAccounts
.where((account) => !failed.contains(account.authUserId))
.length;
}
/// The numeric GitHub ID that [code], from the app's GitHub sign-in, proves.
/// Refused once GitHub sign-in has ended ([GitHubSignInRefusal.ended]).
///
/// The code is exchanged here, with the client secret, which never leaves the
/// server. GitHub's answer also carries the name and possibly an email
/// address; neither is kept, and the access token is used once and dropped.
Future<String> verifiedGitHubUserId(
Session session, {
required String code,
required String codeVerifier,
required String redirectUri,
}) async {
if (gitHubSignInEnded(session.serverpod)) {
throw GitHubSignInRefusedException(reason: GitHubSignInRefusal.ended);
}
if (!gitHubSignInAvailable(session.serverpod)) {
throw GitHubSignInRefusedException(
reason: GitHubSignInRefusal.unavailable,
);
}
final utils = AuthServices.instance.githubIdp.utils;
final accessToken = await utils.exchangeCodeForToken(
session,
code: code,
codeVerifier: codeVerifier,
redirectUri: redirectUri,
);
final details = await utils.fetchAccountDetails(
session,
accessToken: accessToken,
);
return details.userIdentifier;
}
/// The person who has the GitHub account [gitHubUserId]; null for nobody.
/// A session for them is the caller's to issue (`issueSession`), when it
/// switches to them on another device.
///
/// Nobody is created here: no account gets GitHub any more (ADR 0017, D2).
Future<UuidValue?> accountWithGitHub(
Session session, {
required String gitHubUserId,
}) async {
// SELECT * FROM "serverpod_auth_idp_github_account" WHERE "userIdentifier" = $1
final account = await GitHubAccount.db.findFirstRow(
session,
where: (t) => t.userIdentifier.equals(gitHubUserId),
);
return account?.authUserId;
}

View file

@ -1,55 +0,0 @@
import 'package:halkyon_base_server/src/auth/github.dart';
import 'package:halkyon_base_server/src/generated/protocol.dart';
import 'package:halkyon_base_server/src/register/account_register.dart';
import 'package:halkyon_base_server/src/register/people.dart';
import 'package:serverpod/serverpod.dart';
import 'package:serverpod_auth_idp_server/core.dart';
/// Signs in the person who already has a GitHub account, e.g. when they switch
/// to it on another device. Called without a session: Serverpod issues no
/// session for another person inside a signed-in call.
class GitHubSignInEndpoint extends Endpoint {
/// A session for the person with the GitHub account that [code] proves;
/// refusals as [gitHubAccountToSignIn]'s.
Future<AuthSuccess> signIn(
Session session, {
required String code,
required String codeVerifier,
required String redirectUri,
}) async {
final gitHubUserId = await verifiedGitHubUserId(
session,
code: code,
codeVerifier: codeVerifier,
redirectUri: redirectUri,
);
final account = await gitHubAccountToSignIn(session, gitHubUserId);
return await issueSession(session, account, method: gitHubMethod);
}
}
/// The account the GitHub account [gitHubUserId] signs into, as the
/// instance's register knows it; an account it doesn't know is refused
/// ([GitHubSignInRefusal.unknownAccount]), and so is one that signs in with
/// a password ([GitHubSignInRefusal.usePassword], ADR 0017: it had a
/// confirmed authenticator, `SignInWays.passwordOnly`). GitHub is
/// deprecated: a person signed in so without a password sets an address, a
/// password and an authenticator next, as the app reads from their ways in
/// (`account.signInWays`).
Future<UuidValue> gitHubAccountToSignIn(
Session session,
String gitHubUserId,
) async {
final register = AccountRegister.of(session.serverpod);
final account =
await register.accountWithGitHub(session, gitHubUserId) ??
(throw GitHubSignInRefusedException(
reason: GitHubSignInRefusal.unknownAccount,
));
if ((await register.signInWaysOf(session, account)).passwordOnly) {
throw GitHubSignInRefusedException(
reason: GitHubSignInRefusal.usePassword,
);
}
return account;
}

View file

@ -1,22 +0,0 @@
### Why signing in with GitHub, or linking a GitHub account, was refused.
enum: GitHubSignInRefusal
serialized: byName
values:
### This instance has no GitHub OAuth app (no githubClientId and
### githubClientSecret in its passwords).
- unavailable
### The GitHub account already belongs to another person.
- takenByAnotherPerson
### The person already has a different GitHub account.
- otherGitHubAccountLinked
### No person has this GitHub account; only linking one creates it.
- unknownAccount
### The account signs in with its password: GitHub is deprecated (ADR 0017),
### and can't unlock the account's keys.
- usePassword
### GitHub accounts are no longer linked (ADR 0017): an address and a
### password are the way in.
- deprecated
### GitHub signs nobody in any more here: its time ended
### (`GITHUB_SIGN_IN_ENDS`).
- ended

View file

@ -1,6 +0,0 @@
### Signing in with GitHub, or linking a GitHub account, was refused; nothing
### changed.
exception: GitHubSignInRefusedException
fields:
### Why it was refused.
reason: GitHubSignInRefusal

View file

@ -1,7 +1,6 @@
### The ways a person signs in, besides their sessions.
class: SignInWays
fields:
gitHub: bool
email: bool
### SimpleLogin (ADR 0020): the instance's register keeps its ID for the
### instance's SimpleLogin app.
@ -18,8 +17,8 @@ fields:
### A confirmed authenticator: the password signs in only with one.
authenticator: bool, default=false
### The account signs in with its password alone: it had a confirmed
### authenticator (a reset may have left it without one since), so
### neither a code by mail nor GitHub signs into it (ADR 0017).
### authenticator (a reset may have left it without one since), so a
### code by mail no longer signs into it (ADR 0017).
passwordOnly: bool, default=false
### A second factor works: a confirmed authenticator, and no reset of the
### authenticators that took effect. Where none does, deleting the account

View file

@ -1,70 +0,0 @@
/* 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;
/// Why signing in with GitHub, or linking a GitHub account, was refused.
enum GitHubSignInRefusal implements _is.SerializableModel {
/// This instance has no GitHub OAuth app (no githubClientId and
/// githubClientSecret in its passwords).
unavailable,
/// The GitHub account already belongs to another person.
takenByAnotherPerson,
/// The person already has a different GitHub account.
otherGitHubAccountLinked,
/// No person has this GitHub account; only linking one creates it.
unknownAccount,
/// The account signs in with its password: GitHub is deprecated (ADR 0017),
/// and can't unlock the account's keys.
usePassword,
/// GitHub accounts are no longer linked (ADR 0017): an address and a
/// password are the way in.
deprecated,
/// GitHub signs nobody in any more here: its time ended
/// (`GITHUB_SIGN_IN_ENDS`).
ended;
static GitHubSignInRefusal fromJson(String name) {
switch (name) {
case 'unavailable':
return GitHubSignInRefusal.unavailable;
case 'takenByAnotherPerson':
return GitHubSignInRefusal.takenByAnotherPerson;
case 'otherGitHubAccountLinked':
return GitHubSignInRefusal.otherGitHubAccountLinked;
case 'unknownAccount':
return GitHubSignInRefusal.unknownAccount;
case 'usePassword':
return GitHubSignInRefusal.usePassword;
case 'deprecated':
return GitHubSignInRefusal.deprecated;
case 'ended':
return GitHubSignInRefusal.ended;
default:
throw ArgumentError(
'Value "$name" cannot be converted to "GitHubSignInRefusal"',
);
}
}
@override
String toJson() => name;
@override
String toString() => name;
}

View file

@ -1,84 +0,0 @@
/* 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;
import '../auth/github_sign_in_refusal.dart' as _irgbwy1y;
/// Signing in with GitHub, or linking a GitHub account, was refused; nothing
/// changed.
abstract class GitHubSignInRefusedException
implements
_is.SerializableException,
_is.SerializableModel,
_is.ProtocolSerialization {
GitHubSignInRefusedException._({required this.reason});
factory GitHubSignInRefusedException({
required _irgbwy1y.GitHubSignInRefusal reason,
}) = _GitHubSignInRefusedExceptionImpl;
factory GitHubSignInRefusedException.fromJson(
Map<String, dynamic> jsonSerialization,
) {
return GitHubSignInRefusedException(
reason: _irgbwy1y.GitHubSignInRefusal.fromJson(
(jsonSerialization['reason'] as String),
),
);
}
/// Why it was refused.
_irgbwy1y.GitHubSignInRefusal reason;
/// Returns a shallow copy of this [GitHubSignInRefusedException]
/// with some or all fields replaced by the given arguments.
@_is.useResult
GitHubSignInRefusedException copyWith({
_irgbwy1y.GitHubSignInRefusal? reason,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.GitHubSignInRefusedException',
'reason': reason.toJson(),
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'halkyon_base.GitHubSignInRefusedException',
'reason': reason.toJson(),
};
}
@override
String toString() {
return 'GitHubSignInRefusedException(reason: $reason)';
}
}
class _GitHubSignInRefusedExceptionImpl extends GitHubSignInRefusedException {
_GitHubSignInRefusedExceptionImpl({
required _irgbwy1y.GitHubSignInRefusal reason,
}) : super._(reason: reason);
/// Returns a shallow copy of this [GitHubSignInRefusedException]
/// with some or all fields replaced by the given arguments.
@_is.useResult
@override
GitHubSignInRefusedException copyWith({
_irgbwy1y.GitHubSignInRefusal? reason,
}) {
return GitHubSignInRefusedException(reason: reason ?? this.reason);
}
}

View file

@ -16,7 +16,6 @@ import 'package:serverpod/serverpod.dart' as _is;
abstract class SignInWays
implements _is.SerializableModel, _is.ProtocolSerialization {
SignInWays._({
required this.gitHub,
required this.email,
bool? simpleLogin,
bool? password,
@ -37,7 +36,6 @@ abstract class SignInWays
secondFactorWorks = secondFactorWorks ?? false;
factory SignInWays({
required bool gitHub,
required bool email,
bool? simpleLogin,
bool? password,
@ -53,7 +51,6 @@ abstract class SignInWays
factory SignInWays.fromJson(Map<String, dynamic> jsonSerialization) {
return SignInWays(
gitHub: _is.BoolJsonExtension.fromJson(jsonSerialization['gitHub']),
email: _is.BoolJsonExtension.fromJson(jsonSerialization['email']),
simpleLogin: jsonSerialization['simpleLogin'] == null
? null
@ -97,8 +94,6 @@ abstract class SignInWays
);
}
bool gitHub;
bool email;
/// SimpleLogin (ADR 0020): the instance's register keeps its ID for the
@ -121,8 +116,8 @@ abstract class SignInWays
bool authenticator;
/// The account signs in with its password alone: it had a confirmed
/// authenticator (a reset may have left it without one since), so
/// neither a code by mail nor GitHub signs into it (ADR 0017).
/// authenticator (a reset may have left it without one since), so a
/// code by mail no longer signs into it (ADR 0017).
bool passwordOnly;
/// A second factor works: a confirmed authenticator, and no reset of the
@ -147,7 +142,6 @@ abstract class SignInWays
/// with some or all fields replaced by the given arguments.
@_is.useResult
SignInWays copyWith({
bool? gitHub,
bool? email,
bool? simpleLogin,
bool? password,
@ -164,7 +158,6 @@ abstract class SignInWays
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.SignInWays',
'gitHub': gitHub,
'email': email,
'simpleLogin': simpleLogin,
'password': password,
@ -185,7 +178,6 @@ abstract class SignInWays
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'halkyon_base.SignInWays',
'gitHub': gitHub,
'email': email,
'simpleLogin': simpleLogin,
'password': password,
@ -212,7 +204,6 @@ class _Undefined {}
class _SignInWaysImpl extends SignInWays {
_SignInWaysImpl({
required bool gitHub,
required bool email,
bool? simpleLogin,
bool? password,
@ -225,7 +216,6 @@ class _SignInWaysImpl extends SignInWays {
DateTime? authenticatorResetAt,
DateTime? accountResetAt,
}) : super._(
gitHub: gitHub,
email: email,
simpleLogin: simpleLogin,
password: password,
@ -244,7 +234,6 @@ class _SignInWaysImpl extends SignInWays {
@_is.useResult
@override
SignInWays copyWith({
bool? gitHub,
bool? email,
bool? simpleLogin,
bool? password,
@ -258,7 +247,6 @@ class _SignInWaysImpl extends SignInWays {
Object? accountResetAt = _Undefined,
}) {
return SignInWays(
gitHub: gitHub ?? this.gitHub,
email: email ?? this.email,
simpleLogin: simpleLogin ?? this.simpleLogin,
password: password ?? this.password,

View file

@ -41,7 +41,6 @@ import 'package:serverpod_auth_idp_server/serverpod_auth_idp_server.dart'
as _iais;
import '../auth/account_endpoint.dart' as _ii6ycf6v;
import '../auth/email_code_endpoint.dart' as _in3cimlf;
import '../auth/github_sign_in_endpoint.dart' as _iasw79m5;
import '../instance/instance_endpoint.dart' as _i3g8bfc2;
import '../keys/keys_endpoint.dart' as _icowoccn;
import '../password/account_recovery_endpoint.dart' as _ivnbjhab;
@ -67,12 +66,6 @@ class Endpoints extends _is.EndpointDispatch {
'emailCode',
'halkyon_base',
),
'gitHubSignIn': _iasw79m5.GitHubSignInEndpoint()
..initialize(
server,
'gitHubSignIn',
'halkyon_base',
),
'instance': _i3g8bfc2.InstanceEndpoint()
..initialize(
server,
@ -206,44 +199,6 @@ class Endpoints extends _is.EndpointDispatch {
),
},
);
connectors['gitHubSignIn'] = _is.EndpointConnector(
name: 'gitHubSignIn',
endpoint: endpoints['gitHubSignIn']!,
methodConnectors: {
'signIn': _is.MethodConnector(
name: 'signIn',
params: {
'code': _is.ParameterDescription(
name: 'code',
type: _is.getType<String>(),
nullable: false,
),
'codeVerifier': _is.ParameterDescription(
name: 'codeVerifier',
type: _is.getType<String>(),
nullable: false,
),
'redirectUri': _is.ParameterDescription(
name: 'redirectUri',
type: _is.getType<String>(),
nullable: false,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['gitHubSignIn'] as _iasw79m5.GitHubSignInEndpoint)
.signIn(
session,
code: params['code'],
codeVerifier: params['codeVerifier'],
redirectUri: params['redirectUri'],
),
),
},
);
connectors['instance'] = _is.EndpointConnector(
name: 'instance',
endpoint: endpoints['instance']!,

View file

@ -20,7 +20,6 @@ abstract class InstanceBasics
implements _is.SerializableModel, _is.ProtocolSerialization {
InstanceBasics._({
required this.app,
this.gitHubClientId,
required this.emailSignIn,
required this.sourceCodeUrl,
required this.minimumAppBuild,
@ -28,13 +27,11 @@ abstract class InstanceBasics
this.backupLocation,
bool? passwordSignIn,
bool? simpleLogin,
this.gitHubSignInEnds,
}) : passwordSignIn = passwordSignIn ?? false,
simpleLogin = simpleLogin ?? false;
factory InstanceBasics({
required String app,
String? gitHubClientId,
required bool emailSignIn,
required String sourceCodeUrl,
required int minimumAppBuild,
@ -42,13 +39,11 @@ abstract class InstanceBasics
String? backupLocation,
bool? passwordSignIn,
bool? simpleLogin,
DateTime? gitHubSignInEnds,
}) = _InstanceBasicsImpl;
factory InstanceBasics.fromJson(Map<String, dynamic> jsonSerialization) {
return InstanceBasics(
app: jsonSerialization['app'] as String,
gitHubClientId: jsonSerialization['gitHubClientId'] as String?,
emailSignIn: _is.BoolJsonExtension.fromJson(
jsonSerialization['emailSignIn'],
),
@ -62,11 +57,6 @@ abstract class InstanceBasics
simpleLogin: jsonSerialization['simpleLogin'] == null
? null
: _is.BoolJsonExtension.fromJson(jsonSerialization['simpleLogin']),
gitHubSignInEnds: jsonSerialization['gitHubSignInEnds'] == null
? null
: _is.DateTimeJsonExtension.fromJson(
jsonSerialization['gitHubSignInEnds'],
),
);
}
@ -74,10 +64,6 @@ abstract class InstanceBasics
/// configures it: an app accepts only an instance of its own.
String app;
/// The client ID of this instance's GitHub OAuth app, or null when GitHub
/// sign-in is off here.
String? gitHubClientId;
/// Whether people can sign in with an email address here: a code mailed
/// to it.
bool emailSignIn;
@ -108,18 +94,11 @@ abstract class InstanceBasics
/// instance's register has a SimpleLogin app, and its operator offers it.
bool simpleLogin;
/// When GitHub sign-in ends here (ADR 0017: it is deprecated), as its
/// operator set it (GITHUB_SIGN_IN_ENDS), in UTC; null while no end is set,
/// or once it has ended. People who sign in with GitHub add an address, a
/// password and an authenticator before then.
DateTime? gitHubSignInEnds;
/// Returns a shallow copy of this [InstanceBasics]
/// with some or all fields replaced by the given arguments.
@_is.useResult
InstanceBasics copyWith({
String? app,
String? gitHubClientId,
bool? emailSignIn,
String? sourceCodeUrl,
int? minimumAppBuild,
@ -127,14 +106,12 @@ abstract class InstanceBasics
String? backupLocation,
bool? passwordSignIn,
bool? simpleLogin,
DateTime? gitHubSignInEnds,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.InstanceBasics',
'app': app,
if (gitHubClientId != null) 'gitHubClientId': gitHubClientId,
'emailSignIn': emailSignIn,
'sourceCodeUrl': sourceCodeUrl,
'minimumAppBuild': minimumAppBuild,
@ -142,8 +119,6 @@ abstract class InstanceBasics
if (backupLocation != null) 'backupLocation': backupLocation,
'passwordSignIn': passwordSignIn,
'simpleLogin': simpleLogin,
if (gitHubSignInEnds != null)
'gitHubSignInEnds': gitHubSignInEnds?.toJson(),
};
}
@ -152,7 +127,6 @@ abstract class InstanceBasics
return {
'__className__': 'halkyon_base.InstanceBasics',
'app': app,
if (gitHubClientId != null) 'gitHubClientId': gitHubClientId,
'emailSignIn': emailSignIn,
'sourceCodeUrl': sourceCodeUrl,
'minimumAppBuild': minimumAppBuild,
@ -160,8 +134,6 @@ abstract class InstanceBasics
if (backupLocation != null) 'backupLocation': backupLocation,
'passwordSignIn': passwordSignIn,
'simpleLogin': simpleLogin,
if (gitHubSignInEnds != null)
'gitHubSignInEnds': gitHubSignInEnds?.toJson(),
};
}
@ -176,7 +148,6 @@ class _Undefined {}
class _InstanceBasicsImpl extends InstanceBasics {
_InstanceBasicsImpl({
required String app,
String? gitHubClientId,
required bool emailSignIn,
required String sourceCodeUrl,
required int minimumAppBuild,
@ -184,10 +155,8 @@ class _InstanceBasicsImpl extends InstanceBasics {
String? backupLocation,
bool? passwordSignIn,
bool? simpleLogin,
DateTime? gitHubSignInEnds,
}) : super._(
app: app,
gitHubClientId: gitHubClientId,
emailSignIn: emailSignIn,
sourceCodeUrl: sourceCodeUrl,
minimumAppBuild: minimumAppBuild,
@ -195,7 +164,6 @@ class _InstanceBasicsImpl extends InstanceBasics {
backupLocation: backupLocation,
passwordSignIn: passwordSignIn,
simpleLogin: simpleLogin,
gitHubSignInEnds: gitHubSignInEnds,
);
/// Returns a shallow copy of this [InstanceBasics]
@ -204,7 +172,6 @@ class _InstanceBasicsImpl extends InstanceBasics {
@override
InstanceBasics copyWith({
String? app,
Object? gitHubClientId = _Undefined,
bool? emailSignIn,
String? sourceCodeUrl,
int? minimumAppBuild,
@ -212,13 +179,9 @@ class _InstanceBasicsImpl extends InstanceBasics {
Object? backupLocation = _Undefined,
bool? passwordSignIn,
bool? simpleLogin,
Object? gitHubSignInEnds = _Undefined,
}) {
return InstanceBasics(
app: app ?? this.app,
gitHubClientId: gitHubClientId is String?
? gitHubClientId
: this.gitHubClientId,
emailSignIn: emailSignIn ?? this.emailSignIn,
sourceCodeUrl: sourceCodeUrl ?? this.sourceCodeUrl,
minimumAppBuild: minimumAppBuild ?? this.minimumAppBuild,
@ -228,9 +191,6 @@ class _InstanceBasicsImpl extends InstanceBasics {
: this.backupLocation,
passwordSignIn: passwordSignIn ?? this.passwordSignIn,
simpleLogin: simpleLogin ?? this.simpleLogin,
gitHubSignInEnds: gitHubSignInEnds is DateTime?
? gitHubSignInEnds
: this.gitHubSignInEnds,
);
}
}

View file

@ -41,10 +41,6 @@ enum PasswordRefusal implements _is.SerializableModel {
/// The account has a password already.
passwordExists,
/// The account has no email address yet: a password needs one, for
/// resets. A code mailed to one makes it the account's.
noAddress,
/// The address the code proved is another account's.
addressTaken,
@ -84,11 +80,6 @@ enum PasswordRefusal implements _is.SerializableModel {
/// sign in again.
sessionEnded,
/// A first password for an account that signs in with GitHub alone takes
/// a GitHub sign-in of the last 10 minutes (the fourth reading: a session
/// alone sets none): sign in with GitHub again, then set it.
signInAgain,
/// The warning that a session of the account was ended for its wrong
/// proofs is dismissed only by a session made since (the full reading's
/// L1: another session of a stolen device would dismiss its only use):
@ -111,8 +102,6 @@ enum PasswordRefusal implements _is.SerializableModel {
return PasswordRefusal.invalidSetup;
case 'passwordExists':
return PasswordRefusal.passwordExists;
case 'noAddress':
return PasswordRefusal.noAddress;
case 'addressTaken':
return PasswordRefusal.addressTaken;
case 'noPassword':
@ -135,8 +124,6 @@ enum PasswordRefusal implements _is.SerializableModel {
return PasswordRefusal.codesWait;
case 'sessionEnded':
return PasswordRefusal.sessionEnded;
case 'signInAgain':
return PasswordRefusal.signInAgain;
case 'signedInBefore':
return PasswordRefusal.signedInBefore;
default:

View file

@ -25,8 +25,6 @@ import 'auth/email_code_request.dart' as _iqxum1j8;
import 'auth/email_identity.dart' as _id1dpd4g;
import 'auth/email_sign_in_refusal.dart' as _ieyvcag1;
import 'auth/email_sign_in_refused_exception.dart' as _iztfgag5;
import 'auth/github_sign_in_refusal.dart' as _ir1gd9p5;
import 'auth/github_sign_in_refused_exception.dart' as _i0xfcu5n;
import 'auth/sign_in_ways.dart' as _ie6tbh85;
import 'instance/instance_basics.dart' as _i8jixqx6;
import 'keys/device_keys.dart' as _iz50cwqw;
@ -76,8 +74,6 @@ export 'auth/email_code_request.dart';
export 'auth/email_identity.dart';
export 'auth/email_sign_in_refusal.dart';
export 'auth/email_sign_in_refused_exception.dart';
export 'auth/github_sign_in_refusal.dart';
export 'auth/github_sign_in_refused_exception.dart';
export 'auth/sign_in_ways.dart';
export 'instance/instance_basics.dart';
export 'keys/device_keys.dart';
@ -1087,12 +1083,6 @@ class Protocol extends _is.DatabaseSerializationManager {
if (t == _iztfgag5.EmailSignInRefusedException) {
return _iztfgag5.EmailSignInRefusedException.fromJson(data) as T;
}
if (t == _ir1gd9p5.GitHubSignInRefusal) {
return _ir1gd9p5.GitHubSignInRefusal.fromJson(data) as T;
}
if (t == _i0xfcu5n.GitHubSignInRefusedException) {
return _i0xfcu5n.GitHubSignInRefusedException.fromJson(data) as T;
}
if (t == _ie6tbh85.SignInWays) {
return _ie6tbh85.SignInWays.fromJson(data) as T;
}
@ -1247,18 +1237,6 @@ class Protocol extends _is.DatabaseSerializationManager {
: null)
as T;
}
if (t == _is.getType<_ir1gd9p5.GitHubSignInRefusal?>()) {
return (data != null
? _ir1gd9p5.GitHubSignInRefusal.fromJson(data)
: null)
as T;
}
if (t == _is.getType<_i0xfcu5n.GitHubSignInRefusedException?>()) {
return (data != null
? _i0xfcu5n.GitHubSignInRefusedException.fromJson(data)
: null)
as T;
}
if (t == _is.getType<_ie6tbh85.SignInWays?>()) {
return (data != null ? _ie6tbh85.SignInWays.fromJson(data) : null) as T;
}
@ -1486,8 +1464,6 @@ class Protocol extends _is.DatabaseSerializationManager {
_id1dpd4g.EmailIdentity => 'EmailIdentity',
_ieyvcag1.EmailSignInRefusal => 'EmailSignInRefusal',
_iztfgag5.EmailSignInRefusedException => 'EmailSignInRefusedException',
_ir1gd9p5.GitHubSignInRefusal => 'GitHubSignInRefusal',
_i0xfcu5n.GitHubSignInRefusedException => 'GitHubSignInRefusedException',
_ie6tbh85.SignInWays => 'SignInWays',
_i8jixqx6.InstanceBasics => 'InstanceBasics',
_iz50cwqw.DeviceKeys => 'DeviceKeys',
@ -1559,10 +1535,6 @@ class Protocol extends _is.DatabaseSerializationManager {
return 'EmailSignInRefusal';
case _iztfgag5.EmailSignInRefusedException():
return 'EmailSignInRefusedException';
case _ir1gd9p5.GitHubSignInRefusal():
return 'GitHubSignInRefusal';
case _i0xfcu5n.GitHubSignInRefusedException():
return 'GitHubSignInRefusedException';
case _ie6tbh85.SignInWays():
return 'SignInWays';
case _i8jixqx6.InstanceBasics():
@ -1680,12 +1652,6 @@ class Protocol extends _is.DatabaseSerializationManager {
if (dataClassName == 'EmailSignInRefusedException') {
return deserialize<_iztfgag5.EmailSignInRefusedException>(data['data']);
}
if (dataClassName == 'GitHubSignInRefusal') {
return deserialize<_ir1gd9p5.GitHubSignInRefusal>(data['data']);
}
if (dataClassName == 'GitHubSignInRefusedException') {
return deserialize<_i0xfcu5n.GitHubSignInRefusedException>(data['data']);
}
if (dataClassName == 'SignInWays') {
return deserialize<_ie6tbh85.SignInWays>(data['data']);
}

View file

@ -3,8 +3,6 @@ account:
- signInWays:
emailCode:
- sendCode:
gitHubSignIn:
- signIn:
instance:
- basics:
keys:

View file

@ -7,9 +7,6 @@ fields:
### Which app this instance serves (`learn` for Halkyon Learn), as its host
### configures it: an app accepts only an instance of its own.
app: String
### The client ID of this instance's GitHub OAuth app, or null when GitHub
### sign-in is off here.
gitHubClientId: String?
### Whether people can sign in with an email address here: a code mailed
### to it.
emailSignIn: bool
@ -33,8 +30,3 @@ fields:
### Whether people sign in with SimpleLogin here (ADR 0020): the
### instance's register has a SimpleLogin app, and its operator offers it.
simpleLogin: bool, default=false
### When GitHub sign-in ends here (ADR 0017: it is deprecated), as its
### operator set it (GITHUB_SIGN_IN_ENDS), in UTC; null while no end is set,
### or once it has ended. People who sign in with GitHub add an address, a
### password and an authenticator before then.
gitHubSignInEnds: DateTime?

View file

@ -1,5 +1,4 @@
import 'package:halkyon_base_server/src/auth/email.dart';
import 'package:halkyon_base_server/src/auth/github.dart';
import 'package:halkyon_base_server/src/auth/membership.dart';
import 'package:halkyon_base_server/src/generated/protocol.dart';
import 'package:halkyon_base_server/src/halkyon_base.dart';
@ -14,22 +13,18 @@ import 'package:serverpod/serverpod.dart';
/// What every Halkyon instance tells its apps. Public: an app asks before
/// anyone is signed in.
class InstanceEndpoint extends Endpoint {
/// This instance's basics: the app it serves, GitHub sign-in only with the
/// operator's OAuth app and until it ends (ADR 0017), email sign-in only
/// where the register can mail codes (this server, or on a member the
/// register, ADR 0016), password sign-in where the register holds its
/// keys, SimpleLogin where the register has the instance's SimpleLogin app
/// (ADR 0020), each as its operator offers it, when GitHub sign-in ends,
/// where its source is, the oldest app build it works with, and where it
/// keeps people's data and its backups.
/// This instance's basics: the app it serves, email sign-in only where
/// the register can mail codes (this server, or on a member the register,
/// ADR 0016), password sign-in where the register holds its keys,
/// SimpleLogin where the register has the instance's SimpleLogin app
/// (ADR 0020), each as its operator offers it, where its source is, the
/// oldest app build it works with, and where it keeps people's data and
/// its backups.
Future<InstanceBasics> basics(Session session) async {
final pod = session.serverpod;
final base = HalkyonBase.of(pod);
return InstanceBasics(
app: base.app,
gitHubClientId: gitHubSignInAvailable(pod) && !gitHubSignInEnded(pod)
? pod.getPassword('githubClientId')
: null,
emailSignIn: isMember(pod)
? await _registerOffers(session, (register) {
return register.emailSignInOffered(session);
@ -49,7 +44,6 @@ class InstanceEndpoint extends Endpoint {
minimumAppBuild: base.minimumAppBuild,
dataLocation: dataLocation(),
backupLocation: backupLocation(),
gitHubSignInEnds: gitHubSignInEnded(pod) ? null : gitHubSignInEnds(pod),
);
}
}

Some files were not shown because too many files have changed in this diff Show more