fix(auth): a code by mail serves only what it was asked for
Codes by mail were tied to nothing but their address (the re-review's
seventh finding): a hostile instance could answer that a setup never
finished, have the person type a code into it, relay it to start the real
instance's account reset, and 72 hours later relay a second to finish it
with its own password and keys; the mail said "to sign in" whatever the
code was for. A code is now asked for a purpose (signUp, finishSetup,
authenticatorReset, accountReset, deletion), only that purpose takes it (any
other is refused as no longer valid, leaving the code as it was), and its
mail says what it would do ("to reset your account on learn.halkyon.eu: a
new password and new keys, and what was encrypted with the old ones is
lost") and to type it only in the app on that instance, nowhere else. The
app asks for each code with the purpose of its step.
Migration (module, Learn's and Todo's servers, Learn's device schema):
ALTER TABLE "email_code_request" ADD COLUMN "purpose" text; nullable,
so the requests of the last 15 minutes before it stay, taken by nothing
and forgotten within the hour.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
56d7aac9a8
commit
1d4320370b
47 changed files with 42026 additions and 121 deletions
|
|
@ -11,11 +11,13 @@ import 'package:serverpod/serverpod.dart';
|
|||
|
||||
/// Mails sign-in codes, and notices of resets.
|
||||
abstract interface class CodeMailer {
|
||||
/// Mails [code] to [to], worded in [language] (`en` or `de`).
|
||||
/// Mails [code] for [purpose] to [to], worded in [language] (`en` or
|
||||
/// `de`): the mail says what the code would do ([codeMail]).
|
||||
Future<void> send(
|
||||
Session session, {
|
||||
required String to,
|
||||
required String code,
|
||||
required EmailCodePurpose purpose,
|
||||
required String language,
|
||||
});
|
||||
|
||||
|
|
@ -52,8 +54,9 @@ final class LogCodeMailer implements CodeMailer {
|
|||
Session session, {
|
||||
required String to,
|
||||
required String code,
|
||||
required EmailCodePurpose purpose,
|
||||
required String language,
|
||||
}) async => session.log('Email sign-in code for $to: $code');
|
||||
}) async => session.log('Email code (${purpose.name}) for $to: $code');
|
||||
|
||||
@override
|
||||
Future<void> sendResetNotice(
|
||||
|
|
@ -77,10 +80,13 @@ final class LogCodeMailer implements CodeMailer {
|
|||
|
||||
/// A code's mail, worded in [language] (`en`, else German for `de`): its
|
||||
/// subject and its body. [app] is the app's name, which the code is typed
|
||||
/// into, and [instance] the instance's address, which the mail names so
|
||||
/// that people know where they are signing in.
|
||||
/// into, and [instance] the instance's address. The mail says what the code
|
||||
/// would do ([purpose]: the re-review's seventh finding), so that whoever
|
||||
/// is asked for it by another than [app] on [instance] sees what they would
|
||||
/// hand over, and that only there may it be typed.
|
||||
({String subject, String body}) codeMail({
|
||||
required String code,
|
||||
required EmailCodePurpose purpose,
|
||||
required String language,
|
||||
required String app,
|
||||
required String instance,
|
||||
|
|
@ -91,11 +97,26 @@ final class LogCodeMailer implements CodeMailer {
|
|||
body:
|
||||
'$code\n'
|
||||
'\n'
|
||||
'Gib diesen Code in $app ein, um dich auf $instance '
|
||||
'anzumelden. Er gilt 15 Minuten.\n'
|
||||
'Gib diesen Code in $app ein, ${switch (purpose) {
|
||||
EmailCodePurpose.signUp => 'um dein Konto auf $instance anzulegen',
|
||||
EmailCodePurpose.finishSetup =>
|
||||
'um die Einrichtung deines Kontos auf $instance abzuschließen',
|
||||
EmailCodePurpose.authenticatorReset =>
|
||||
'um die Codes deiner Authenticator-App für dein Konto auf '
|
||||
'$instance abzuschalten (72 Stunden später)',
|
||||
EmailCodePurpose.accountReset =>
|
||||
'um dein Konto auf $instance zurückzusetzen: ein neues Passwort '
|
||||
'und neue Schlüssel; was mit den alten verschlüsselt war, ist '
|
||||
'dann verloren',
|
||||
EmailCodePurpose.deletion =>
|
||||
'um dein Konto auf $instance mit allem darin zu löschen',
|
||||
}}. Er gilt 15 Minuten.\n'
|
||||
'\n'
|
||||
'Gib ihn nur in $app auf $instance ein, nirgends sonst, auch wenn '
|
||||
'eine Seite oder App in deren Namen danach fragt.\n'
|
||||
'\n'
|
||||
'Wenn du ihn nicht angefordert hast, ignoriere diese Mail: Ohne den '
|
||||
'Code kann sich niemand mit deiner Adresse anmelden.\n'
|
||||
'Code geschieht nichts.\n'
|
||||
'\n'
|
||||
'Diese Instanz behält nur einen Fingerabdruck deiner Adresse und '
|
||||
'schreibt ihr nur, wenn du einen Code anforderst.\n',
|
||||
|
|
@ -105,11 +126,25 @@ final class LogCodeMailer implements CodeMailer {
|
|||
body:
|
||||
'$code\n'
|
||||
'\n'
|
||||
'Type this code in $app to sign in on $instance. It is valid '
|
||||
'for 15 minutes.\n'
|
||||
'Type this code in $app ${switch (purpose) {
|
||||
EmailCodePurpose.signUp => 'to create your account on $instance',
|
||||
EmailCodePurpose.finishSetup =>
|
||||
'to finish setting up your account on $instance',
|
||||
EmailCodePurpose.authenticatorReset =>
|
||||
"to switch off your authenticator app's codes for your account "
|
||||
'on $instance (72 hours later)',
|
||||
EmailCodePurpose.accountReset =>
|
||||
'to reset your account on $instance: a new password and new '
|
||||
'keys, and what was encrypted with the old ones is lost',
|
||||
EmailCodePurpose.deletion =>
|
||||
'to delete your account on $instance with everything in it',
|
||||
}}. It is valid for 15 minutes.\n'
|
||||
'\n'
|
||||
"If you didn't ask for it, ignore this mail: without the code, nobody "
|
||||
'can sign in with your address.\n'
|
||||
'Type it only in $app on $instance, nowhere else, even if a page or '
|
||||
'an app asks for it in their name.\n'
|
||||
'\n'
|
||||
"If you didn't ask for it, ignore this mail: without the code, "
|
||||
'nothing happens.\n'
|
||||
'\n'
|
||||
'This instance keeps only a fingerprint of your address and writes to '
|
||||
'it only when you ask for a code.\n',
|
||||
|
|
@ -284,8 +319,9 @@ final class SmtpCodeMailer implements CodeMailer {
|
|||
Session session, {
|
||||
required String to,
|
||||
required String code,
|
||||
required EmailCodePurpose purpose,
|
||||
required String language,
|
||||
}) => mail(to: to, code: code, language: language);
|
||||
}) => mail(to: to, code: code, purpose: purpose, language: language);
|
||||
|
||||
@override
|
||||
Future<void> sendResetNotice(
|
||||
|
|
@ -321,15 +357,22 @@ final class SmtpCodeMailer implements CodeMailer {
|
|||
),
|
||||
);
|
||||
|
||||
/// Mails [code] to [to], worded in [language]; throws what the service
|
||||
/// answered when it took no mail.
|
||||
/// Mails [code] for [purpose] to [to], worded in [language]; throws what
|
||||
/// the service answered when it took no mail.
|
||||
Future<void> mail({
|
||||
required String to,
|
||||
required String code,
|
||||
required EmailCodePurpose purpose,
|
||||
required String language,
|
||||
}) => _deliver(
|
||||
to,
|
||||
codeMail(code: code, language: language, app: _app, instance: _instance),
|
||||
codeMail(
|
||||
code: code,
|
||||
purpose: purpose,
|
||||
language: language,
|
||||
app: _app,
|
||||
instance: _instance,
|
||||
),
|
||||
);
|
||||
|
||||
Future<void> _deliver(
|
||||
|
|
|
|||
|
|
@ -126,7 +126,8 @@ final _codesPerNetwork = DatabaseRateLimiter(
|
|||
),
|
||||
);
|
||||
|
||||
/// Mails a new code to [address] and returns the request it answers, in the
|
||||
/// Mails a new code for [purpose] to [address] (its mail says what the
|
||||
/// code would do, `codeMail`) and returns the request it answers, in the
|
||||
/// same way whether anybody has the address or not. Up to
|
||||
/// [liveCodesPerAddress] codes for the address are valid at once; the
|
||||
/// oldest beyond them stops being valid. Refused for no address, too many
|
||||
|
|
@ -138,6 +139,7 @@ Future<UuidValue> sendSignInCode(
|
|||
Session session, {
|
||||
required String address,
|
||||
required String language,
|
||||
required EmailCodePurpose purpose,
|
||||
String? network,
|
||||
}) async {
|
||||
final pod = session.serverpod;
|
||||
|
|
@ -191,6 +193,7 @@ Future<UuidValue> sendSignInCode(
|
|||
EmailCodeRequest(
|
||||
addressHash: hash,
|
||||
challengeId: challenge.id!,
|
||||
purpose: purpose,
|
||||
createdAt: clock.now(),
|
||||
),
|
||||
transaction: transaction,
|
||||
|
|
@ -200,6 +203,7 @@ Future<UuidValue> sendSignInCode(
|
|||
session,
|
||||
to: to,
|
||||
code: code,
|
||||
purpose: purpose,
|
||||
language: language == 'de' ? 'de' : 'en',
|
||||
);
|
||||
} on Exception catch (error) {
|
||||
|
|
@ -219,19 +223,36 @@ Future<UuidValue> sendSignInCode(
|
|||
/// The keyed hash of the address [code] proves for [request], once: the
|
||||
/// request goes as it is accepted. A code by mail signs nobody in (ADR
|
||||
/// 0017): it proves an address for a password's setup, a sign-up, a setup
|
||||
/// never finished and the resets, whose register compares the hash with
|
||||
/// the account's. Refused for a wrong code, too many tries, or a code no
|
||||
/// longer valid ([EmailSignInRefusedException]).
|
||||
/// never finished, the resets and deletion, whose register compares the
|
||||
/// hash with the account's. Only a code asked for [purpose] proves anything
|
||||
/// (the re-review's seventh finding: a code asked for one thing, typed into
|
||||
/// a hostile instance, would otherwise serve another). Refused for a wrong
|
||||
/// code, too many tries, a code no longer valid, or one asked for something
|
||||
/// else ([EmailSignInRefusal.codeExpired], leaving it as it is)
|
||||
/// ([EmailSignInRefusedException]).
|
||||
Future<String> proveAddressCode(
|
||||
Session session, {
|
||||
required UuidValue request,
|
||||
required String code,
|
||||
required EmailCodePurpose purpose,
|
||||
}) async {
|
||||
final pod = session.serverpod;
|
||||
if (!emailSignInAvailable(pod)) {
|
||||
throw EmailSignInRefusedException(reason: EmailSignInRefusal.unavailable);
|
||||
}
|
||||
return await session.db.transaction((transaction) async {
|
||||
// A code asked for something else does nothing here, and stays as it
|
||||
// is (no try counted): it is no code for this.
|
||||
final asked = await _requestById(
|
||||
session,
|
||||
request,
|
||||
transaction: transaction,
|
||||
);
|
||||
if (asked == null || asked.purpose != purpose) {
|
||||
throw EmailSignInRefusedException(
|
||||
reason: EmailSignInRefusal.codeExpired,
|
||||
);
|
||||
}
|
||||
await _refusingAsEmail(
|
||||
() => _challengesOf(pod).verifyChallenge(
|
||||
session,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:halkyon_base_server/src/auth/email.dart';
|
||||
import 'package:halkyon_base_server/src/common/network.dart';
|
||||
import 'package:halkyon_base_server/src/generated/protocol.dart';
|
||||
import 'package:halkyon_base_server/src/register/account_register.dart';
|
||||
import 'package:serverpod/serverpod.dart';
|
||||
|
||||
|
|
@ -10,16 +11,19 @@ import 'package:serverpod/serverpod.dart';
|
|||
/// hashes (ADR 0016), this server's own database when it holds the
|
||||
/// register.
|
||||
class EmailCodeEndpoint extends Endpoint {
|
||||
/// Mails a code to [address], worded in [language] (`en`, `de`), and
|
||||
/// returns the request it answers; refusals as in [sendSignInCode].
|
||||
/// Mails a code for [purpose] to [address], worded in [language] (`en`,
|
||||
/// `de`), and returns the request it answers; only [purpose] takes it.
|
||||
/// Refusals as in [sendSignInCode].
|
||||
Future<UuidValue> sendCode(
|
||||
Session session, {
|
||||
required String address,
|
||||
required String language,
|
||||
required EmailCodePurpose purpose,
|
||||
}) => AccountRegister.of(session.serverpod).sendSignInCode(
|
||||
session,
|
||||
address: address,
|
||||
language: language,
|
||||
purpose: purpose,
|
||||
network: networkOf(session),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
### What a code by mail is for (ADR 0017; the re-review's seventh finding):
|
||||
### asked for with it, checked wherever the code is used, and named in its
|
||||
### mail, so a code asked for one thing does nothing else, and its owner
|
||||
### reads what it would do before typing it anywhere.
|
||||
enum: EmailCodePurpose
|
||||
serialized: byName
|
||||
values:
|
||||
### Proving an address for a new account, a password set up by someone
|
||||
### signed in, or an account with that address and no password yet.
|
||||
- signUp
|
||||
### Signing in, with the password, to an account whose setup never
|
||||
### finished (no authenticator confirmed yet).
|
||||
- finishSetup
|
||||
### Asking, with the password, for the reset of a lost authenticator.
|
||||
- authenticatorReset
|
||||
### Asking for, or carrying out, the reset of the whole account: new keys.
|
||||
- accountReset
|
||||
### Deleting an account whose setup never finished, with the password.
|
||||
- deletion
|
||||
|
|
@ -12,6 +12,9 @@ fields:
|
|||
addressHash: String
|
||||
challenge: module:serverpod_auth_idp:SecretChallenge?, relation(onDelete=Cascade)
|
||||
switchChallenge: module:serverpod_auth_idp:SecretChallenge?, relation(optional, onDelete=Cascade)
|
||||
### What the code is for, checked wherever it is used; null for requests
|
||||
### from before codes had a purpose, which nothing takes.
|
||||
purpose: EmailCodePurpose?
|
||||
createdAt: DateTime
|
||||
indexes:
|
||||
email_code_request_address_idx:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
/* 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;
|
||||
|
||||
/// What a code by mail is for (ADR 0017; the re-review's seventh finding):
|
||||
/// asked for with it, checked wherever the code is used, and named in its
|
||||
/// mail, so a code asked for one thing does nothing else, and its owner
|
||||
/// reads what it would do before typing it anywhere.
|
||||
enum EmailCodePurpose implements _is.SerializableModel {
|
||||
/// Proving an address for a new account, a password set up by someone
|
||||
/// signed in, or an account with that address and no password yet.
|
||||
signUp,
|
||||
|
||||
/// Signing in, with the password, to an account whose setup never
|
||||
/// finished (no authenticator confirmed yet).
|
||||
finishSetup,
|
||||
|
||||
/// Asking, with the password, for the reset of a lost authenticator.
|
||||
authenticatorReset,
|
||||
|
||||
/// Asking for, or carrying out, the reset of the whole account: new keys.
|
||||
accountReset,
|
||||
|
||||
/// Deleting an account whose setup never finished, with the password.
|
||||
deletion;
|
||||
|
||||
static EmailCodePurpose fromJson(String name) {
|
||||
switch (name) {
|
||||
case 'signUp':
|
||||
return EmailCodePurpose.signUp;
|
||||
case 'finishSetup':
|
||||
return EmailCodePurpose.finishSetup;
|
||||
case 'authenticatorReset':
|
||||
return EmailCodePurpose.authenticatorReset;
|
||||
case 'accountReset':
|
||||
return EmailCodePurpose.accountReset;
|
||||
case 'deletion':
|
||||
return EmailCodePurpose.deletion;
|
||||
default:
|
||||
throw ArgumentError(
|
||||
'Value "$name" cannot be converted to "EmailCodePurpose"',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toJson() => name;
|
||||
|
||||
@override
|
||||
String toString() => name;
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ import 'package:halkyon_base_server/src/generated/protocol.dart' as _ibewtqgw;
|
|||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
import 'package:serverpod_auth_idp_server/serverpod_auth_idp_server.dart'
|
||||
as _iais;
|
||||
import '../auth/email_code_purpose.dart' as _i02c05gv;
|
||||
|
||||
/// A code mailed to an address, waiting to be typed (Halkyon Learn's ADR
|
||||
/// 0010): the address's hash and the code's challenge (Serverpod's
|
||||
|
|
@ -31,6 +32,7 @@ abstract class EmailCodeRequest
|
|||
this.challenge,
|
||||
this.switchChallengeId,
|
||||
this.switchChallenge,
|
||||
this.purpose,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
|
|
@ -41,6 +43,7 @@ abstract class EmailCodeRequest
|
|||
_iais.SecretChallenge? challenge,
|
||||
_is.UuidValue? switchChallengeId,
|
||||
_iais.SecretChallenge? switchChallenge,
|
||||
_i02c05gv.EmailCodePurpose? purpose,
|
||||
required DateTime createdAt,
|
||||
}) = _EmailCodeRequestImpl;
|
||||
|
||||
|
|
@ -68,6 +71,11 @@ abstract class EmailCodeRequest
|
|||
: _ibewtqgw.Protocol().deserialize<_iais.SecretChallenge>(
|
||||
jsonSerialization['switchChallenge'],
|
||||
),
|
||||
purpose: jsonSerialization['purpose'] == null
|
||||
? null
|
||||
: _i02c05gv.EmailCodePurpose.fromJson(
|
||||
(jsonSerialization['purpose'] as String),
|
||||
),
|
||||
createdAt: _is.DateTimeJsonExtension.fromJson(
|
||||
jsonSerialization['createdAt'],
|
||||
),
|
||||
|
|
@ -91,6 +99,10 @@ abstract class EmailCodeRequest
|
|||
|
||||
_iais.SecretChallenge? switchChallenge;
|
||||
|
||||
/// What the code is for, checked wherever it is used; null for requests
|
||||
/// from before codes had a purpose, which nothing takes.
|
||||
_i02c05gv.EmailCodePurpose? purpose;
|
||||
|
||||
DateTime createdAt;
|
||||
|
||||
@override
|
||||
|
|
@ -106,6 +118,7 @@ abstract class EmailCodeRequest
|
|||
_iais.SecretChallenge? challenge,
|
||||
_is.UuidValue? switchChallengeId,
|
||||
_iais.SecretChallenge? switchChallenge,
|
||||
_i02c05gv.EmailCodePurpose? purpose,
|
||||
DateTime? createdAt,
|
||||
});
|
||||
@override
|
||||
|
|
@ -119,6 +132,7 @@ abstract class EmailCodeRequest
|
|||
if (switchChallengeId != null)
|
||||
'switchChallengeId': switchChallengeId?.toJson(),
|
||||
if (switchChallenge != null) 'switchChallenge': switchChallenge?.toJson(),
|
||||
if (purpose != null) 'purpose': purpose?.toJson(),
|
||||
'createdAt': createdAt.toJson(),
|
||||
};
|
||||
}
|
||||
|
|
@ -172,6 +186,7 @@ class _EmailCodeRequestImpl extends EmailCodeRequest {
|
|||
_iais.SecretChallenge? challenge,
|
||||
_is.UuidValue? switchChallengeId,
|
||||
_iais.SecretChallenge? switchChallenge,
|
||||
_i02c05gv.EmailCodePurpose? purpose,
|
||||
required DateTime createdAt,
|
||||
}) : super._(
|
||||
id: id,
|
||||
|
|
@ -180,6 +195,7 @@ class _EmailCodeRequestImpl extends EmailCodeRequest {
|
|||
challenge: challenge,
|
||||
switchChallengeId: switchChallengeId,
|
||||
switchChallenge: switchChallenge,
|
||||
purpose: purpose,
|
||||
createdAt: createdAt,
|
||||
);
|
||||
|
||||
|
|
@ -194,6 +210,7 @@ class _EmailCodeRequestImpl extends EmailCodeRequest {
|
|||
Object? challenge = _Undefined,
|
||||
Object? switchChallengeId = _Undefined,
|
||||
Object? switchChallenge = _Undefined,
|
||||
Object? purpose = _Undefined,
|
||||
DateTime? createdAt,
|
||||
}) {
|
||||
return EmailCodeRequest(
|
||||
|
|
@ -209,6 +226,7 @@ class _EmailCodeRequestImpl extends EmailCodeRequest {
|
|||
switchChallenge: switchChallenge is _iais.SecretChallenge?
|
||||
? switchChallenge
|
||||
: this.switchChallenge?.copyWith(),
|
||||
purpose: purpose is _i02c05gv.EmailCodePurpose? ? purpose : this.purpose,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
);
|
||||
}
|
||||
|
|
@ -237,6 +255,12 @@ class EmailCodeRequestUpdateTable
|
|||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<_i02c05gv.EmailCodePurpose, _i02c05gv.EmailCodePurpose>
|
||||
purpose(_i02c05gv.EmailCodePurpose? value) => _is.ColumnValue(
|
||||
table.purpose,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<DateTime, DateTime> createdAt(DateTime value) =>
|
||||
_is.ColumnValue(
|
||||
table.createdAt,
|
||||
|
|
@ -260,6 +284,11 @@ class EmailCodeRequestTable extends _is.Table<_is.UuidValue?> {
|
|||
'switchChallengeId',
|
||||
this,
|
||||
);
|
||||
purpose = _is.ColumnEnum(
|
||||
'purpose',
|
||||
this,
|
||||
_is.EnumSerialization.byName,
|
||||
);
|
||||
createdAt = _is.ColumnDateTime(
|
||||
'createdAt',
|
||||
this,
|
||||
|
|
@ -278,6 +307,10 @@ class EmailCodeRequestTable extends _is.Table<_is.UuidValue?> {
|
|||
|
||||
_iais.SecretChallengeTable? _switchChallenge;
|
||||
|
||||
/// What the code is for, checked wherever it is used; null for requests
|
||||
/// from before codes had a purpose, which nothing takes.
|
||||
late final _is.ColumnEnum<_i02c05gv.EmailCodePurpose> purpose;
|
||||
|
||||
late final _is.ColumnDateTime createdAt;
|
||||
|
||||
_iais.SecretChallengeTable get challenge {
|
||||
|
|
@ -312,6 +345,7 @@ class EmailCodeRequestTable extends _is.Table<_is.UuidValue?> {
|
|||
addressHash,
|
||||
challengeId,
|
||||
switchChallengeId,
|
||||
purpose,
|
||||
createdAt,
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
|
||||
import '../auth/email_sign_in_refusal.dart' as _in9x16lz;
|
||||
|
||||
/// Mailing a code, accepting it, or signing in with an email address was
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@
|
|||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:typed_data' as _idt;
|
||||
|
||||
import 'package:halkyon_base_server/src/generated/auth/email_code_purpose.dart'
|
||||
as _i5775c0l;
|
||||
import 'package:halkyon_base_server/src/generated/password/deletion_proof.dart'
|
||||
as _i619l6vv;
|
||||
import 'package:halkyon_base_server/src/generated/password/new_password.dart'
|
||||
|
|
@ -25,7 +26,6 @@ import 'package:serverpod_auth_core_server/serverpod_auth_core_server.dart'
|
|||
as _iacs;
|
||||
import 'package:serverpod_auth_idp_server/serverpod_auth_idp_server.dart'
|
||||
as _iais;
|
||||
|
||||
import '../auth/account_endpoint.dart' as _ii6ycf6v;
|
||||
import '../auth/anonymous_idp_endpoint.dart' as _in0zita6;
|
||||
import '../auth/email_code_endpoint.dart' as _in3cimlf;
|
||||
|
|
@ -193,6 +193,11 @@ class Endpoints extends _is.EndpointDispatch {
|
|||
type: _is.getType<String>(),
|
||||
nullable: false,
|
||||
),
|
||||
'purpose': _is.ParameterDescription(
|
||||
name: 'purpose',
|
||||
type: _is.getType<_i5775c0l.EmailCodePurpose>(),
|
||||
nullable: false,
|
||||
),
|
||||
},
|
||||
call:
|
||||
(
|
||||
|
|
@ -203,6 +208,7 @@ class Endpoints extends _is.EndpointDispatch {
|
|||
session,
|
||||
address: params['address'],
|
||||
language: params['language'],
|
||||
purpose: params['purpose'],
|
||||
),
|
||||
),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:typed_data' as _idt;
|
||||
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
|
||||
/// An account's password as the register keeps it (ADR 0017): nothing that
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
|
||||
import '../password/account_reset_kind.dart' as _itrk9fro;
|
||||
|
||||
/// A reset asked for that takes effect only later (ADR 0017), so the
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:typed_data' as _idt;
|
||||
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
|
||||
/// What deleting an account that has a password takes (ADR 0017; the second
|
||||
|
|
|
|||
|
|
@ -11,10 +11,8 @@
|
|||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:typed_data' as _idt;
|
||||
|
||||
import 'package:halkyon_base_server/src/generated/protocol.dart' as _ibewtqgw;
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
|
||||
import '../password/wrapped_keys.dart' as _i2rxumte;
|
||||
|
||||
/// What a device needs to prove the recovery phrase (ADR 0017): the
|
||||
|
|
|
|||
|
|
@ -11,10 +11,8 @@
|
|||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:typed_data' as _idt;
|
||||
|
||||
import 'package:halkyon_base_server/src/generated/protocol.dart' as _ibewtqgw;
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
|
||||
import '../password/password_costs.dart' as _ioe6tior;
|
||||
import '../password/wrapped_keys.dart' as _i2rxumte;
|
||||
|
||||
|
|
|
|||
|
|
@ -11,10 +11,8 @@
|
|||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:typed_data' as _idt;
|
||||
|
||||
import 'package:halkyon_base_server/src/generated/protocol.dart' as _ibewtqgw;
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
|
||||
import '../password/password_costs.dart' as _ioe6tior;
|
||||
|
||||
/// The register's answer to the first step of signing in with a password
|
||||
|
|
|
|||
|
|
@ -11,10 +11,8 @@
|
|||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:typed_data' as _idt;
|
||||
|
||||
import 'package:halkyon_base_server/src/generated/protocol.dart' as _ibewtqgw;
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
|
||||
import '../password/wrapped_keys.dart' as _i2rxumte;
|
||||
|
||||
/// What the register gives a member (or itself) once a password sign-in's
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
|
||||
import '../password/password_refusal.dart' as _i647hpdf;
|
||||
|
||||
/// Something to do with a password, an authenticator or a reset was refused
|
||||
|
|
|
|||
|
|
@ -11,10 +11,8 @@
|
|||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:typed_data' as _idt;
|
||||
|
||||
import 'package:halkyon_base_server/src/generated/protocol.dart' as _ibewtqgw;
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
|
||||
import '../password/password_costs.dart' as _ioe6tior;
|
||||
import '../password/wrapped_keys.dart' as _i2rxumte;
|
||||
|
||||
|
|
|
|||
|
|
@ -11,10 +11,8 @@
|
|||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:typed_data' as _idt;
|
||||
|
||||
import 'package:halkyon_base_server/src/generated/protocol.dart' as _ibewtqgw;
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
|
||||
import '../password/wrapped_keys.dart' as _i2rxumte;
|
||||
|
||||
/// Signed in with a password (ADR 0017): SRP's proof M2, which the device
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue