fix(auth)!: a session alone never gets a code checked

The third reading found a stolen session guessing TOTP codes without end:
nine wrong codes for new recovery codes, then removing an unconfirmed
authenticator of its own, a success that cleared the session's count. As
Proton has it, every signed-in change of how an account is secured now
takes the owner's proof (OwnerProof): the password's SRP proof for a
change's challenge and a TOTP or recovery code. Adding an authenticator
once one is confirmed, removing a confirmed one, new recovery codes, a new
phrase, a new password and deleting take it; only the first authenticator
is confirmed with its own code alone, and removing an unconfirmed one
takes nothing. A proof given where none is taken is refused, and a
confirmed authenticator's code is never checked for a session alone, so
no success passes a proof unchecked.

A session's proofs count for it alone, before they are checked, cleared
only by one accepted; the tenth wrong in a row ends the session. They
never count toward the account's waits nor wait for them, so whoever
knows the password no longer holds a signed-in owner out of changing it
(the second finding), and a new password forgets those waits. Beginning a
change counts nothing against the account (the third finding): 30 an
hour per session, holding back that session alone. Deleting takes a TOTP
code wherever an authenticator is confirmed, a code by mail where no
second factor works (the fourth finding), which SignInWays now tells
(secondFactorWorks). Signed-in calls no longer take the address: nothing
there mails. wrongExistingCode goes, a wrong existing code being a wrong
proof.

The app follows in the next commit.

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-21 10:33:25 +02:00
commit a9459bb875
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
6 changed files with 1461 additions and 1026 deletions

View file

@ -21,9 +21,11 @@ abstract class SignInWays
bool? password,
bool? authenticator,
bool? passwordOnly,
bool? secondFactorWorks,
}) : password = password ?? false,
authenticator = authenticator ?? false,
passwordOnly = passwordOnly ?? false;
passwordOnly = passwordOnly ?? false,
secondFactorWorks = secondFactorWorks ?? false;
factory SignInWays({
required bool gitHub,
@ -31,6 +33,7 @@ abstract class SignInWays
bool? password,
bool? authenticator,
bool? passwordOnly,
bool? secondFactorWorks,
}) = _SignInWaysImpl;
factory SignInWays.fromJson(Map<String, dynamic> jsonSerialization) {
@ -46,6 +49,11 @@ abstract class SignInWays
passwordOnly: jsonSerialization['passwordOnly'] == null
? null
: _isc.BoolJsonExtension.fromJson(jsonSerialization['passwordOnly']),
secondFactorWorks: jsonSerialization['secondFactorWorks'] == null
? null
: _isc.BoolJsonExtension.fromJson(
jsonSerialization['secondFactorWorks'],
),
);
}
@ -64,6 +72,11 @@ abstract class SignInWays
/// neither a code by mail nor GitHub signs into it (ADR 0017).
bool passwordOnly;
/// A second factor works: a confirmed authenticator, and no reset of the
/// authenticators that took effect. Where none does, deleting the account
/// takes a code by mail in place of a TOTP or recovery code.
bool secondFactorWorks;
/// Returns a shallow copy of this [SignInWays]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@ -73,6 +86,7 @@ abstract class SignInWays
bool? password,
bool? authenticator,
bool? passwordOnly,
bool? secondFactorWorks,
});
@override
Map<String, dynamic> toJson() {
@ -83,6 +97,7 @@ abstract class SignInWays
'password': password,
'authenticator': authenticator,
'passwordOnly': passwordOnly,
'secondFactorWorks': secondFactorWorks,
};
}
@ -95,6 +110,7 @@ abstract class SignInWays
'password': password,
'authenticator': authenticator,
'passwordOnly': passwordOnly,
'secondFactorWorks': secondFactorWorks,
};
}
@ -111,12 +127,14 @@ class _SignInWaysImpl extends SignInWays {
bool? password,
bool? authenticator,
bool? passwordOnly,
bool? secondFactorWorks,
}) : super._(
gitHub: gitHub,
email: email,
password: password,
authenticator: authenticator,
passwordOnly: passwordOnly,
secondFactorWorks: secondFactorWorks,
);
/// Returns a shallow copy of this [SignInWays]
@ -129,6 +147,7 @@ class _SignInWaysImpl extends SignInWays {
bool? password,
bool? authenticator,
bool? passwordOnly,
bool? secondFactorWorks,
}) {
return SignInWays(
gitHub: gitHub ?? this.gitHub,
@ -136,6 +155,7 @@ class _SignInWaysImpl extends SignInWays {
password: password ?? this.password,
authenticator: authenticator ?? this.authenticator,
passwordOnly: passwordOnly ?? this.passwordOnly,
secondFactorWorks: secondFactorWorks ?? this.secondFactorWorks,
);
}
}

View file

@ -28,6 +28,8 @@ import 'package:halkyon_base_client/src/protocol/password/key_proof_challenge.da
as _ir3ecgdn;
import 'package:halkyon_base_client/src/protocol/password/new_password.dart'
as _i95l60wq;
import 'package:halkyon_base_client/src/protocol/password/owner_proof.dart'
as _ikq9b2yz;
import 'package:halkyon_base_client/src/protocol/password/password_challenge.dart'
as _iykren7a;
import 'package:halkyon_base_client/src/protocol/password/password_setup.dart'
@ -343,9 +345,11 @@ class EndpointAccountRecovery extends _isc.EndpointRef {
}
/// The signed-in person's authenticators and recovery codes (ADR 0017),
/// which the instance's register keeps (ADR 0016). Every code tried here
/// counts for the session (`asSessionCodeTry`): the tenth wrong in a row
/// ends it ([PasswordRefusal.sessionEnded]).
/// which the instance's register keeps (ADR 0016). Once one is confirmed,
/// every change takes the owner's proof (`OwnerProof`: the password and a
/// code; the third reading), which counts for the session
/// (`asOwnerProofTry`): the tenth wrong in a row ends it
/// ([PasswordRefusal.sessionEnded]).
/// {@category Endpoint}
class EndpointAuthenticator extends _isc.EndpointRef {
EndpointAuthenticator(_isc.EndpointCaller caller) : super(caller);
@ -369,20 +373,20 @@ class EndpointAuthenticator extends _isc.EndpointRef {
);
/// Confirms [authenticator] with a [code] from it, and, where the account
/// has a confirmed authenticator already, an [existingCode] from that one;
/// the account's first returns its recovery codes, shown once. Refusals as
/// has a confirmed authenticator already, the owner's [proof]; the
/// account's first returns its recovery codes, shown once. Refusals as
/// `confirmAuthenticator`'s.
_ida.Future<List<String>> confirm({
required _isc.UuidValue authenticator,
required String code,
String? existingCode,
_ikq9b2yz.OwnerProof? proof,
}) => caller.callServerEndpoint<List<String>>(
'halkyon_base.authenticator',
'confirm',
{
'authenticator': authenticator,
'code': code,
'existingCode': existingCode,
'proof': proof,
},
);
@ -394,28 +398,30 @@ class EndpointAuthenticator extends _isc.EndpointRef {
{},
);
/// Removes [authenticator]: a confirmed one with a [code] from one of the
/// caller's, while another stays. Refusals as `removeAuthenticator`'s.
/// Removes [authenticator]: one never confirmed as it is, a confirmed one
/// with the owner's [proof], while another stays. Refusals as
/// `removeAuthenticator`'s.
_ida.Future<void> remove({
required _isc.UuidValue authenticator,
String? code,
_ikq9b2yz.OwnerProof? proof,
}) => caller.callServerEndpoint<void>(
'halkyon_base.authenticator',
'remove',
{
'authenticator': authenticator,
'code': code,
'proof': proof,
},
);
/// New recovery codes in place of the old, with a [code] from one of the
/// caller's authenticators: shown once. Refusals as `newRecoveryCodes`'.
_ida.Future<List<String>> newRecoveryCodes({required String code}) =>
caller.callServerEndpoint<List<String>>(
'halkyon_base.authenticator',
'newRecoveryCodes',
{'code': code},
);
/// New recovery codes in place of the old, with the owner's [proof]:
/// shown once. Refusals as `newRecoveryCodes`'.
_ida.Future<List<String>> newRecoveryCodes({
required _ikq9b2yz.OwnerProof proof,
}) => caller.callServerEndpoint<List<String>>(
'halkyon_base.authenticator',
'newRecoveryCodes',
{'proof': proof},
);
}
/// Giving the signed-in person a password (ADR 0017): the way an anonymous
@ -448,8 +454,13 @@ class EndpointPassword extends _isc.EndpointRef {
},
);
/// The first step of the caller proving their password to change it:
/// its salt and costs, and SRP's B. Refusals as `beginPasswordChange`'s.
/// The first step of the caller proving their password (`OwnerProof`) to
/// change how their account is secured: its salt and costs, and SRP's B.
/// At most [changesBegunPerSession] an hour per session, which holds back
/// no other session, the owner's among them (the third reading's third
/// finding); the proofs that fail count against the session too
/// (`asOwnerProofTry`). Refusals as `beginPasswordChange`'s, and
/// [PasswordRefusal.tooManyAttempts].
_ida.Future<_iykren7a.PasswordChallenge> beginChange() =>
caller.callServerEndpoint<_iykren7a.PasswordChallenge>(
'halkyon_base.password',
@ -458,69 +469,38 @@ class EndpointPassword extends _isc.EndpointRef {
);
/// Changes the caller's password to [password] (the same keys, wrapped
/// under the new password's key on the device), with the old password's
/// proof for [challenge] ([beginChange]) and a [totpCode] or
/// [recoveryCode], and the account's [address] as the device typed it
/// (checked before the code; the owner is mailed in [language] should
/// codes come to wait). Every other session of theirs ends, in every
/// service of the instance; this one stays. Refusals as `changePassword`'s.
/// under the new password's key on the device), with the owner's [proof]
/// (the old password's proof for a challenge [beginChange] gave, and a
/// TOTP or recovery code). Every other session of theirs ends, in every
/// service of the instance; this one stays. The proof counts for this
/// session (`asOwnerProofTry`). Refusals as `changePassword`'s.
_ida.Future<void> change({
required _isc.UuidValue challenge,
required _idt.ByteData clientValue,
required _idt.ByteData clientProof,
String? totpCode,
String? recoveryCode,
required _ikq9b2yz.OwnerProof proof,
required _i95l60wq.NewPassword password,
required String host,
required String address,
required String language,
}) => caller.callServerEndpoint<void>(
'halkyon_base.password',
'change',
{
'challenge': challenge,
'clientValue': clientValue,
'clientProof': clientProof,
'totpCode': totpCode,
'recoveryCode': recoveryCode,
'proof': proof,
'password': password,
'host': host,
'address': address,
'language': language,
},
);
/// A new recovery phrase: [recoveryKeys], the caller's keys wrapped under
/// it on the device, replace the old wrapping, and [recoveryProofKey], the
/// public half of its proof key, the old one, with the password's proof
/// for [challenge] ([beginChange]) and a [totpCode] or [recoveryCode], with
/// the account's [address] and [language], as [change] takes. Refusals as
/// `setRecoveryKeys`'.
/// public half of its proof key, the old one, with the owner's [proof], as
/// [change] takes. Refusals as `setRecoveryKeys`'.
_ida.Future<void> setRecoveryKeys({
required _isc.UuidValue challenge,
required _idt.ByteData clientValue,
required _idt.ByteData clientProof,
String? totpCode,
String? recoveryCode,
required _ikq9b2yz.OwnerProof proof,
required _i3so7e0p.WrappedKeys recoveryKeys,
required _idt.ByteData recoveryProofKey,
required String host,
required String address,
required String language,
}) => caller.callServerEndpoint<void>(
'halkyon_base.password',
'setRecoveryKeys',
{
'challenge': challenge,
'clientValue': clientValue,
'clientProof': clientProof,
'totpCode': totpCode,
'recoveryCode': recoveryCode,
'proof': proof,
'recoveryKeys': recoveryKeys,
'recoveryProofKey': recoveryProofKey,
'host': host,
'address': address,
'language': language,
},
);
}

View file

@ -16,8 +16,8 @@ import 'package:serverpod_client/serverpod_client.dart' as _isc;
/// What deleting an account that has a password takes (ADR 0017; the second
/// reading): the password's SRP proof for a challenge `password.beginChange`
/// gave, a TOTP or recovery code, and the host the device addressed, as a
/// change of password does. A session alone deletes only an account without
/// one.
/// change of password does (`OwnerProof`). A session alone deletes only an
/// account without one.
abstract class DeletionProof
implements _isc.SerializableModel, _isc.ProtocolSerialization {
DeletionProof._({
@ -29,8 +29,6 @@ abstract class DeletionProof
this.request,
this.code,
required this.host,
required this.address,
required this.language,
});
factory DeletionProof({
@ -42,8 +40,6 @@ abstract class DeletionProof
_isc.UuidValue? request,
String? code,
required String host,
required String address,
required String language,
}) = _DeletionProofImpl;
factory DeletionProof.fromJson(Map<String, dynamic> jsonSerialization) {
@ -64,8 +60,6 @@ abstract class DeletionProof
: _isc.UuidValueJsonExtension.fromJson(jsonSerialization['request']),
code: jsonSerialization['code'] as String?,
host: jsonSerialization['host'] as String,
address: jsonSerialization['address'] as String,
language: jsonSerialization['language'] as String,
);
}
@ -91,13 +85,6 @@ abstract class DeletionProof
/// checks it is its own before the register binds the proof to it.
String host;
/// The account's address as the device typed it, and the language its
/// mails are worded in: checked before the code, so the owner is mailed
/// should codes come to wait (the re-review's fourth finding).
String address;
String language;
/// Returns a shallow copy of this [DeletionProof]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@ -110,8 +97,6 @@ abstract class DeletionProof
_isc.UuidValue? request,
String? code,
String? host,
String? address,
String? language,
});
@override
Map<String, dynamic> toJson() {
@ -125,8 +110,6 @@ abstract class DeletionProof
if (request != null) 'request': request?.toJson(),
if (code != null) 'code': code,
'host': host,
'address': address,
'language': language,
};
}
@ -142,8 +125,6 @@ abstract class DeletionProof
if (request != null) 'request': request?.toJson(),
if (code != null) 'code': code,
'host': host,
'address': address,
'language': language,
};
}
@ -165,8 +146,6 @@ class _DeletionProofImpl extends DeletionProof {
_isc.UuidValue? request,
String? code,
required String host,
required String address,
required String language,
}) : super._(
challenge: challenge,
clientValue: clientValue,
@ -176,8 +155,6 @@ class _DeletionProofImpl extends DeletionProof {
request: request,
code: code,
host: host,
address: address,
language: language,
);
/// Returns a shallow copy of this [DeletionProof]
@ -193,8 +170,6 @@ class _DeletionProofImpl extends DeletionProof {
Object? request = _Undefined,
Object? code = _Undefined,
String? host,
String? address,
String? language,
}) {
return DeletionProof(
challenge: challenge ?? this.challenge,
@ -205,8 +180,6 @@ class _DeletionProofImpl extends DeletionProof {
request: request is _isc.UuidValue? ? request : this.request,
code: code is String? ? code : this.code,
host: host ?? this.host,
address: address ?? this.address,
language: language ?? this.language,
);
}
}

View file

@ -0,0 +1,158 @@
/* 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 'dart:typed_data' as _idt;
import 'package:serverpod_client/serverpod_client.dart' as _isc;
/// What a signed-in owner proves to change how their account is secured
/// (ADR 0017; the third reading, as Proton has it): the password's SRP
/// proof for a challenge `password.beginChange` gave, bound to the host the
/// device addressed, and a TOTP code from one of the account's confirmed
/// authenticators or one of its recovery codes. Adding an authenticator once
/// one is confirmed, removing a confirmed one, new recovery codes, a new
/// recovery phrase and a new password take it: a session alone never gets a
/// code checked.
abstract class OwnerProof
implements _isc.SerializableModel, _isc.ProtocolSerialization {
OwnerProof._({
required this.challenge,
required this.clientValue,
required this.clientProof,
required this.host,
this.totpCode,
this.recoveryCode,
});
factory OwnerProof({
required _isc.UuidValue challenge,
required _idt.ByteData clientValue,
required _idt.ByteData clientProof,
required String host,
String? totpCode,
String? recoveryCode,
}) = _OwnerProofImpl;
factory OwnerProof.fromJson(Map<String, dynamic> jsonSerialization) {
return OwnerProof(
challenge: _isc.UuidValueJsonExtension.fromJson(
jsonSerialization['challenge'],
),
clientValue: _isc.ByteDataJsonExtension.fromJson(
jsonSerialization['clientValue'],
),
clientProof: _isc.ByteDataJsonExtension.fromJson(
jsonSerialization['clientProof'],
),
host: jsonSerialization['host'] as String,
totpCode: jsonSerialization['totpCode'] as String?,
recoveryCode: jsonSerialization['recoveryCode'] as String?,
);
}
_isc.UuidValue challenge;
_idt.ByteData clientValue;
_idt.ByteData clientProof;
/// The host the device addressed the instance by: the server it reached
/// checks it is its own before the register binds the proof to it.
String host;
String? totpCode;
String? recoveryCode;
/// Returns a shallow copy of this [OwnerProof]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
OwnerProof copyWith({
_isc.UuidValue? challenge,
_idt.ByteData? clientValue,
_idt.ByteData? clientProof,
String? host,
String? totpCode,
String? recoveryCode,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.OwnerProof',
'challenge': challenge.toJson(),
'clientValue': clientValue.toJson(),
'clientProof': clientProof.toJson(),
'host': host,
if (totpCode != null) 'totpCode': totpCode,
if (recoveryCode != null) 'recoveryCode': recoveryCode,
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'halkyon_base.OwnerProof',
'challenge': challenge.toJson(),
'clientValue': clientValue.toJson(),
'clientProof': clientProof.toJson(),
'host': host,
if (totpCode != null) 'totpCode': totpCode,
if (recoveryCode != null) 'recoveryCode': recoveryCode,
};
}
@override
String toString() {
return _isc.SerializationManager.encode(this);
}
}
class _Undefined {}
class _OwnerProofImpl extends OwnerProof {
_OwnerProofImpl({
required _isc.UuidValue challenge,
required _idt.ByteData clientValue,
required _idt.ByteData clientProof,
required String host,
String? totpCode,
String? recoveryCode,
}) : super._(
challenge: challenge,
clientValue: clientValue,
clientProof: clientProof,
host: host,
totpCode: totpCode,
recoveryCode: recoveryCode,
);
/// Returns a shallow copy of this [OwnerProof]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@override
OwnerProof copyWith({
_isc.UuidValue? challenge,
_idt.ByteData? clientValue,
_idt.ByteData? clientProof,
String? host,
Object? totpCode = _Undefined,
Object? recoveryCode = _Undefined,
}) {
return OwnerProof(
challenge: challenge ?? this.challenge,
clientValue: clientValue ?? this.clientValue.clone(),
clientProof: clientProof ?? this.clientProof.clone(),
host: host ?? this.host,
totpCode: totpCode is String? ? totpCode : this.totpCode,
recoveryCode: recoveryCode is String? ? recoveryCode : this.recoveryCode,
);
}
}

View file

@ -61,7 +61,7 @@ enum PasswordRefusal implements _isc.SerializableModel {
/// The account has as many authenticators as it may: remove one first.
tooManyAuthenticators,
/// The code from the authenticator isn't right.
/// The code from the authenticator being set up isn't right.
wrongCode,
/// The reset's time hasn't come yet.
@ -70,11 +70,6 @@ enum PasswordRefusal implements _isc.SerializableModel {
/// No such reset is waiting, or the address isn't the account's.
noReset,
/// Adding an authenticator to an account that has one confirmed takes a
/// code from one of those, which is missing or isn't right: a session
/// alone adds no second factor.
wrongExistingCode,
/// The device addressed this instance by another name than its own
/// (`apiServer.publicHost`): what it proves is bound to the name it
/// addressed, so the operator must configure the name devices use.
@ -84,8 +79,9 @@ enum PasswordRefusal implements _isc.SerializableModel {
/// `takesEffectAt` (the second reading's M4). Only after a right password.
codesWait,
/// Ten codes in a row from this signed-in session were wrong: the
/// session ended (the re-review's third finding); sign in again.
/// Ten proofs in a row from this signed-in session (the password with a
/// code, `OwnerProof`) were wrong: the session ended (the third reading);
/// sign in again.
sessionEnded,
/// The password was changed (a reset carried out), but this service
@ -128,8 +124,6 @@ enum PasswordRefusal implements _isc.SerializableModel {
return PasswordRefusal.notDue;
case 'noReset':
return PasswordRefusal.noReset;
case 'wrongExistingCode':
return PasswordRefusal.wrongExistingCode;
case 'otherInstance':
return PasswordRefusal.otherInstance;
case 'codesWait':

View file

@ -31,6 +31,7 @@ import 'password/authenticator_enrolment.dart' as _iv7tra7v;
import 'password/deletion_proof.dart' as _ix1u7hdq;
import 'password/key_proof_challenge.dart' as _iehtx95q;
import 'password/new_password.dart' as _itvvd6m6;
import 'password/owner_proof.dart' as _i5f4sdg4;
import 'password/password_challenge.dart' as _iuxisac7;
import 'password/password_costs.dart' as _izp492e5;
import 'password/password_refusal.dart' as _ibfrwdhm;
@ -57,6 +58,7 @@ export 'password/authenticator_enrolment.dart';
export 'password/deletion_proof.dart';
export 'password/key_proof_challenge.dart';
export 'password/new_password.dart';
export 'password/owner_proof.dart';
export 'password/password_challenge.dart';
export 'password/password_costs.dart';
export 'password/password_refusal.dart';
@ -156,6 +158,9 @@ class Protocol extends _isc.SerializationManager {
if (t == _itvvd6m6.NewPassword) {
return _itvvd6m6.NewPassword.fromJson(data) as T;
}
if (t == _i5f4sdg4.OwnerProof) {
return _i5f4sdg4.OwnerProof.fromJson(data) as T;
}
if (t == _iuxisac7.PasswordChallenge) {
return _iuxisac7.PasswordChallenge.fromJson(data) as T;
}
@ -253,6 +258,9 @@ class Protocol extends _isc.SerializationManager {
if (t == _isc.getType<_itvvd6m6.NewPassword?>()) {
return (data != null ? _itvvd6m6.NewPassword.fromJson(data) : null) as T;
}
if (t == _isc.getType<_i5f4sdg4.OwnerProof?>()) {
return (data != null ? _i5f4sdg4.OwnerProof.fromJson(data) : null) as T;
}
if (t == _isc.getType<_iuxisac7.PasswordChallenge?>()) {
return (data != null ? _iuxisac7.PasswordChallenge.fromJson(data) : null)
as T;
@ -355,6 +363,7 @@ class Protocol extends _isc.SerializationManager {
_ix1u7hdq.DeletionProof => 'DeletionProof',
_iehtx95q.KeyProofChallenge => 'KeyProofChallenge',
_itvvd6m6.NewPassword => 'NewPassword',
_i5f4sdg4.OwnerProof => 'OwnerProof',
_iuxisac7.PasswordChallenge => 'PasswordChallenge',
_izp492e5.PasswordCosts => 'PasswordCosts',
_ibfrwdhm.PasswordRefusal => 'PasswordRefusal',
@ -411,6 +420,8 @@ class Protocol extends _isc.SerializationManager {
return 'KeyProofChallenge';
case _itvvd6m6.NewPassword():
return 'NewPassword';
case _i5f4sdg4.OwnerProof():
return 'OwnerProof';
case _iuxisac7.PasswordChallenge():
return 'PasswordChallenge';
case _izp492e5.PasswordCosts():
@ -486,6 +497,9 @@ class Protocol extends _isc.SerializationManager {
if (dataClassName == 'NewPassword') {
return deserialize<_itvvd6m6.NewPassword>(data['data']);
}
if (dataClassName == 'OwnerProof') {
return deserialize<_i5f4sdg4.OwnerProof>(data['data']);
}
if (dataClassName == 'PasswordChallenge') {
return deserialize<_iuxisac7.PasswordChallenge>(data['data']);
}