feat(base): SimpleLogin accounts' keys without SRP, signing up in one call (ADR 0020, step 3)
An account that signs in with SimpleLogin keeps its keys under a password that signs nobody in (D19): `account_password` keeps the password's own proof key (Ed25519, `passwordProofKey`) in place of SRP's verifier, which becomes nullable. The migration adds the column and drops the NOT NULL, nothing else. - `simpleLogin.signUp(attempt, handle, setup)`: someone new whose identity waits under the attempt gets, in one transaction, an account (identified), the link, a profile with the handle and the keys; then a session (D18). Refused, the identity still waiting, for a setup out of range and a handle refused as saving a profile refuses it. - `simpleLogin.checkHandle(attempt, handle)`: whether someone new could take a handle, at most 30 an hour per attempt and 60 per network. - `simpleLogin.complete` says, for an account, whether it has its keys and its handle, and keeps the sign-in ten minutes under its attempt, the account as its caller: D20's confirmation for the changes to come. - `keys.begin`, `keys.unlock`: the keys for a signed-in device once the password is proven with its proof key, for a nonce, bound to the base URL the device addressed; wrong proofs counted per session as ADR 0017's. - An authenticator is enrolled only for a password that signs in. - Members ask the register for all of it; `RemoteRegister` and the instance API carry the four new operations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
c6831b0b7b
commit
090d901e92
36 changed files with 46254 additions and 78 deletions
|
|
@ -6,8 +6,12 @@ fields:
|
|||
### SimpleLogin (ADR 0020): the instance's register keeps its ID for the
|
||||
### instance's SimpleLogin app.
|
||||
simpleLogin: bool, default=false
|
||||
### A password (ADR 0017), which an address needs for resets.
|
||||
### A password that signs in (ADR 0017, SRP's), which an address needs
|
||||
### for resets.
|
||||
password: bool, default=false
|
||||
### The account's keys, under a password that signs in (ADR 0017) or one
|
||||
### that only opens them, as for SimpleLogin (ADR 0020, D19).
|
||||
keys: bool, default=false
|
||||
### 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
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ abstract class SignInWays
|
|||
required this.email,
|
||||
bool? simpleLogin,
|
||||
bool? password,
|
||||
bool? keys,
|
||||
bool? authenticator,
|
||||
bool? passwordOnly,
|
||||
bool? secondFactorWorks,
|
||||
|
|
@ -28,6 +29,7 @@ abstract class SignInWays
|
|||
this.accountResetAt,
|
||||
}) : simpleLogin = simpleLogin ?? false,
|
||||
password = password ?? false,
|
||||
keys = keys ?? false,
|
||||
authenticator = authenticator ?? false,
|
||||
passwordOnly = passwordOnly ?? false,
|
||||
secondFactorWorks = secondFactorWorks ?? false;
|
||||
|
|
@ -37,6 +39,7 @@ abstract class SignInWays
|
|||
required bool email,
|
||||
bool? simpleLogin,
|
||||
bool? password,
|
||||
bool? keys,
|
||||
bool? authenticator,
|
||||
bool? passwordOnly,
|
||||
bool? secondFactorWorks,
|
||||
|
|
@ -55,6 +58,9 @@ abstract class SignInWays
|
|||
password: jsonSerialization['password'] == null
|
||||
? null
|
||||
: _is.BoolJsonExtension.fromJson(jsonSerialization['password']),
|
||||
keys: jsonSerialization['keys'] == null
|
||||
? null
|
||||
: _is.BoolJsonExtension.fromJson(jsonSerialization['keys']),
|
||||
authenticator: jsonSerialization['authenticator'] == null
|
||||
? null
|
||||
: _is.BoolJsonExtension.fromJson(jsonSerialization['authenticator']),
|
||||
|
|
@ -93,9 +99,14 @@ abstract class SignInWays
|
|||
/// instance's SimpleLogin app.
|
||||
bool simpleLogin;
|
||||
|
||||
/// A password (ADR 0017), which an address needs for resets.
|
||||
/// A password that signs in (ADR 0017, SRP's), which an address needs
|
||||
/// for resets.
|
||||
bool password;
|
||||
|
||||
/// The account's keys, under a password that signs in (ADR 0017) or one
|
||||
/// that only opens them, as for SimpleLogin (ADR 0020, D19).
|
||||
bool keys;
|
||||
|
||||
/// A confirmed authenticator: the password signs in only with one.
|
||||
bool authenticator;
|
||||
|
||||
|
|
@ -130,6 +141,7 @@ abstract class SignInWays
|
|||
bool? email,
|
||||
bool? simpleLogin,
|
||||
bool? password,
|
||||
bool? keys,
|
||||
bool? authenticator,
|
||||
bool? passwordOnly,
|
||||
bool? secondFactorWorks,
|
||||
|
|
@ -145,6 +157,7 @@ abstract class SignInWays
|
|||
'email': email,
|
||||
'simpleLogin': simpleLogin,
|
||||
'password': password,
|
||||
'keys': keys,
|
||||
'authenticator': authenticator,
|
||||
'passwordOnly': passwordOnly,
|
||||
'secondFactorWorks': secondFactorWorks,
|
||||
|
|
@ -164,6 +177,7 @@ abstract class SignInWays
|
|||
'email': email,
|
||||
'simpleLogin': simpleLogin,
|
||||
'password': password,
|
||||
'keys': keys,
|
||||
'authenticator': authenticator,
|
||||
'passwordOnly': passwordOnly,
|
||||
'secondFactorWorks': secondFactorWorks,
|
||||
|
|
@ -189,6 +203,7 @@ class _SignInWaysImpl extends SignInWays {
|
|||
required bool email,
|
||||
bool? simpleLogin,
|
||||
bool? password,
|
||||
bool? keys,
|
||||
bool? authenticator,
|
||||
bool? passwordOnly,
|
||||
bool? secondFactorWorks,
|
||||
|
|
@ -200,6 +215,7 @@ class _SignInWaysImpl extends SignInWays {
|
|||
email: email,
|
||||
simpleLogin: simpleLogin,
|
||||
password: password,
|
||||
keys: keys,
|
||||
authenticator: authenticator,
|
||||
passwordOnly: passwordOnly,
|
||||
secondFactorWorks: secondFactorWorks,
|
||||
|
|
@ -217,6 +233,7 @@ class _SignInWaysImpl extends SignInWays {
|
|||
bool? email,
|
||||
bool? simpleLogin,
|
||||
bool? password,
|
||||
bool? keys,
|
||||
bool? authenticator,
|
||||
bool? passwordOnly,
|
||||
bool? secondFactorWorks,
|
||||
|
|
@ -229,6 +246,7 @@ class _SignInWaysImpl extends SignInWays {
|
|||
email: email ?? this.email,
|
||||
simpleLogin: simpleLogin ?? this.simpleLogin,
|
||||
password: password ?? this.password,
|
||||
keys: keys ?? this.keys,
|
||||
authenticator: authenticator ?? this.authenticator,
|
||||
passwordOnly: passwordOnly ?? this.passwordOnly,
|
||||
secondFactorWorks: secondFactorWorks ?? this.secondFactorWorks,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@
|
|||
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/keys/keys_proof.dart'
|
||||
as _iy592cuk;
|
||||
import 'package:halkyon_base_server/src/generated/keys/keys_setup.dart'
|
||||
as _ii64ejy6;
|
||||
import 'package:halkyon_base_server/src/generated/password/deletion_proof.dart'
|
||||
as _i619l6vv;
|
||||
import 'package:halkyon_base_server/src/generated/password/new_password.dart'
|
||||
|
|
@ -37,6 +41,7 @@ import '../auth/email_code_endpoint.dart' as _in3cimlf;
|
|||
import '../auth/github_sign_in_endpoint.dart' as _iasw79m5;
|
||||
import '../auth/github_upgrade_endpoint.dart' as _ivnugbf2;
|
||||
import '../instance/instance_endpoint.dart' as _i3g8bfc2;
|
||||
import '../keys/keys_endpoint.dart' as _icowoccn;
|
||||
import '../password/account_recovery_endpoint.dart' as _ivnbjhab;
|
||||
import '../password/authenticator_endpoint.dart' as _i3xqawxj;
|
||||
import '../password/password_endpoint.dart' as _ig7fyg1b;
|
||||
|
|
@ -84,6 +89,12 @@ class Endpoints extends _is.EndpointDispatch {
|
|||
'instance',
|
||||
'halkyon_base',
|
||||
),
|
||||
'keys': _icowoccn.KeysEndpoint()
|
||||
..initialize(
|
||||
server,
|
||||
'keys',
|
||||
'halkyon_base',
|
||||
),
|
||||
'accountRecovery': _ivnbjhab.AccountRecoveryEndpoint()
|
||||
..initialize(
|
||||
server,
|
||||
|
|
@ -318,6 +329,40 @@ class Endpoints extends _is.EndpointDispatch {
|
|||
),
|
||||
},
|
||||
);
|
||||
connectors['keys'] = _is.EndpointConnector(
|
||||
name: 'keys',
|
||||
endpoint: endpoints['keys']!,
|
||||
methodConnectors: {
|
||||
'begin': _is.MethodConnector(
|
||||
name: 'begin',
|
||||
params: {},
|
||||
call:
|
||||
(
|
||||
_is.Session session,
|
||||
Map<String, dynamic> params,
|
||||
) async =>
|
||||
(endpoints['keys'] as _icowoccn.KeysEndpoint).begin(session),
|
||||
),
|
||||
'unlock': _is.MethodConnector(
|
||||
name: 'unlock',
|
||||
params: {
|
||||
'proof': _is.ParameterDescription(
|
||||
name: 'proof',
|
||||
type: _is.getType<_iy592cuk.KeysProof>(),
|
||||
nullable: false,
|
||||
),
|
||||
},
|
||||
call:
|
||||
(
|
||||
_is.Session session,
|
||||
Map<String, dynamic> params,
|
||||
) async => (endpoints['keys'] as _icowoccn.KeysEndpoint).unlock(
|
||||
session,
|
||||
params['proof'],
|
||||
),
|
||||
),
|
||||
},
|
||||
);
|
||||
connectors['accountRecovery'] = _is.EndpointConnector(
|
||||
name: 'accountRecovery',
|
||||
endpoint: endpoints['accountRecovery']!,
|
||||
|
|
@ -1150,6 +1195,64 @@ class Endpoints extends _is.EndpointDispatch {
|
|||
code: params['code'],
|
||||
),
|
||||
),
|
||||
'checkHandle': _is.MethodConnector(
|
||||
name: 'checkHandle',
|
||||
params: {
|
||||
'attempt': _is.ParameterDescription(
|
||||
name: 'attempt',
|
||||
type: _is.getType<_is.UuidValue>(),
|
||||
nullable: false,
|
||||
),
|
||||
'handle': _is.ParameterDescription(
|
||||
name: 'handle',
|
||||
type: _is.getType<String>(),
|
||||
nullable: false,
|
||||
),
|
||||
},
|
||||
call:
|
||||
(
|
||||
_is.Session session,
|
||||
Map<String, dynamic> params,
|
||||
) async =>
|
||||
(endpoints['simpleLogin'] as _ia8c9alv.SimpleLoginEndpoint)
|
||||
.checkHandle(
|
||||
session,
|
||||
attempt: params['attempt'],
|
||||
handle: params['handle'],
|
||||
),
|
||||
),
|
||||
'signUp': _is.MethodConnector(
|
||||
name: 'signUp',
|
||||
params: {
|
||||
'attempt': _is.ParameterDescription(
|
||||
name: 'attempt',
|
||||
type: _is.getType<_is.UuidValue>(),
|
||||
nullable: false,
|
||||
),
|
||||
'handle': _is.ParameterDescription(
|
||||
name: 'handle',
|
||||
type: _is.getType<String>(),
|
||||
nullable: false,
|
||||
),
|
||||
'setup': _is.ParameterDescription(
|
||||
name: 'setup',
|
||||
type: _is.getType<_ii64ejy6.KeysSetup>(),
|
||||
nullable: false,
|
||||
),
|
||||
},
|
||||
call:
|
||||
(
|
||||
_is.Session session,
|
||||
Map<String, dynamic> params,
|
||||
) async =>
|
||||
(endpoints['simpleLogin'] as _ia8c9alv.SimpleLoginEndpoint)
|
||||
.signUp(
|
||||
session,
|
||||
attempt: params['attempt'],
|
||||
handle: params['handle'],
|
||||
setup: params['setup'],
|
||||
),
|
||||
),
|
||||
},
|
||||
);
|
||||
modules['halkyon_oidc'] = _ipoqfiqr.Endpoints()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,116 @@
|
|||
/* 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:halkyon_base_server/src/generated/protocol.dart' as _ibewtqgw;
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
import '../password/wrapped_keys.dart' as _i2rxumte;
|
||||
|
||||
/// The account's keys as a device gets them once it proved the password
|
||||
/// (Halkyon Learn's ADR 0020, 7 and D19): its public keys, and its private
|
||||
/// keys wrapped under the password's key, which the device opens.
|
||||
abstract class DeviceKeys
|
||||
implements _is.SerializableModel, _is.ProtocolSerialization {
|
||||
DeviceKeys._({
|
||||
required this.encryptionPublicKey,
|
||||
required this.signingPublicKey,
|
||||
required this.passwordKeys,
|
||||
});
|
||||
|
||||
factory DeviceKeys({
|
||||
required _idt.ByteData encryptionPublicKey,
|
||||
required _idt.ByteData signingPublicKey,
|
||||
required _i2rxumte.WrappedKeys passwordKeys,
|
||||
}) = _DeviceKeysImpl;
|
||||
|
||||
factory DeviceKeys.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return DeviceKeys(
|
||||
encryptionPublicKey: _is.ByteDataJsonExtension.fromJson(
|
||||
jsonSerialization['encryptionPublicKey'],
|
||||
),
|
||||
signingPublicKey: _is.ByteDataJsonExtension.fromJson(
|
||||
jsonSerialization['signingPublicKey'],
|
||||
),
|
||||
passwordKeys: _ibewtqgw.Protocol().deserialize<_i2rxumte.WrappedKeys>(
|
||||
jsonSerialization['passwordKeys'],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_idt.ByteData encryptionPublicKey;
|
||||
|
||||
_idt.ByteData signingPublicKey;
|
||||
|
||||
_i2rxumte.WrappedKeys passwordKeys;
|
||||
|
||||
/// Returns a shallow copy of this [DeviceKeys]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
DeviceKeys copyWith({
|
||||
_idt.ByteData? encryptionPublicKey,
|
||||
_idt.ByteData? signingPublicKey,
|
||||
_i2rxumte.WrappedKeys? passwordKeys,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'halkyon_base.DeviceKeys',
|
||||
'encryptionPublicKey': encryptionPublicKey.toJson(),
|
||||
'signingPublicKey': signingPublicKey.toJson(),
|
||||
'passwordKeys': passwordKeys.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'halkyon_base.DeviceKeys',
|
||||
'encryptionPublicKey': encryptionPublicKey.toJson(),
|
||||
'signingPublicKey': signingPublicKey.toJson(),
|
||||
'passwordKeys': passwordKeys.toJsonForProtocol(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _is.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _DeviceKeysImpl extends DeviceKeys {
|
||||
_DeviceKeysImpl({
|
||||
required _idt.ByteData encryptionPublicKey,
|
||||
required _idt.ByteData signingPublicKey,
|
||||
required _i2rxumte.WrappedKeys passwordKeys,
|
||||
}) : super._(
|
||||
encryptionPublicKey: encryptionPublicKey,
|
||||
signingPublicKey: signingPublicKey,
|
||||
passwordKeys: passwordKeys,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [DeviceKeys]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
@override
|
||||
DeviceKeys copyWith({
|
||||
_idt.ByteData? encryptionPublicKey,
|
||||
_idt.ByteData? signingPublicKey,
|
||||
_i2rxumte.WrappedKeys? passwordKeys,
|
||||
}) {
|
||||
return DeviceKeys(
|
||||
encryptionPublicKey:
|
||||
encryptionPublicKey ?? this.encryptionPublicKey.clone(),
|
||||
signingPublicKey: signingPublicKey ?? this.signingPublicKey.clone(),
|
||||
passwordKeys: passwordKeys ?? this.passwordKeys.copyWith(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
/* 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:halkyon_base_server/src/generated/protocol.dart' as _ibewtqgw;
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
import '../password/password_costs.dart' as _ioe6tior;
|
||||
|
||||
/// What a signed-in device needs to prove the password of an account that
|
||||
/// signs in with SimpleLogin (Halkyon Learn's ADR 0020, D19): the salt and
|
||||
/// costs it derives the password's keys with, and a nonce it signs with the
|
||||
/// password's proof key, within two minutes, once.
|
||||
abstract class KeysChallenge
|
||||
implements _is.SerializableModel, _is.ProtocolSerialization {
|
||||
KeysChallenge._({
|
||||
required this.challenge,
|
||||
required this.nonce,
|
||||
required this.salt,
|
||||
required this.costs,
|
||||
});
|
||||
|
||||
factory KeysChallenge({
|
||||
required _is.UuidValue challenge,
|
||||
required _idt.ByteData nonce,
|
||||
required _idt.ByteData salt,
|
||||
required _ioe6tior.PasswordCosts costs,
|
||||
}) = _KeysChallengeImpl;
|
||||
|
||||
factory KeysChallenge.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return KeysChallenge(
|
||||
challenge: _is.UuidValueJsonExtension.fromJson(
|
||||
jsonSerialization['challenge'],
|
||||
),
|
||||
nonce: _is.ByteDataJsonExtension.fromJson(jsonSerialization['nonce']),
|
||||
salt: _is.ByteDataJsonExtension.fromJson(jsonSerialization['salt']),
|
||||
costs: _ibewtqgw.Protocol().deserialize<_ioe6tior.PasswordCosts>(
|
||||
jsonSerialization['costs'],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_is.UuidValue challenge;
|
||||
|
||||
_idt.ByteData nonce;
|
||||
|
||||
_idt.ByteData salt;
|
||||
|
||||
_ioe6tior.PasswordCosts costs;
|
||||
|
||||
/// Returns a shallow copy of this [KeysChallenge]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
KeysChallenge copyWith({
|
||||
_is.UuidValue? challenge,
|
||||
_idt.ByteData? nonce,
|
||||
_idt.ByteData? salt,
|
||||
_ioe6tior.PasswordCosts? costs,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'halkyon_base.KeysChallenge',
|
||||
'challenge': challenge.toJson(),
|
||||
'nonce': nonce.toJson(),
|
||||
'salt': salt.toJson(),
|
||||
'costs': costs.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'halkyon_base.KeysChallenge',
|
||||
'challenge': challenge.toJson(),
|
||||
'nonce': nonce.toJson(),
|
||||
'salt': salt.toJson(),
|
||||
'costs': costs.toJsonForProtocol(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _is.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _KeysChallengeImpl extends KeysChallenge {
|
||||
_KeysChallengeImpl({
|
||||
required _is.UuidValue challenge,
|
||||
required _idt.ByteData nonce,
|
||||
required _idt.ByteData salt,
|
||||
required _ioe6tior.PasswordCosts costs,
|
||||
}) : super._(
|
||||
challenge: challenge,
|
||||
nonce: nonce,
|
||||
salt: salt,
|
||||
costs: costs,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [KeysChallenge]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
@override
|
||||
KeysChallenge copyWith({
|
||||
_is.UuidValue? challenge,
|
||||
_idt.ByteData? nonce,
|
||||
_idt.ByteData? salt,
|
||||
_ioe6tior.PasswordCosts? costs,
|
||||
}) {
|
||||
return KeysChallenge(
|
||||
challenge: challenge ?? this.challenge,
|
||||
nonce: nonce ?? this.nonce.clone(),
|
||||
salt: salt ?? this.salt.clone(),
|
||||
costs: costs ?? this.costs.copyWith(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
/* 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/serverpod.dart' as _is;
|
||||
|
||||
/// A proof of the password of an account that signs in with SimpleLogin
|
||||
/// (Halkyon Learn's ADR 0020, D19): the signature by the password's proof
|
||||
/// key over the challenge's nonce, bound to the base URL the device
|
||||
/// addressed and to what it is for (`keyProofMessage`).
|
||||
abstract class KeysProof
|
||||
implements _is.SerializableModel, _is.ProtocolSerialization {
|
||||
KeysProof._({
|
||||
required this.challenge,
|
||||
required this.signature,
|
||||
required this.instanceUrl,
|
||||
});
|
||||
|
||||
factory KeysProof({
|
||||
required _is.UuidValue challenge,
|
||||
required _idt.ByteData signature,
|
||||
required String instanceUrl,
|
||||
}) = _KeysProofImpl;
|
||||
|
||||
factory KeysProof.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return KeysProof(
|
||||
challenge: _is.UuidValueJsonExtension.fromJson(
|
||||
jsonSerialization['challenge'],
|
||||
),
|
||||
signature: _is.ByteDataJsonExtension.fromJson(
|
||||
jsonSerialization['signature'],
|
||||
),
|
||||
instanceUrl: jsonSerialization['instanceUrl'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
_is.UuidValue challenge;
|
||||
|
||||
/// Ed25519's signature, 64 bytes.
|
||||
_idt.ByteData signature;
|
||||
|
||||
/// The instance's base URL the device addressed (scheme, host, port,
|
||||
/// path): the server it reached checks it is its own before the
|
||||
/// register binds the proof to it.
|
||||
String instanceUrl;
|
||||
|
||||
/// Returns a shallow copy of this [KeysProof]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
KeysProof copyWith({
|
||||
_is.UuidValue? challenge,
|
||||
_idt.ByteData? signature,
|
||||
String? instanceUrl,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'halkyon_base.KeysProof',
|
||||
'challenge': challenge.toJson(),
|
||||
'signature': signature.toJson(),
|
||||
'instanceUrl': instanceUrl,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'halkyon_base.KeysProof',
|
||||
'challenge': challenge.toJson(),
|
||||
'signature': signature.toJson(),
|
||||
'instanceUrl': instanceUrl,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _is.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _KeysProofImpl extends KeysProof {
|
||||
_KeysProofImpl({
|
||||
required _is.UuidValue challenge,
|
||||
required _idt.ByteData signature,
|
||||
required String instanceUrl,
|
||||
}) : super._(
|
||||
challenge: challenge,
|
||||
signature: signature,
|
||||
instanceUrl: instanceUrl,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [KeysProof]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
@override
|
||||
KeysProof copyWith({
|
||||
_is.UuidValue? challenge,
|
||||
_idt.ByteData? signature,
|
||||
String? instanceUrl,
|
||||
}) {
|
||||
return KeysProof(
|
||||
challenge: challenge ?? this.challenge,
|
||||
signature: signature ?? this.signature.clone(),
|
||||
instanceUrl: instanceUrl ?? this.instanceUrl,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
/* 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: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;
|
||||
|
||||
/// What a device sends to give an account that signs in with SimpleLogin
|
||||
/// its keys (Halkyon Learn's ADR 0020, 6 and D19), all made on the device:
|
||||
/// the salt and costs it derived the password's keys with, the public half
|
||||
/// of the password's proof key, the account's public keys, and its private
|
||||
/// keys wrapped under the password's key and the recovery phrase's, with
|
||||
/// the phrase's proof key. As ADR 0017's `PasswordSetup`, without SRP's
|
||||
/// verifier: nothing of it signs in, and nothing decrypts without the
|
||||
/// password or the phrase.
|
||||
abstract class KeysSetup
|
||||
implements _is.SerializableModel, _is.ProtocolSerialization {
|
||||
KeysSetup._({
|
||||
required this.salt,
|
||||
required this.costs,
|
||||
required this.passwordProofKey,
|
||||
required this.encryptionPublicKey,
|
||||
required this.signingPublicKey,
|
||||
required this.passwordKeys,
|
||||
required this.recoveryKeys,
|
||||
required this.recoveryProofKey,
|
||||
});
|
||||
|
||||
factory KeysSetup({
|
||||
required _idt.ByteData salt,
|
||||
required _ioe6tior.PasswordCosts costs,
|
||||
required _idt.ByteData passwordProofKey,
|
||||
required _idt.ByteData encryptionPublicKey,
|
||||
required _idt.ByteData signingPublicKey,
|
||||
required _i2rxumte.WrappedKeys passwordKeys,
|
||||
required _i2rxumte.WrappedKeys recoveryKeys,
|
||||
required _idt.ByteData recoveryProofKey,
|
||||
}) = _KeysSetupImpl;
|
||||
|
||||
factory KeysSetup.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return KeysSetup(
|
||||
salt: _is.ByteDataJsonExtension.fromJson(jsonSerialization['salt']),
|
||||
costs: _ibewtqgw.Protocol().deserialize<_ioe6tior.PasswordCosts>(
|
||||
jsonSerialization['costs'],
|
||||
),
|
||||
passwordProofKey: _is.ByteDataJsonExtension.fromJson(
|
||||
jsonSerialization['passwordProofKey'],
|
||||
),
|
||||
encryptionPublicKey: _is.ByteDataJsonExtension.fromJson(
|
||||
jsonSerialization['encryptionPublicKey'],
|
||||
),
|
||||
signingPublicKey: _is.ByteDataJsonExtension.fromJson(
|
||||
jsonSerialization['signingPublicKey'],
|
||||
),
|
||||
passwordKeys: _ibewtqgw.Protocol().deserialize<_i2rxumte.WrappedKeys>(
|
||||
jsonSerialization['passwordKeys'],
|
||||
),
|
||||
recoveryKeys: _ibewtqgw.Protocol().deserialize<_i2rxumte.WrappedKeys>(
|
||||
jsonSerialization['recoveryKeys'],
|
||||
),
|
||||
recoveryProofKey: _is.ByteDataJsonExtension.fromJson(
|
||||
jsonSerialization['recoveryProofKey'],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_idt.ByteData salt;
|
||||
|
||||
_ioe6tior.PasswordCosts costs;
|
||||
|
||||
/// The public half of the password's proof key (Ed25519).
|
||||
_idt.ByteData passwordProofKey;
|
||||
|
||||
_idt.ByteData encryptionPublicKey;
|
||||
|
||||
_idt.ByteData signingPublicKey;
|
||||
|
||||
_i2rxumte.WrappedKeys passwordKeys;
|
||||
|
||||
_i2rxumte.WrappedKeys recoveryKeys;
|
||||
|
||||
/// The public half of the recovery phrase's proof key (Ed25519, from the
|
||||
/// phrase alone).
|
||||
_idt.ByteData recoveryProofKey;
|
||||
|
||||
/// Returns a shallow copy of this [KeysSetup]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
KeysSetup copyWith({
|
||||
_idt.ByteData? salt,
|
||||
_ioe6tior.PasswordCosts? costs,
|
||||
_idt.ByteData? passwordProofKey,
|
||||
_idt.ByteData? encryptionPublicKey,
|
||||
_idt.ByteData? signingPublicKey,
|
||||
_i2rxumte.WrappedKeys? passwordKeys,
|
||||
_i2rxumte.WrappedKeys? recoveryKeys,
|
||||
_idt.ByteData? recoveryProofKey,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'halkyon_base.KeysSetup',
|
||||
'salt': salt.toJson(),
|
||||
'costs': costs.toJson(),
|
||||
'passwordProofKey': passwordProofKey.toJson(),
|
||||
'encryptionPublicKey': encryptionPublicKey.toJson(),
|
||||
'signingPublicKey': signingPublicKey.toJson(),
|
||||
'passwordKeys': passwordKeys.toJson(),
|
||||
'recoveryKeys': recoveryKeys.toJson(),
|
||||
'recoveryProofKey': recoveryProofKey.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'halkyon_base.KeysSetup',
|
||||
'salt': salt.toJson(),
|
||||
'costs': costs.toJsonForProtocol(),
|
||||
'passwordProofKey': passwordProofKey.toJson(),
|
||||
'encryptionPublicKey': encryptionPublicKey.toJson(),
|
||||
'signingPublicKey': signingPublicKey.toJson(),
|
||||
'passwordKeys': passwordKeys.toJsonForProtocol(),
|
||||
'recoveryKeys': recoveryKeys.toJsonForProtocol(),
|
||||
'recoveryProofKey': recoveryProofKey.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _is.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _KeysSetupImpl extends KeysSetup {
|
||||
_KeysSetupImpl({
|
||||
required _idt.ByteData salt,
|
||||
required _ioe6tior.PasswordCosts costs,
|
||||
required _idt.ByteData passwordProofKey,
|
||||
required _idt.ByteData encryptionPublicKey,
|
||||
required _idt.ByteData signingPublicKey,
|
||||
required _i2rxumte.WrappedKeys passwordKeys,
|
||||
required _i2rxumte.WrappedKeys recoveryKeys,
|
||||
required _idt.ByteData recoveryProofKey,
|
||||
}) : super._(
|
||||
salt: salt,
|
||||
costs: costs,
|
||||
passwordProofKey: passwordProofKey,
|
||||
encryptionPublicKey: encryptionPublicKey,
|
||||
signingPublicKey: signingPublicKey,
|
||||
passwordKeys: passwordKeys,
|
||||
recoveryKeys: recoveryKeys,
|
||||
recoveryProofKey: recoveryProofKey,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [KeysSetup]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
@override
|
||||
KeysSetup copyWith({
|
||||
_idt.ByteData? salt,
|
||||
_ioe6tior.PasswordCosts? costs,
|
||||
_idt.ByteData? passwordProofKey,
|
||||
_idt.ByteData? encryptionPublicKey,
|
||||
_idt.ByteData? signingPublicKey,
|
||||
_i2rxumte.WrappedKeys? passwordKeys,
|
||||
_i2rxumte.WrappedKeys? recoveryKeys,
|
||||
_idt.ByteData? recoveryProofKey,
|
||||
}) {
|
||||
return KeysSetup(
|
||||
salt: salt ?? this.salt.clone(),
|
||||
costs: costs ?? this.costs.copyWith(),
|
||||
passwordProofKey: passwordProofKey ?? this.passwordProofKey.clone(),
|
||||
encryptionPublicKey:
|
||||
encryptionPublicKey ?? this.encryptionPublicKey.clone(),
|
||||
signingPublicKey: signingPublicKey ?? this.signingPublicKey.clone(),
|
||||
passwordKeys: passwordKeys ?? this.passwordKeys.copyWith(),
|
||||
recoveryKeys: recoveryKeys ?? this.recoveryKeys.copyWith(),
|
||||
recoveryProofKey: recoveryProofKey ?? this.recoveryProofKey.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -16,9 +16,11 @@ import 'package:serverpod/serverpod.dart' as _is;
|
|||
/// An account's password as the register keeps it (ADR 0017): nothing that
|
||||
/// signs in or decrypts in the password's place. The salt and Argon2id's
|
||||
/// costs the device derives the password's keys with, SRP's verifier
|
||||
/// (v = g^x mod N, PAD to N's length), the account's public keys, and its
|
||||
/// private keys wrapped under the password's key and again under the
|
||||
/// recovery phrase's, which only a device can open.
|
||||
/// (v = g^x mod N, PAD to N's length) or, for an account that signs in with
|
||||
/// SimpleLogin, the password's proof key (Halkyon Learn's ADR 0020, D19),
|
||||
/// the account's public keys, and its private keys wrapped under the
|
||||
/// password's key and again under the recovery phrase's, which only a
|
||||
/// device can open.
|
||||
abstract class AccountPassword
|
||||
implements _is.TableRow<int?>, _is.ProtocolSerialization {
|
||||
AccountPassword._({
|
||||
|
|
@ -28,7 +30,8 @@ abstract class AccountPassword
|
|||
required this.kdfAlgorithm,
|
||||
required this.kdfOpsLimit,
|
||||
required this.kdfMemLimit,
|
||||
required this.verifier,
|
||||
this.verifier,
|
||||
this.passwordProofKey,
|
||||
required this.encryptionPublicKey,
|
||||
required this.signingPublicKey,
|
||||
required this.passwordKeysNonce,
|
||||
|
|
@ -48,7 +51,8 @@ abstract class AccountPassword
|
|||
required String kdfAlgorithm,
|
||||
required int kdfOpsLimit,
|
||||
required int kdfMemLimit,
|
||||
required _idt.ByteData verifier,
|
||||
_idt.ByteData? verifier,
|
||||
_idt.ByteData? passwordProofKey,
|
||||
required _idt.ByteData encryptionPublicKey,
|
||||
required _idt.ByteData signingPublicKey,
|
||||
required _idt.ByteData passwordKeysNonce,
|
||||
|
|
@ -71,9 +75,14 @@ abstract class AccountPassword
|
|||
kdfAlgorithm: jsonSerialization['kdfAlgorithm'] as String,
|
||||
kdfOpsLimit: jsonSerialization['kdfOpsLimit'] as int,
|
||||
kdfMemLimit: jsonSerialization['kdfMemLimit'] as int,
|
||||
verifier: _is.ByteDataJsonExtension.fromJson(
|
||||
jsonSerialization['verifier'],
|
||||
),
|
||||
verifier: jsonSerialization['verifier'] == null
|
||||
? null
|
||||
: _is.ByteDataJsonExtension.fromJson(jsonSerialization['verifier']),
|
||||
passwordProofKey: jsonSerialization['passwordProofKey'] == null
|
||||
? null
|
||||
: _is.ByteDataJsonExtension.fromJson(
|
||||
jsonSerialization['passwordProofKey'],
|
||||
),
|
||||
encryptionPublicKey: _is.ByteDataJsonExtension.fromJson(
|
||||
jsonSerialization['encryptionPublicKey'],
|
||||
),
|
||||
|
|
@ -134,8 +143,16 @@ abstract class AccountPassword
|
|||
/// Argon2id's memory, in bytes.
|
||||
int kdfMemLimit;
|
||||
|
||||
/// SRP's verifier, big-endian, as long as N.
|
||||
_idt.ByteData verifier;
|
||||
/// SRP's verifier, big-endian, as long as N; null for an account that
|
||||
/// signs in with SimpleLogin, which proves the password with
|
||||
/// [passwordProofKey] instead.
|
||||
_idt.ByteData? verifier;
|
||||
|
||||
/// The public half of the password's own Ed25519 proof key, derived from
|
||||
/// the password alone (halkyon_keys' `PasswordKeys.proofPublicKey`): what
|
||||
/// proves the password of an account without SRP's verifier. Null for
|
||||
/// one with it.
|
||||
_idt.ByteData? passwordProofKey;
|
||||
|
||||
/// The account's X25519 public key, which data keys are sealed to.
|
||||
_idt.ByteData encryptionPublicKey;
|
||||
|
|
@ -187,6 +204,7 @@ abstract class AccountPassword
|
|||
int? kdfOpsLimit,
|
||||
int? kdfMemLimit,
|
||||
_idt.ByteData? verifier,
|
||||
_idt.ByteData? passwordProofKey,
|
||||
_idt.ByteData? encryptionPublicKey,
|
||||
_idt.ByteData? signingPublicKey,
|
||||
_idt.ByteData? passwordKeysNonce,
|
||||
|
|
@ -208,7 +226,9 @@ abstract class AccountPassword
|
|||
'kdfAlgorithm': kdfAlgorithm,
|
||||
'kdfOpsLimit': kdfOpsLimit,
|
||||
'kdfMemLimit': kdfMemLimit,
|
||||
'verifier': verifier.toJson(),
|
||||
if (verifier != null) 'verifier': verifier?.toJson(),
|
||||
if (passwordProofKey != null)
|
||||
'passwordProofKey': passwordProofKey?.toJson(),
|
||||
'encryptionPublicKey': encryptionPublicKey.toJson(),
|
||||
'signingPublicKey': signingPublicKey.toJson(),
|
||||
'passwordKeysNonce': passwordKeysNonce.toJson(),
|
||||
|
|
@ -267,7 +287,8 @@ class _AccountPasswordImpl extends AccountPassword {
|
|||
required String kdfAlgorithm,
|
||||
required int kdfOpsLimit,
|
||||
required int kdfMemLimit,
|
||||
required _idt.ByteData verifier,
|
||||
_idt.ByteData? verifier,
|
||||
_idt.ByteData? passwordProofKey,
|
||||
required _idt.ByteData encryptionPublicKey,
|
||||
required _idt.ByteData signingPublicKey,
|
||||
required _idt.ByteData passwordKeysNonce,
|
||||
|
|
@ -286,6 +307,7 @@ class _AccountPasswordImpl extends AccountPassword {
|
|||
kdfOpsLimit: kdfOpsLimit,
|
||||
kdfMemLimit: kdfMemLimit,
|
||||
verifier: verifier,
|
||||
passwordProofKey: passwordProofKey,
|
||||
encryptionPublicKey: encryptionPublicKey,
|
||||
signingPublicKey: signingPublicKey,
|
||||
passwordKeysNonce: passwordKeysNonce,
|
||||
|
|
@ -309,7 +331,8 @@ class _AccountPasswordImpl extends AccountPassword {
|
|||
String? kdfAlgorithm,
|
||||
int? kdfOpsLimit,
|
||||
int? kdfMemLimit,
|
||||
_idt.ByteData? verifier,
|
||||
Object? verifier = _Undefined,
|
||||
Object? passwordProofKey = _Undefined,
|
||||
_idt.ByteData? encryptionPublicKey,
|
||||
_idt.ByteData? signingPublicKey,
|
||||
_idt.ByteData? passwordKeysNonce,
|
||||
|
|
@ -328,7 +351,10 @@ class _AccountPasswordImpl extends AccountPassword {
|
|||
kdfAlgorithm: kdfAlgorithm ?? this.kdfAlgorithm,
|
||||
kdfOpsLimit: kdfOpsLimit ?? this.kdfOpsLimit,
|
||||
kdfMemLimit: kdfMemLimit ?? this.kdfMemLimit,
|
||||
verifier: verifier ?? this.verifier.clone(),
|
||||
verifier: verifier is _idt.ByteData? ? verifier : this.verifier?.clone(),
|
||||
passwordProofKey: passwordProofKey is _idt.ByteData?
|
||||
? passwordProofKey
|
||||
: this.passwordProofKey?.clone(),
|
||||
encryptionPublicKey:
|
||||
encryptionPublicKey ?? this.encryptionPublicKey.clone(),
|
||||
signingPublicKey: signingPublicKey ?? this.signingPublicKey.clone(),
|
||||
|
|
@ -381,11 +407,19 @@ class AccountPasswordUpdateTable extends _is.UpdateTable<AccountPasswordTable> {
|
|||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<_idt.ByteData, _idt.ByteData> verifier(_idt.ByteData value) =>
|
||||
_is.ColumnValue(
|
||||
table.verifier,
|
||||
value,
|
||||
);
|
||||
_is.ColumnValue<_idt.ByteData, _idt.ByteData> verifier(
|
||||
_idt.ByteData? value,
|
||||
) => _is.ColumnValue(
|
||||
table.verifier,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<_idt.ByteData, _idt.ByteData> passwordProofKey(
|
||||
_idt.ByteData? value,
|
||||
) => _is.ColumnValue(
|
||||
table.passwordProofKey,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<_idt.ByteData, _idt.ByteData> encryptionPublicKey(
|
||||
_idt.ByteData value,
|
||||
|
|
@ -484,6 +518,10 @@ class AccountPasswordTable extends _is.Table<int?> {
|
|||
'verifier',
|
||||
this,
|
||||
);
|
||||
passwordProofKey = _is.ColumnByteData(
|
||||
'passwordProofKey',
|
||||
this,
|
||||
);
|
||||
encryptionPublicKey = _is.ColumnByteData(
|
||||
'encryptionPublicKey',
|
||||
this,
|
||||
|
|
@ -543,9 +581,17 @@ class AccountPasswordTable extends _is.Table<int?> {
|
|||
/// Argon2id's memory, in bytes.
|
||||
late final _is.ColumnInt kdfMemLimit;
|
||||
|
||||
/// SRP's verifier, big-endian, as long as N.
|
||||
/// SRP's verifier, big-endian, as long as N; null for an account that
|
||||
/// signs in with SimpleLogin, which proves the password with
|
||||
/// [passwordProofKey] instead.
|
||||
late final _is.ColumnByteData verifier;
|
||||
|
||||
/// The public half of the password's own Ed25519 proof key, derived from
|
||||
/// the password alone (halkyon_keys' `PasswordKeys.proofPublicKey`): what
|
||||
/// proves the password of an account without SRP's verifier. Null for
|
||||
/// one with it.
|
||||
late final _is.ColumnByteData passwordProofKey;
|
||||
|
||||
/// The account's X25519 public key, which data keys are sealed to.
|
||||
late final _is.ColumnByteData encryptionPublicKey;
|
||||
|
||||
|
|
@ -591,6 +637,7 @@ class AccountPasswordTable extends _is.Table<int?> {
|
|||
kdfOpsLimit,
|
||||
kdfMemLimit,
|
||||
verifier,
|
||||
passwordProofKey,
|
||||
encryptionPublicKey,
|
||||
signingPublicKey,
|
||||
passwordKeysNonce,
|
||||
|
|
|
|||
|
|
@ -23,7 +23,13 @@ enum SignInChallengePurpose implements _is.SerializableModel {
|
|||
/// Proving the password while signed in, to change it, set a new phrase
|
||||
/// or delete the account: counted per account, never toward the
|
||||
/// address's proofs, so strangers' guesses hold no signed-in owner back.
|
||||
change;
|
||||
change,
|
||||
|
||||
/// Proving the password of an account without SRP's verifier, signed in,
|
||||
/// by signing a nonce with the password's proof key (Halkyon Learn's ADR
|
||||
/// 0020, D19): to get the keys on a device, or for a change. Counted per
|
||||
/// session (`asOwnerProofTry`).
|
||||
passwordProof;
|
||||
|
||||
static SignInChallengePurpose fromJson(String name) {
|
||||
switch (name) {
|
||||
|
|
@ -33,6 +39,8 @@ enum SignInChallengePurpose implements _is.SerializableModel {
|
|||
return SignInChallengePurpose.keyProof;
|
||||
case 'change':
|
||||
return SignInChallengePurpose.change;
|
||||
case 'passwordProof':
|
||||
return SignInChallengePurpose.passwordProof;
|
||||
default:
|
||||
throw ArgumentError(
|
||||
'Value "$name" cannot be converted to "SignInChallengePurpose"',
|
||||
|
|
|
|||
|
|
@ -43,7 +43,11 @@ enum ProfileRefusal implements _is.SerializableModel {
|
|||
avatarInvalid,
|
||||
|
||||
/// The links were checked too often in the last hour.
|
||||
tooManyLinkChecks;
|
||||
tooManyLinkChecks,
|
||||
|
||||
/// The handles were checked too often in the last hour: their answers
|
||||
/// tell which handles exist.
|
||||
tooManyHandleChecks;
|
||||
|
||||
static ProfileRefusal fromJson(String name) {
|
||||
switch (name) {
|
||||
|
|
@ -67,6 +71,8 @@ enum ProfileRefusal implements _is.SerializableModel {
|
|||
return ProfileRefusal.avatarInvalid;
|
||||
case 'tooManyLinkChecks':
|
||||
return ProfileRefusal.tooManyLinkChecks;
|
||||
case 'tooManyHandleChecks':
|
||||
return ProfileRefusal.tooManyHandleChecks;
|
||||
default:
|
||||
throw ArgumentError(
|
||||
'Value "$name" cannot be converted to "ProfileRefusal"',
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@ 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;
|
||||
import 'keys/keys_challenge.dart' as _itebbsfc;
|
||||
import 'keys/keys_proof.dart' as _iviximyh;
|
||||
import 'keys/keys_setup.dart' as _iqdhlx3t;
|
||||
import 'password/account_password.dart' as _ip37g5t3;
|
||||
import 'password/account_reset.dart' as _i3fad0p6;
|
||||
import 'password/account_reset_kind.dart' as _i91ka1it;
|
||||
|
|
@ -74,6 +78,10 @@ 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';
|
||||
export 'keys/keys_challenge.dart';
|
||||
export 'keys/keys_proof.dart';
|
||||
export 'keys/keys_setup.dart';
|
||||
export 'password/account_password.dart';
|
||||
export 'password/account_reset.dart';
|
||||
export 'password/account_reset_kind.dart';
|
||||
|
|
@ -233,8 +241,14 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
_isp.ColumnDefinition(
|
||||
name: 'verifier',
|
||||
columnType: _isp.ColumnType.bytea,
|
||||
isNullable: false,
|
||||
dartType: 'dart:typed_data:ByteData',
|
||||
isNullable: true,
|
||||
dartType: 'dart:typed_data:ByteData?',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'passwordProofKey',
|
||||
columnType: _isp.ColumnType.bytea,
|
||||
isNullable: true,
|
||||
dartType: 'dart:typed_data:ByteData?',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'encryptionPublicKey',
|
||||
|
|
@ -1081,6 +1095,18 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
if (t == _i8jixqx6.InstanceBasics) {
|
||||
return _i8jixqx6.InstanceBasics.fromJson(data) as T;
|
||||
}
|
||||
if (t == _iz50cwqw.DeviceKeys) {
|
||||
return _iz50cwqw.DeviceKeys.fromJson(data) as T;
|
||||
}
|
||||
if (t == _itebbsfc.KeysChallenge) {
|
||||
return _itebbsfc.KeysChallenge.fromJson(data) as T;
|
||||
}
|
||||
if (t == _iviximyh.KeysProof) {
|
||||
return _iviximyh.KeysProof.fromJson(data) as T;
|
||||
}
|
||||
if (t == _iqdhlx3t.KeysSetup) {
|
||||
return _iqdhlx3t.KeysSetup.fromJson(data) as T;
|
||||
}
|
||||
if (t == _ip37g5t3.AccountPassword) {
|
||||
return _ip37g5t3.AccountPassword.fromJson(data) as T;
|
||||
}
|
||||
|
|
@ -1230,6 +1256,19 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
return (data != null ? _i8jixqx6.InstanceBasics.fromJson(data) : null)
|
||||
as T;
|
||||
}
|
||||
if (t == _is.getType<_iz50cwqw.DeviceKeys?>()) {
|
||||
return (data != null ? _iz50cwqw.DeviceKeys.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _is.getType<_itebbsfc.KeysChallenge?>()) {
|
||||
return (data != null ? _itebbsfc.KeysChallenge.fromJson(data) : null)
|
||||
as T;
|
||||
}
|
||||
if (t == _is.getType<_iviximyh.KeysProof?>()) {
|
||||
return (data != null ? _iviximyh.KeysProof.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _is.getType<_iqdhlx3t.KeysSetup?>()) {
|
||||
return (data != null ? _iqdhlx3t.KeysSetup.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _is.getType<_ip37g5t3.AccountPassword?>()) {
|
||||
return (data != null ? _ip37g5t3.AccountPassword.fromJson(data) : null)
|
||||
as T;
|
||||
|
|
@ -1436,6 +1475,10 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
_i0xfcu5n.GitHubSignInRefusedException => 'GitHubSignInRefusedException',
|
||||
_ie6tbh85.SignInWays => 'SignInWays',
|
||||
_i8jixqx6.InstanceBasics => 'InstanceBasics',
|
||||
_iz50cwqw.DeviceKeys => 'DeviceKeys',
|
||||
_itebbsfc.KeysChallenge => 'KeysChallenge',
|
||||
_iviximyh.KeysProof => 'KeysProof',
|
||||
_iqdhlx3t.KeysSetup => 'KeysSetup',
|
||||
_ip37g5t3.AccountPassword => 'AccountPassword',
|
||||
_i3fad0p6.AccountReset => 'AccountReset',
|
||||
_i91ka1it.AccountResetKind => 'AccountResetKind',
|
||||
|
|
@ -1507,6 +1550,14 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
return 'SignInWays';
|
||||
case _i8jixqx6.InstanceBasics():
|
||||
return 'InstanceBasics';
|
||||
case _iz50cwqw.DeviceKeys():
|
||||
return 'DeviceKeys';
|
||||
case _itebbsfc.KeysChallenge():
|
||||
return 'KeysChallenge';
|
||||
case _iviximyh.KeysProof():
|
||||
return 'KeysProof';
|
||||
case _iqdhlx3t.KeysSetup():
|
||||
return 'KeysSetup';
|
||||
case _ip37g5t3.AccountPassword():
|
||||
return 'AccountPassword';
|
||||
case _i3fad0p6.AccountReset():
|
||||
|
|
@ -1620,6 +1671,18 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
if (dataClassName == 'InstanceBasics') {
|
||||
return deserialize<_i8jixqx6.InstanceBasics>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'DeviceKeys') {
|
||||
return deserialize<_iz50cwqw.DeviceKeys>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'KeysChallenge') {
|
||||
return deserialize<_itebbsfc.KeysChallenge>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'KeysProof') {
|
||||
return deserialize<_iviximyh.KeysProof>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'KeysSetup') {
|
||||
return deserialize<_iqdhlx3t.KeysSetup>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'AccountPassword') {
|
||||
return deserialize<_ip37g5t3.AccountPassword>(data['data']);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ gitHubUpgrade:
|
|||
- upgrade:
|
||||
instance:
|
||||
- basics:
|
||||
keys:
|
||||
- begin:
|
||||
- unlock:
|
||||
accountRecovery:
|
||||
- resetAuthenticator:
|
||||
- beginKeyProof:
|
||||
|
|
@ -47,3 +50,5 @@ profiles:
|
|||
simpleLogin:
|
||||
- begin:
|
||||
- complete:
|
||||
- checkHandle:
|
||||
- signUp:
|
||||
|
|
|
|||
|
|
@ -26,12 +26,16 @@ abstract class SimpleLoginCompleted
|
|||
required this.outcome,
|
||||
this.session,
|
||||
this.suggestedHandle,
|
||||
});
|
||||
bool? hasKeys,
|
||||
this.handle,
|
||||
}) : hasKeys = hasKeys ?? false;
|
||||
|
||||
factory SimpleLoginCompleted({
|
||||
required _i2lb2chw.SimpleLoginOutcome outcome,
|
||||
_iacs.AuthSuccess? session,
|
||||
String? suggestedHandle,
|
||||
bool? hasKeys,
|
||||
String? handle,
|
||||
}) = _SimpleLoginCompletedImpl;
|
||||
|
||||
factory SimpleLoginCompleted.fromJson(
|
||||
|
|
@ -47,6 +51,10 @@ abstract class SimpleLoginCompleted
|
|||
jsonSerialization['session'],
|
||||
),
|
||||
suggestedHandle: jsonSerialization['suggestedHandle'] as String?,
|
||||
hasKeys: jsonSerialization['hasKeys'] == null
|
||||
? null
|
||||
: _is.BoolJsonExtension.fromJson(jsonSerialization['hasKeys']),
|
||||
handle: jsonSerialization['handle'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -56,6 +64,15 @@ abstract class SimpleLoginCompleted
|
|||
|
||||
String? suggestedHandle;
|
||||
|
||||
/// Whether the account signed in to, or SimpleLogin was added to, has its
|
||||
/// keys (`keys.unlock` opens them); one without (an account of before,
|
||||
/// adding SimpleLogin) makes them next.
|
||||
bool hasKeys;
|
||||
|
||||
/// The account's handle; null for one without (it chooses one with its
|
||||
/// keys).
|
||||
String? handle;
|
||||
|
||||
/// Returns a shallow copy of this [SimpleLoginCompleted]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
|
|
@ -63,6 +80,8 @@ abstract class SimpleLoginCompleted
|
|||
_i2lb2chw.SimpleLoginOutcome? outcome,
|
||||
_iacs.AuthSuccess? session,
|
||||
String? suggestedHandle,
|
||||
bool? hasKeys,
|
||||
String? handle,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
|
|
@ -71,6 +90,8 @@ abstract class SimpleLoginCompleted
|
|||
'outcome': outcome.toJson(),
|
||||
if (session != null) 'session': session?.toJson(),
|
||||
if (suggestedHandle != null) 'suggestedHandle': suggestedHandle,
|
||||
'hasKeys': hasKeys,
|
||||
if (handle != null) 'handle': handle,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -81,6 +102,8 @@ abstract class SimpleLoginCompleted
|
|||
'outcome': outcome.toJson(),
|
||||
if (session != null) 'session': session?.toJson(),
|
||||
if (suggestedHandle != null) 'suggestedHandle': suggestedHandle,
|
||||
'hasKeys': hasKeys,
|
||||
if (handle != null) 'handle': handle,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -97,10 +120,14 @@ class _SimpleLoginCompletedImpl extends SimpleLoginCompleted {
|
|||
required _i2lb2chw.SimpleLoginOutcome outcome,
|
||||
_iacs.AuthSuccess? session,
|
||||
String? suggestedHandle,
|
||||
bool? hasKeys,
|
||||
String? handle,
|
||||
}) : super._(
|
||||
outcome: outcome,
|
||||
session: session,
|
||||
suggestedHandle: suggestedHandle,
|
||||
hasKeys: hasKeys,
|
||||
handle: handle,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [SimpleLoginCompleted]
|
||||
|
|
@ -111,6 +138,8 @@ class _SimpleLoginCompletedImpl extends SimpleLoginCompleted {
|
|||
_i2lb2chw.SimpleLoginOutcome? outcome,
|
||||
Object? session = _Undefined,
|
||||
Object? suggestedHandle = _Undefined,
|
||||
bool? hasKeys,
|
||||
Object? handle = _Undefined,
|
||||
}) {
|
||||
return SimpleLoginCompleted(
|
||||
outcome: outcome ?? this.outcome,
|
||||
|
|
@ -120,6 +149,8 @@ class _SimpleLoginCompletedImpl extends SimpleLoginCompleted {
|
|||
suggestedHandle: suggestedHandle is String?
|
||||
? suggestedHandle
|
||||
: this.suggestedHandle,
|
||||
hasKeys: hasKeys ?? this.hasKeys,
|
||||
handle: handle is String? ? handle : this.handle,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,12 +23,16 @@ abstract class SimpleLoginProof
|
|||
required this.outcome,
|
||||
this.account,
|
||||
this.suggestedHandle,
|
||||
});
|
||||
bool? hasKeys,
|
||||
this.handle,
|
||||
}) : hasKeys = hasKeys ?? false;
|
||||
|
||||
factory SimpleLoginProof({
|
||||
required _i2lb2chw.SimpleLoginOutcome outcome,
|
||||
_is.UuidValue? account,
|
||||
String? suggestedHandle,
|
||||
bool? hasKeys,
|
||||
String? handle,
|
||||
}) = _SimpleLoginProofImpl;
|
||||
|
||||
factory SimpleLoginProof.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
|
|
@ -40,6 +44,10 @@ abstract class SimpleLoginProof
|
|||
? null
|
||||
: _is.UuidValueJsonExtension.fromJson(jsonSerialization['account']),
|
||||
suggestedHandle: jsonSerialization['suggestedHandle'] as String?,
|
||||
hasKeys: jsonSerialization['hasKeys'] == null
|
||||
? null
|
||||
: _is.BoolJsonExtension.fromJson(jsonSerialization['hasKeys']),
|
||||
handle: jsonSerialization['handle'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -49,6 +57,12 @@ abstract class SimpleLoginProof
|
|||
|
||||
String? suggestedHandle;
|
||||
|
||||
/// Whether the account has its keys.
|
||||
bool hasKeys;
|
||||
|
||||
/// The account's handle; null for none.
|
||||
String? handle;
|
||||
|
||||
/// Returns a shallow copy of this [SimpleLoginProof]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
|
|
@ -56,6 +70,8 @@ abstract class SimpleLoginProof
|
|||
_i2lb2chw.SimpleLoginOutcome? outcome,
|
||||
_is.UuidValue? account,
|
||||
String? suggestedHandle,
|
||||
bool? hasKeys,
|
||||
String? handle,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
|
|
@ -64,6 +80,8 @@ abstract class SimpleLoginProof
|
|||
'outcome': outcome.toJson(),
|
||||
if (account != null) 'account': account?.toJson(),
|
||||
if (suggestedHandle != null) 'suggestedHandle': suggestedHandle,
|
||||
'hasKeys': hasKeys,
|
||||
if (handle != null) 'handle': handle,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -85,10 +103,14 @@ class _SimpleLoginProofImpl extends SimpleLoginProof {
|
|||
required _i2lb2chw.SimpleLoginOutcome outcome,
|
||||
_is.UuidValue? account,
|
||||
String? suggestedHandle,
|
||||
bool? hasKeys,
|
||||
String? handle,
|
||||
}) : super._(
|
||||
outcome: outcome,
|
||||
account: account,
|
||||
suggestedHandle: suggestedHandle,
|
||||
hasKeys: hasKeys,
|
||||
handle: handle,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [SimpleLoginProof]
|
||||
|
|
@ -99,6 +121,8 @@ class _SimpleLoginProofImpl extends SimpleLoginProof {
|
|||
_i2lb2chw.SimpleLoginOutcome? outcome,
|
||||
Object? account = _Undefined,
|
||||
Object? suggestedHandle = _Undefined,
|
||||
bool? hasKeys,
|
||||
Object? handle = _Undefined,
|
||||
}) {
|
||||
return SimpleLoginProof(
|
||||
outcome: outcome ?? this.outcome,
|
||||
|
|
@ -106,6 +130,8 @@ class _SimpleLoginProofImpl extends SimpleLoginProof {
|
|||
suggestedHandle: suggestedHandle is String?
|
||||
? suggestedHandle
|
||||
: this.suggestedHandle,
|
||||
hasKeys: hasKeys ?? this.hasKeys,
|
||||
handle: handle is String? ? handle : this.handle,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
### The account's keys as a device gets them once it proved the password
|
||||
### (Halkyon Learn's ADR 0020, 7 and D19): its public keys, and its private
|
||||
### keys wrapped under the password's key, which the device opens.
|
||||
class: DeviceKeys
|
||||
fields:
|
||||
encryptionPublicKey: ByteData
|
||||
signingPublicKey: ByteData
|
||||
passwordKeys: WrappedKeys
|
||||
35
packages/halkyon_base_server/lib/src/keys/keys.dart
Normal file
35
packages/halkyon_base_server/lib/src/keys/keys.dart
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import 'package:halkyon_base_server/src/generated/protocol.dart';
|
||||
import 'package:halkyon_base_server/src/password/key_proof.dart';
|
||||
import 'package:halkyon_base_server/src/password/passwords.dart';
|
||||
import 'package:serverpod/serverpod.dart';
|
||||
|
||||
// The keys of an account that signs in with SimpleLogin, as the instance's
|
||||
// register keeps them (Halkyon Learn's ADR 0020, 6, 7 and D19): ADR 0017's
|
||||
// keys and their wrappings, the password proven with its own proof key in
|
||||
// place of SRP. Refusals are `PasswordRefusedException`s.
|
||||
|
||||
/// [person]'s keys for a device, once [proof] proved their password for it
|
||||
/// ([KeyProofPurpose.unlockKeys]): their public keys and their private keys
|
||||
/// wrapped under the password's key, which the device opens. So someone who
|
||||
/// took over the SimpleLogin account gets nothing to guess the password
|
||||
/// against but the proofs, which count. Refusals as `provePasswordKey`'s.
|
||||
Future<DeviceKeys> unlockKeys(
|
||||
Session session, {
|
||||
required UuidValue person,
|
||||
required KeysProof proof,
|
||||
}) async {
|
||||
final password = await provePasswordKey(
|
||||
session,
|
||||
person: person,
|
||||
proof: proof,
|
||||
purpose: KeyProofPurpose.unlockKeys,
|
||||
);
|
||||
return DeviceKeys(
|
||||
encryptionPublicKey: password.encryptionPublicKey,
|
||||
signingPublicKey: password.signingPublicKey,
|
||||
passwordKeys: WrappedKeys(
|
||||
nonce: password.passwordKeysNonce,
|
||||
cipherText: password.passwordKeysCipherText,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
### What a signed-in device needs to prove the password of an account that
|
||||
### signs in with SimpleLogin (Halkyon Learn's ADR 0020, D19): the salt and
|
||||
### costs it derives the password's keys with, and a nonce it signs with the
|
||||
### password's proof key, within two minutes, once.
|
||||
class: KeysChallenge
|
||||
fields:
|
||||
challenge: UuidValue
|
||||
nonce: ByteData
|
||||
salt: ByteData
|
||||
costs: PasswordCosts
|
||||
48
packages/halkyon_base_server/lib/src/keys/keys_endpoint.dart
Normal file
48
packages/halkyon_base_server/lib/src/keys/keys_endpoint.dart
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import 'package:halkyon_base_server/src/generated/protocol.dart';
|
||||
import 'package:halkyon_base_server/src/password/key_proof.dart';
|
||||
import 'package:halkyon_base_server/src/password/owner_proof_tries.dart';
|
||||
import 'package:halkyon_base_server/src/register/account_register.dart';
|
||||
import 'package:serverpod/serverpod.dart';
|
||||
import 'package:serverpod_auth_idp_server/core.dart';
|
||||
|
||||
/// The keys of an account that signs in with SimpleLogin (Halkyon Learn's
|
||||
/// ADR 0020, 7 and D19), for its signed-in devices: the password proven
|
||||
/// with its own proof key, which the instance's register checks (ADR 0016).
|
||||
/// Refusals are `PasswordRefusedException`s.
|
||||
class KeysEndpoint extends Endpoint {
|
||||
@override
|
||||
bool get requireLogin => true;
|
||||
|
||||
/// The first step of proving your password: the salt and costs your
|
||||
/// device derives the password's keys with, and a nonce to sign with its
|
||||
/// proof key, within two minutes, once. Refused for an account without
|
||||
/// such keys (`PasswordRefusal.noPassword`).
|
||||
Future<KeysChallenge> begin(Session session) =>
|
||||
AccountRegister.of(session.serverpod).beginPasswordKeyProof(
|
||||
session,
|
||||
session.authenticated!.authUserId,
|
||||
);
|
||||
|
||||
/// Your keys for this device, once [proof] proved your password: your
|
||||
/// public keys and your private keys wrapped under the password's key,
|
||||
/// which the device opens. The proof is bound to the base URL the device
|
||||
/// addressed here, and counts against this session: the tenth wrong in a
|
||||
/// row ends it (`PasswordRefusal.sessionEnded`), and you are warned.
|
||||
/// Refused for a wrong proof (`PasswordRefusal.wrongCredentials`) and a
|
||||
/// challenge unknown, used or too old (`PasswordRefusal.challengeExpired`).
|
||||
Future<DeviceKeys> unlock(Session session, KeysProof proof) {
|
||||
final pod = session.serverpod;
|
||||
final bound = proof.copyWith(
|
||||
instanceUrl: deviceInstanceUrlOn(pod, proof.instanceUrl),
|
||||
);
|
||||
return asOwnerProofTry(
|
||||
session,
|
||||
bound,
|
||||
() => AccountRegister.of(pod).unlockKeys(
|
||||
session,
|
||||
session.authenticated!.authUserId,
|
||||
bound,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
### A proof of the password of an account that signs in with SimpleLogin
|
||||
### (Halkyon Learn's ADR 0020, D19): the signature by the password's proof
|
||||
### key over the challenge's nonce, bound to the base URL the device
|
||||
### addressed and to what it is for (`keyProofMessage`).
|
||||
class: KeysProof
|
||||
fields:
|
||||
challenge: UuidValue
|
||||
### Ed25519's signature, 64 bytes.
|
||||
signature: ByteData
|
||||
### The instance's base URL the device addressed (scheme, host, port,
|
||||
### path): the server it reached checks it is its own before the
|
||||
### register binds the proof to it.
|
||||
instanceUrl: String
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
### What a device sends to give an account that signs in with SimpleLogin
|
||||
### its keys (Halkyon Learn's ADR 0020, 6 and D19), all made on the device:
|
||||
### the salt and costs it derived the password's keys with, the public half
|
||||
### of the password's proof key, the account's public keys, and its private
|
||||
### keys wrapped under the password's key and the recovery phrase's, with
|
||||
### the phrase's proof key. As ADR 0017's `PasswordSetup`, without SRP's
|
||||
### verifier: nothing of it signs in, and nothing decrypts without the
|
||||
### password or the phrase.
|
||||
class: KeysSetup
|
||||
fields:
|
||||
salt: ByteData
|
||||
costs: PasswordCosts
|
||||
### The public half of the password's proof key (Ed25519).
|
||||
passwordProofKey: ByteData
|
||||
encryptionPublicKey: ByteData
|
||||
signingPublicKey: ByteData
|
||||
passwordKeys: WrappedKeys
|
||||
recoveryKeys: WrappedKeys
|
||||
### The public half of the recovery phrase's proof key (Ed25519, from the
|
||||
### phrase alone).
|
||||
recoveryProofKey: ByteData
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue