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
3 changed files with 42026 additions and 121 deletions
|
|
@ -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_client/serverpod_client.dart' as _isc;
|
||||
|
||||
/// 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 _isc.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;
|
||||
}
|
||||
|
|
@ -12,6 +12,8 @@
|
|||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:async' as _ida;
|
||||
import 'dart:typed_data' as _idt;
|
||||
import 'package:halkyon_base_client/src/protocol/auth/email_code_purpose.dart'
|
||||
as _i6rtanuw;
|
||||
import 'package:halkyon_base_client/src/protocol/auth/sign_in_ways.dart'
|
||||
as _ifld8s4r;
|
||||
import 'package:halkyon_base_client/src/protocol/instance/instance_basics.dart'
|
||||
|
|
@ -115,17 +117,20 @@ class EndpointEmailCode extends _isc.EndpointRef {
|
|||
@override
|
||||
String get name => 'halkyon_base.emailCode';
|
||||
|
||||
/// 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].
|
||||
_ida.Future<_isc.UuidValue> sendCode({
|
||||
required String address,
|
||||
required String language,
|
||||
required _i6rtanuw.EmailCodePurpose purpose,
|
||||
}) => caller.callServerEndpoint<_isc.UuidValue>(
|
||||
'halkyon_base.emailCode',
|
||||
'sendCode',
|
||||
{
|
||||
'address': address,
|
||||
'language': language,
|
||||
'purpose': purpose,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import 'package:serverpod_auth_core_client/serverpod_auth_core_client.dart'
|
|||
import 'package:serverpod_auth_idp_client/serverpod_auth_idp_client.dart'
|
||||
as _iaic;
|
||||
import 'package:serverpod_client/serverpod_client.dart' as _isc;
|
||||
import 'auth/email_code_purpose.dart' as _ifpka2on;
|
||||
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;
|
||||
|
|
@ -43,6 +44,7 @@ import 'profile/profile.dart' as _ia5j8fch;
|
|||
import 'profile/profile_not_found_exception.dart' as _iim3hfcf;
|
||||
import 'profile/profile_refusal.dart' as _i6j6efd1;
|
||||
import 'profile/profile_refused_exception.dart' as _idvc5e7p;
|
||||
export 'auth/email_code_purpose.dart';
|
||||
export 'auth/email_sign_in_refusal.dart';
|
||||
export 'auth/email_sign_in_refused_exception.dart';
|
||||
export 'auth/github_sign_in_refusal.dart';
|
||||
|
|
@ -115,6 +117,9 @@ class Protocol extends _isc.SerializationManager {
|
|||
}
|
||||
}
|
||||
|
||||
if (t == _ifpka2on.EmailCodePurpose) {
|
||||
return _ifpka2on.EmailCodePurpose.fromJson(data) as T;
|
||||
}
|
||||
if (t == _ieyvcag1.EmailSignInRefusal) {
|
||||
return _ieyvcag1.EmailSignInRefusal.fromJson(data) as T;
|
||||
}
|
||||
|
|
@ -190,6 +195,10 @@ class Protocol extends _isc.SerializationManager {
|
|||
if (t == _idvc5e7p.ProfileRefusedException) {
|
||||
return _idvc5e7p.ProfileRefusedException.fromJson(data) as T;
|
||||
}
|
||||
if (t == _isc.getType<_ifpka2on.EmailCodePurpose?>()) {
|
||||
return (data != null ? _ifpka2on.EmailCodePurpose.fromJson(data) : null)
|
||||
as T;
|
||||
}
|
||||
if (t == _isc.getType<_ieyvcag1.EmailSignInRefusal?>()) {
|
||||
return (data != null ? _ieyvcag1.EmailSignInRefusal.fromJson(data) : null)
|
||||
as T;
|
||||
|
|
@ -333,6 +342,7 @@ class Protocol extends _isc.SerializationManager {
|
|||
|
||||
static String? getClassNameForType(Type type) {
|
||||
return switch (type) {
|
||||
_ifpka2on.EmailCodePurpose => 'EmailCodePurpose',
|
||||
_ieyvcag1.EmailSignInRefusal => 'EmailSignInRefusal',
|
||||
_iztfgag5.EmailSignInRefusedException => 'EmailSignInRefusedException',
|
||||
_ir1gd9p5.GitHubSignInRefusal => 'GitHubSignInRefusal',
|
||||
|
|
@ -375,6 +385,8 @@ class Protocol extends _isc.SerializationManager {
|
|||
}
|
||||
|
||||
switch (data) {
|
||||
case _ifpka2on.EmailCodePurpose():
|
||||
return 'EmailCodePurpose';
|
||||
case _ieyvcag1.EmailSignInRefusal():
|
||||
return 'EmailSignInRefusal';
|
||||
case _iztfgag5.EmailSignInRefusedException():
|
||||
|
|
@ -435,6 +447,9 @@ class Protocol extends _isc.SerializationManager {
|
|||
if (dataClassName is! String) {
|
||||
return super.deserializeByClassName(data);
|
||||
}
|
||||
if (dataClassName == 'EmailCodePurpose') {
|
||||
return deserialize<_ifpka2on.EmailCodePurpose>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'EmailSignInRefusal') {
|
||||
return deserialize<_ieyvcag1.EmailSignInRefusal>(data['data']);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue