identity work

This commit is contained in:
Christien Rioux 2023-07-09 00:07:21 -04:00
parent 9eff8f0cb4
commit ac58e1dea3
15 changed files with 814 additions and 147 deletions

View File

@ -1,3 +1,4 @@
export 'identity.dart'; export 'identity.dart';
export 'local_account.dart'; export 'local_account.dart';
export 'preferences.dart'; export 'preferences.dart';
export 'user_login.dart';

View File

@ -7,7 +7,8 @@ part 'identity.g.dart';
// Identity Key points to accounts associated with this identity // Identity Key points to accounts associated with this identity
// accounts field has a map of service name or uuid to account key pairs // accounts field has a map of service name or uuid to account key pairs
// DHT Schema: DFLT(1) // DHT Schema: DFLT(1)
// DHT Key (Private): identityPublicKey // DHT Key (Private): identityRecordKey
// DHT Owner Key: identityPublicKey
// DHT Secret: identitySecretKey (stored encrypted with unlock code in local table store) // DHT Secret: identitySecretKey (stored encrypted with unlock code in local table store)
@freezed @freezed
class Identity with _$Identity { class Identity with _$Identity {
@ -25,21 +26,43 @@ class Identity with _$Identity {
// Bidirectional Master<->Identity signature allows for // Bidirectional Master<->Identity signature allows for
// chain of identity ownership for account recovery process // chain of identity ownership for account recovery process
// //
// Backed by a DHT key at masterPublicKey, the secret is kept // Backed by a DHT key at masterRecordKey, the secret is kept
// completely offline and only written to upon account recovery // completely offline and only written to upon account recovery
// //
// DHT Schema: DFLT(1) // DHT Schema: DFLT(1)
// DHT Key (Public): masterPublicKey // DHT Record Key (Public): masterRecordKey
// DHT Secret: masterSecretKey (kept offline) // DHT Owner Key: masterPublicKey
// DHT Owner Secret: masterSecretKey (kept offline)
// Encryption: None // Encryption: None
@freezed @freezed
class IdentityMaster with _$IdentityMaster { class IdentityMaster with _$IdentityMaster {
const factory IdentityMaster( const factory IdentityMaster(
{required TypedKey identityPublicKey, {
required TypedKey masterPublicKey, // Private DHT record storing identity account mapping
required TypedKey identityRecordKey,
// Public key of identity
required PublicKey identityPublicKey,
// Public DHT record storing this structure for account recovery
required TypedKey masterRecordKey,
// Public key of master identity used to sign identity keys for recovery
required PublicKey masterPublicKey,
// Signature of identityRecordKey and identityPublicKey by masterPublicKey
required Signature identitySignature, required Signature identitySignature,
// Signature of masterRecordKey and masterPublicKey by identityPublicKey
required Signature masterSignature}) = _IdentityMaster; required Signature masterSignature}) = _IdentityMaster;
factory IdentityMaster.fromJson(Map<String, dynamic> json) => factory IdentityMaster.fromJson(Map<String, dynamic> json) =>
_$IdentityMasterFromJson(json); _$IdentityMasterFromJson(json);
} }
// Identity Master with secrets
// Not freezed because we never persist this class in its entirety
class IdentityMasterWithSecrets {
IdentityMaster identityMaster;
SecretKey masterSecret;
SecretKey identitySecret;
IdentityMasterWithSecrets(
{required this.identityMaster,
required this.masterSecret,
required this.identitySecret});
}

View File

@ -166,12 +166,17 @@ IdentityMaster _$IdentityMasterFromJson(Map<String, dynamic> json) {
/// @nodoc /// @nodoc
mixin _$IdentityMaster { mixin _$IdentityMaster {
Typed<FixedEncodedString43> get identityPublicKey => // Private DHT record storing identity account mapping
throw _privateConstructorUsedError; Typed<FixedEncodedString43> get identityRecordKey =>
Typed<FixedEncodedString43> get masterPublicKey => throw _privateConstructorUsedError; // Public key of identity
throw _privateConstructorUsedError; FixedEncodedString43 get identityPublicKey =>
throw _privateConstructorUsedError; // Public DHT record storing this structure for account recovery
Typed<FixedEncodedString43> get masterRecordKey =>
throw _privateConstructorUsedError; // Public key of master identity used to sign identity keys for recovery
FixedEncodedString43 get masterPublicKey =>
throw _privateConstructorUsedError; // Signature of identityRecordKey and identityPublicKey by masterPublicKey
FixedEncodedString86 get identitySignature => FixedEncodedString86 get identitySignature =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError; // Signature of masterRecordKey and masterPublicKey by identityPublicKey
FixedEncodedString86 get masterSignature => FixedEncodedString86 get masterSignature =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
@ -188,8 +193,10 @@ abstract class $IdentityMasterCopyWith<$Res> {
_$IdentityMasterCopyWithImpl<$Res, IdentityMaster>; _$IdentityMasterCopyWithImpl<$Res, IdentityMaster>;
@useResult @useResult
$Res call( $Res call(
{Typed<FixedEncodedString43> identityPublicKey, {Typed<FixedEncodedString43> identityRecordKey,
Typed<FixedEncodedString43> masterPublicKey, FixedEncodedString43 identityPublicKey,
Typed<FixedEncodedString43> masterRecordKey,
FixedEncodedString43 masterPublicKey,
FixedEncodedString86 identitySignature, FixedEncodedString86 identitySignature,
FixedEncodedString86 masterSignature}); FixedEncodedString86 masterSignature});
} }
@ -207,20 +214,30 @@ class _$IdentityMasterCopyWithImpl<$Res, $Val extends IdentityMaster>
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
Object? identityRecordKey = null,
Object? identityPublicKey = null, Object? identityPublicKey = null,
Object? masterRecordKey = null,
Object? masterPublicKey = null, Object? masterPublicKey = null,
Object? identitySignature = null, Object? identitySignature = null,
Object? masterSignature = null, Object? masterSignature = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
identityRecordKey: null == identityRecordKey
? _value.identityRecordKey
: identityRecordKey // ignore: cast_nullable_to_non_nullable
as Typed<FixedEncodedString43>,
identityPublicKey: null == identityPublicKey identityPublicKey: null == identityPublicKey
? _value.identityPublicKey ? _value.identityPublicKey
: identityPublicKey // ignore: cast_nullable_to_non_nullable : identityPublicKey // ignore: cast_nullable_to_non_nullable
as FixedEncodedString43,
masterRecordKey: null == masterRecordKey
? _value.masterRecordKey
: masterRecordKey // ignore: cast_nullable_to_non_nullable
as Typed<FixedEncodedString43>, as Typed<FixedEncodedString43>,
masterPublicKey: null == masterPublicKey masterPublicKey: null == masterPublicKey
? _value.masterPublicKey ? _value.masterPublicKey
: masterPublicKey // ignore: cast_nullable_to_non_nullable : masterPublicKey // ignore: cast_nullable_to_non_nullable
as Typed<FixedEncodedString43>, as FixedEncodedString43,
identitySignature: null == identitySignature identitySignature: null == identitySignature
? _value.identitySignature ? _value.identitySignature
: identitySignature // ignore: cast_nullable_to_non_nullable : identitySignature // ignore: cast_nullable_to_non_nullable
@ -242,8 +259,10 @@ abstract class _$$_IdentityMasterCopyWith<$Res>
@override @override
@useResult @useResult
$Res call( $Res call(
{Typed<FixedEncodedString43> identityPublicKey, {Typed<FixedEncodedString43> identityRecordKey,
Typed<FixedEncodedString43> masterPublicKey, FixedEncodedString43 identityPublicKey,
Typed<FixedEncodedString43> masterRecordKey,
FixedEncodedString43 masterPublicKey,
FixedEncodedString86 identitySignature, FixedEncodedString86 identitySignature,
FixedEncodedString86 masterSignature}); FixedEncodedString86 masterSignature});
} }
@ -259,20 +278,30 @@ class __$$_IdentityMasterCopyWithImpl<$Res>
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
Object? identityRecordKey = null,
Object? identityPublicKey = null, Object? identityPublicKey = null,
Object? masterRecordKey = null,
Object? masterPublicKey = null, Object? masterPublicKey = null,
Object? identitySignature = null, Object? identitySignature = null,
Object? masterSignature = null, Object? masterSignature = null,
}) { }) {
return _then(_$_IdentityMaster( return _then(_$_IdentityMaster(
identityRecordKey: null == identityRecordKey
? _value.identityRecordKey
: identityRecordKey // ignore: cast_nullable_to_non_nullable
as Typed<FixedEncodedString43>,
identityPublicKey: null == identityPublicKey identityPublicKey: null == identityPublicKey
? _value.identityPublicKey ? _value.identityPublicKey
: identityPublicKey // ignore: cast_nullable_to_non_nullable : identityPublicKey // ignore: cast_nullable_to_non_nullable
as FixedEncodedString43,
masterRecordKey: null == masterRecordKey
? _value.masterRecordKey
: masterRecordKey // ignore: cast_nullable_to_non_nullable
as Typed<FixedEncodedString43>, as Typed<FixedEncodedString43>,
masterPublicKey: null == masterPublicKey masterPublicKey: null == masterPublicKey
? _value.masterPublicKey ? _value.masterPublicKey
: masterPublicKey // ignore: cast_nullable_to_non_nullable : masterPublicKey // ignore: cast_nullable_to_non_nullable
as Typed<FixedEncodedString43>, as FixedEncodedString43,
identitySignature: null == identitySignature identitySignature: null == identitySignature
? _value.identitySignature ? _value.identitySignature
: identitySignature // ignore: cast_nullable_to_non_nullable : identitySignature // ignore: cast_nullable_to_non_nullable
@ -289,7 +318,9 @@ class __$$_IdentityMasterCopyWithImpl<$Res>
@JsonSerializable() @JsonSerializable()
class _$_IdentityMaster implements _IdentityMaster { class _$_IdentityMaster implements _IdentityMaster {
const _$_IdentityMaster( const _$_IdentityMaster(
{required this.identityPublicKey, {required this.identityRecordKey,
required this.identityPublicKey,
required this.masterRecordKey,
required this.masterPublicKey, required this.masterPublicKey,
required this.identitySignature, required this.identitySignature,
required this.masterSignature}); required this.masterSignature});
@ -297,18 +328,28 @@ class _$_IdentityMaster implements _IdentityMaster {
factory _$_IdentityMaster.fromJson(Map<String, dynamic> json) => factory _$_IdentityMaster.fromJson(Map<String, dynamic> json) =>
_$$_IdentityMasterFromJson(json); _$$_IdentityMasterFromJson(json);
// Private DHT record storing identity account mapping
@override @override
final Typed<FixedEncodedString43> identityPublicKey; final Typed<FixedEncodedString43> identityRecordKey;
// Public key of identity
@override @override
final Typed<FixedEncodedString43> masterPublicKey; final FixedEncodedString43 identityPublicKey;
// Public DHT record storing this structure for account recovery
@override
final Typed<FixedEncodedString43> masterRecordKey;
// Public key of master identity used to sign identity keys for recovery
@override
final FixedEncodedString43 masterPublicKey;
// Signature of identityRecordKey and identityPublicKey by masterPublicKey
@override @override
final FixedEncodedString86 identitySignature; final FixedEncodedString86 identitySignature;
// Signature of masterRecordKey and masterPublicKey by identityPublicKey
@override @override
final FixedEncodedString86 masterSignature; final FixedEncodedString86 masterSignature;
@override @override
String toString() { String toString() {
return 'IdentityMaster(identityPublicKey: $identityPublicKey, masterPublicKey: $masterPublicKey, identitySignature: $identitySignature, masterSignature: $masterSignature)'; return 'IdentityMaster(identityRecordKey: $identityRecordKey, identityPublicKey: $identityPublicKey, masterRecordKey: $masterRecordKey, masterPublicKey: $masterPublicKey, identitySignature: $identitySignature, masterSignature: $masterSignature)';
} }
@override @override
@ -316,8 +357,12 @@ class _$_IdentityMaster implements _IdentityMaster {
return identical(this, other) || return identical(this, other) ||
(other.runtimeType == runtimeType && (other.runtimeType == runtimeType &&
other is _$_IdentityMaster && other is _$_IdentityMaster &&
(identical(other.identityRecordKey, identityRecordKey) ||
other.identityRecordKey == identityRecordKey) &&
(identical(other.identityPublicKey, identityPublicKey) || (identical(other.identityPublicKey, identityPublicKey) ||
other.identityPublicKey == identityPublicKey) && other.identityPublicKey == identityPublicKey) &&
(identical(other.masterRecordKey, masterRecordKey) ||
other.masterRecordKey == masterRecordKey) &&
(identical(other.masterPublicKey, masterPublicKey) || (identical(other.masterPublicKey, masterPublicKey) ||
other.masterPublicKey == masterPublicKey) && other.masterPublicKey == masterPublicKey) &&
(identical(other.identitySignature, identitySignature) || (identical(other.identitySignature, identitySignature) ||
@ -328,8 +373,14 @@ class _$_IdentityMaster implements _IdentityMaster {
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
int get hashCode => Object.hash(runtimeType, identityPublicKey, int get hashCode => Object.hash(
masterPublicKey, identitySignature, masterSignature); runtimeType,
identityRecordKey,
identityPublicKey,
masterRecordKey,
masterPublicKey,
identitySignature,
masterSignature);
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
@ -347,21 +398,27 @@ class _$_IdentityMaster implements _IdentityMaster {
abstract class _IdentityMaster implements IdentityMaster { abstract class _IdentityMaster implements IdentityMaster {
const factory _IdentityMaster( const factory _IdentityMaster(
{required final Typed<FixedEncodedString43> identityPublicKey, {required final Typed<FixedEncodedString43> identityRecordKey,
required final Typed<FixedEncodedString43> masterPublicKey, required final FixedEncodedString43 identityPublicKey,
required final Typed<FixedEncodedString43> masterRecordKey,
required final FixedEncodedString43 masterPublicKey,
required final FixedEncodedString86 identitySignature, required final FixedEncodedString86 identitySignature,
required final FixedEncodedString86 masterSignature}) = _$_IdentityMaster; required final FixedEncodedString86 masterSignature}) = _$_IdentityMaster;
factory _IdentityMaster.fromJson(Map<String, dynamic> json) = factory _IdentityMaster.fromJson(Map<String, dynamic> json) =
_$_IdentityMaster.fromJson; _$_IdentityMaster.fromJson;
@override @override // Private DHT record storing identity account mapping
Typed<FixedEncodedString43> get identityPublicKey; Typed<FixedEncodedString43> get identityRecordKey;
@override @override // Public key of identity
Typed<FixedEncodedString43> get masterPublicKey; FixedEncodedString43 get identityPublicKey;
@override @override // Public DHT record storing this structure for account recovery
Typed<FixedEncodedString43> get masterRecordKey;
@override // Public key of master identity used to sign identity keys for recovery
FixedEncodedString43 get masterPublicKey;
@override // Signature of identityRecordKey and identityPublicKey by masterPublicKey
FixedEncodedString86 get identitySignature; FixedEncodedString86 get identitySignature;
@override @override // Signature of masterRecordKey and masterPublicKey by identityPublicKey
FixedEncodedString86 get masterSignature; FixedEncodedString86 get masterSignature;
@override @override
@JsonKey(ignore: true) @JsonKey(ignore: true)

View File

@ -20,10 +20,13 @@ Map<String, dynamic> _$$_IdentityToJson(_$_Identity instance) =>
_$_IdentityMaster _$$_IdentityMasterFromJson(Map<String, dynamic> json) => _$_IdentityMaster _$$_IdentityMasterFromJson(Map<String, dynamic> json) =>
_$_IdentityMaster( _$_IdentityMaster(
identityRecordKey:
Typed<FixedEncodedString43>.fromJson(json['identity_record_key']),
identityPublicKey: identityPublicKey:
Typed<FixedEncodedString43>.fromJson(json['identity_public_key']), FixedEncodedString43.fromJson(json['identity_public_key']),
masterPublicKey: masterRecordKey:
Typed<FixedEncodedString43>.fromJson(json['master_public_key']), Typed<FixedEncodedString43>.fromJson(json['master_record_key']),
masterPublicKey: FixedEncodedString43.fromJson(json['master_public_key']),
identitySignature: identitySignature:
FixedEncodedString86.fromJson(json['identity_signature']), FixedEncodedString86.fromJson(json['identity_signature']),
masterSignature: FixedEncodedString86.fromJson(json['master_signature']), masterSignature: FixedEncodedString86.fromJson(json['master_signature']),
@ -31,7 +34,9 @@ _$_IdentityMaster _$$_IdentityMasterFromJson(Map<String, dynamic> json) =>
Map<String, dynamic> _$$_IdentityMasterToJson(_$_IdentityMaster instance) => Map<String, dynamic> _$$_IdentityMasterToJson(_$_IdentityMaster instance) =>
<String, dynamic>{ <String, dynamic>{
'identity_record_key': instance.identityRecordKey.toJson(),
'identity_public_key': instance.identityPublicKey.toJson(), 'identity_public_key': instance.identityPublicKey.toJson(),
'master_record_key': instance.masterRecordKey.toJson(),
'master_public_key': instance.masterPublicKey.toJson(), 'master_public_key': instance.masterPublicKey.toJson(),
'identity_signature': instance.identitySignature.toJson(), 'identity_signature': instance.identitySignature.toJson(),
'master_signature': instance.masterSignature.toJson(), 'master_signature': instance.masterSignature.toJson(),

View File

@ -2,6 +2,7 @@ import 'dart:typed_data';
import 'package:change_case/change_case.dart'; import 'package:change_case/change_case.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:veilid/veilid.dart'; import 'package:veilid/veilid.dart';
import 'identity.dart'; import 'identity.dart';

View File

@ -0,0 +1,42 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:veilid/veilid.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
part 'user_login.freezed.dart';
part 'user_login.g.dart';
// Represents a currently logged in account
// User logins are stored in the user_logins tablestore table
// indexed by the accountMasterKey
@freezed
class UserLogin with _$UserLogin {
const factory UserLogin({
// Master public key for the user used to index the local accounts table
required TypedKey accountMasterKey,
// The identity secret as unlocked from the local accounts table
required TypedSecret secretKey,
// The time this login was most recently used
required Timestamp lastActive,
}) = _UserLogin;
factory UserLogin.fromJson(Map<String, dynamic> json) =>
_$UserLoginFromJson(json);
}
// Represents a set of user logins
// and the currently selected account
@freezed
class ActiveLogins with _$ActiveLogins {
const factory ActiveLogins({
// The list of current logged in accounts
required IList<UserLogin> userLogins,
// The current selected account indexed by master key
TypedKey? activeUserLogin,
}) = _ActiveLogins;
factory ActiveLogins.empty() =>
const ActiveLogins(userLogins: IListConst([]));
factory ActiveLogins.fromJson(Map<String, dynamic> json) =>
_$ActiveLoginsFromJson(json);
}

View File

@ -0,0 +1,369 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'user_login.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
UserLogin _$UserLoginFromJson(Map<String, dynamic> json) {
return _UserLogin.fromJson(json);
}
/// @nodoc
mixin _$UserLogin {
// Master public key for the user used to index the local accounts table
Typed<FixedEncodedString43> get accountMasterKey =>
throw _privateConstructorUsedError; // The identity secret as unlocked from the local accounts table
Typed<FixedEncodedString43> get secretKey =>
throw _privateConstructorUsedError; // The time this login was most recently used
Timestamp get lastActive => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$UserLoginCopyWith<UserLogin> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $UserLoginCopyWith<$Res> {
factory $UserLoginCopyWith(UserLogin value, $Res Function(UserLogin) then) =
_$UserLoginCopyWithImpl<$Res, UserLogin>;
@useResult
$Res call(
{Typed<FixedEncodedString43> accountMasterKey,
Typed<FixedEncodedString43> secretKey,
Timestamp lastActive});
}
/// @nodoc
class _$UserLoginCopyWithImpl<$Res, $Val extends UserLogin>
implements $UserLoginCopyWith<$Res> {
_$UserLoginCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? accountMasterKey = null,
Object? secretKey = null,
Object? lastActive = null,
}) {
return _then(_value.copyWith(
accountMasterKey: null == accountMasterKey
? _value.accountMasterKey
: accountMasterKey // ignore: cast_nullable_to_non_nullable
as Typed<FixedEncodedString43>,
secretKey: null == secretKey
? _value.secretKey
: secretKey // ignore: cast_nullable_to_non_nullable
as Typed<FixedEncodedString43>,
lastActive: null == lastActive
? _value.lastActive
: lastActive // ignore: cast_nullable_to_non_nullable
as Timestamp,
) as $Val);
}
}
/// @nodoc
abstract class _$$_UserLoginCopyWith<$Res> implements $UserLoginCopyWith<$Res> {
factory _$$_UserLoginCopyWith(
_$_UserLogin value, $Res Function(_$_UserLogin) then) =
__$$_UserLoginCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{Typed<FixedEncodedString43> accountMasterKey,
Typed<FixedEncodedString43> secretKey,
Timestamp lastActive});
}
/// @nodoc
class __$$_UserLoginCopyWithImpl<$Res>
extends _$UserLoginCopyWithImpl<$Res, _$_UserLogin>
implements _$$_UserLoginCopyWith<$Res> {
__$$_UserLoginCopyWithImpl(
_$_UserLogin _value, $Res Function(_$_UserLogin) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? accountMasterKey = null,
Object? secretKey = null,
Object? lastActive = null,
}) {
return _then(_$_UserLogin(
accountMasterKey: null == accountMasterKey
? _value.accountMasterKey
: accountMasterKey // ignore: cast_nullable_to_non_nullable
as Typed<FixedEncodedString43>,
secretKey: null == secretKey
? _value.secretKey
: secretKey // ignore: cast_nullable_to_non_nullable
as Typed<FixedEncodedString43>,
lastActive: null == lastActive
? _value.lastActive
: lastActive // ignore: cast_nullable_to_non_nullable
as Timestamp,
));
}
}
/// @nodoc
@JsonSerializable()
class _$_UserLogin implements _UserLogin {
const _$_UserLogin(
{required this.accountMasterKey,
required this.secretKey,
required this.lastActive});
factory _$_UserLogin.fromJson(Map<String, dynamic> json) =>
_$$_UserLoginFromJson(json);
// Master public key for the user used to index the local accounts table
@override
final Typed<FixedEncodedString43> accountMasterKey;
// The identity secret as unlocked from the local accounts table
@override
final Typed<FixedEncodedString43> secretKey;
// The time this login was most recently used
@override
final Timestamp lastActive;
@override
String toString() {
return 'UserLogin(accountMasterKey: $accountMasterKey, secretKey: $secretKey, lastActive: $lastActive)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_UserLogin &&
(identical(other.accountMasterKey, accountMasterKey) ||
other.accountMasterKey == accountMasterKey) &&
(identical(other.secretKey, secretKey) ||
other.secretKey == secretKey) &&
(identical(other.lastActive, lastActive) ||
other.lastActive == lastActive));
}
@JsonKey(ignore: true)
@override
int get hashCode =>
Object.hash(runtimeType, accountMasterKey, secretKey, lastActive);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$_UserLoginCopyWith<_$_UserLogin> get copyWith =>
__$$_UserLoginCopyWithImpl<_$_UserLogin>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$_UserLoginToJson(
this,
);
}
}
abstract class _UserLogin implements UserLogin {
const factory _UserLogin(
{required final Typed<FixedEncodedString43> accountMasterKey,
required final Typed<FixedEncodedString43> secretKey,
required final Timestamp lastActive}) = _$_UserLogin;
factory _UserLogin.fromJson(Map<String, dynamic> json) =
_$_UserLogin.fromJson;
@override // Master public key for the user used to index the local accounts table
Typed<FixedEncodedString43> get accountMasterKey;
@override // The identity secret as unlocked from the local accounts table
Typed<FixedEncodedString43> get secretKey;
@override // The time this login was most recently used
Timestamp get lastActive;
@override
@JsonKey(ignore: true)
_$$_UserLoginCopyWith<_$_UserLogin> get copyWith =>
throw _privateConstructorUsedError;
}
ActiveLogins _$ActiveLoginsFromJson(Map<String, dynamic> json) {
return _ActiveLogins.fromJson(json);
}
/// @nodoc
mixin _$ActiveLogins {
// The list of current logged in accounts
IList<UserLogin> get userLogins =>
throw _privateConstructorUsedError; // The current selected account indexed by master key
Typed<FixedEncodedString43>? get activeUserLogin =>
throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ActiveLoginsCopyWith<ActiveLogins> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ActiveLoginsCopyWith<$Res> {
factory $ActiveLoginsCopyWith(
ActiveLogins value, $Res Function(ActiveLogins) then) =
_$ActiveLoginsCopyWithImpl<$Res, ActiveLogins>;
@useResult
$Res call(
{IList<UserLogin> userLogins,
Typed<FixedEncodedString43>? activeUserLogin});
}
/// @nodoc
class _$ActiveLoginsCopyWithImpl<$Res, $Val extends ActiveLogins>
implements $ActiveLoginsCopyWith<$Res> {
_$ActiveLoginsCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? userLogins = null,
Object? activeUserLogin = freezed,
}) {
return _then(_value.copyWith(
userLogins: null == userLogins
? _value.userLogins
: userLogins // ignore: cast_nullable_to_non_nullable
as IList<UserLogin>,
activeUserLogin: freezed == activeUserLogin
? _value.activeUserLogin
: activeUserLogin // ignore: cast_nullable_to_non_nullable
as Typed<FixedEncodedString43>?,
) as $Val);
}
}
/// @nodoc
abstract class _$$_ActiveLoginsCopyWith<$Res>
implements $ActiveLoginsCopyWith<$Res> {
factory _$$_ActiveLoginsCopyWith(
_$_ActiveLogins value, $Res Function(_$_ActiveLogins) then) =
__$$_ActiveLoginsCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{IList<UserLogin> userLogins,
Typed<FixedEncodedString43>? activeUserLogin});
}
/// @nodoc
class __$$_ActiveLoginsCopyWithImpl<$Res>
extends _$ActiveLoginsCopyWithImpl<$Res, _$_ActiveLogins>
implements _$$_ActiveLoginsCopyWith<$Res> {
__$$_ActiveLoginsCopyWithImpl(
_$_ActiveLogins _value, $Res Function(_$_ActiveLogins) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? userLogins = null,
Object? activeUserLogin = freezed,
}) {
return _then(_$_ActiveLogins(
userLogins: null == userLogins
? _value.userLogins
: userLogins // ignore: cast_nullable_to_non_nullable
as IList<UserLogin>,
activeUserLogin: freezed == activeUserLogin
? _value.activeUserLogin
: activeUserLogin // ignore: cast_nullable_to_non_nullable
as Typed<FixedEncodedString43>?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$_ActiveLogins implements _ActiveLogins {
const _$_ActiveLogins({required this.userLogins, this.activeUserLogin});
factory _$_ActiveLogins.fromJson(Map<String, dynamic> json) =>
_$$_ActiveLoginsFromJson(json);
// The list of current logged in accounts
@override
final IList<UserLogin> userLogins;
// The current selected account indexed by master key
@override
final Typed<FixedEncodedString43>? activeUserLogin;
@override
String toString() {
return 'ActiveLogins(userLogins: $userLogins, activeUserLogin: $activeUserLogin)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_ActiveLogins &&
const DeepCollectionEquality()
.equals(other.userLogins, userLogins) &&
(identical(other.activeUserLogin, activeUserLogin) ||
other.activeUserLogin == activeUserLogin));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType,
const DeepCollectionEquality().hash(userLogins), activeUserLogin);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$_ActiveLoginsCopyWith<_$_ActiveLogins> get copyWith =>
__$$_ActiveLoginsCopyWithImpl<_$_ActiveLogins>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$_ActiveLoginsToJson(
this,
);
}
}
abstract class _ActiveLogins implements ActiveLogins {
const factory _ActiveLogins(
{required final IList<UserLogin> userLogins,
final Typed<FixedEncodedString43>? activeUserLogin}) = _$_ActiveLogins;
factory _ActiveLogins.fromJson(Map<String, dynamic> json) =
_$_ActiveLogins.fromJson;
@override // The list of current logged in accounts
IList<UserLogin> get userLogins;
@override // The current selected account indexed by master key
Typed<FixedEncodedString43>? get activeUserLogin;
@override
@JsonKey(ignore: true)
_$$_ActiveLoginsCopyWith<_$_ActiveLogins> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -0,0 +1,38 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'user_login.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$_UserLogin _$$_UserLoginFromJson(Map<String, dynamic> json) => _$_UserLogin(
accountMasterKey:
Typed<FixedEncodedString43>.fromJson(json['account_master_key']),
secretKey: Typed<FixedEncodedString43>.fromJson(json['secret_key']),
lastActive: Timestamp.fromJson(json['last_active']),
);
Map<String, dynamic> _$$_UserLoginToJson(_$_UserLogin instance) =>
<String, dynamic>{
'account_master_key': instance.accountMasterKey.toJson(),
'secret_key': instance.secretKey.toJson(),
'last_active': instance.lastActive.toJson(),
};
_$_ActiveLogins _$$_ActiveLoginsFromJson(Map<String, dynamic> json) =>
_$_ActiveLogins(
userLogins: IList<UserLogin>.fromJson(json['user_logins'],
(value) => UserLogin.fromJson(value as Map<String, dynamic>)),
activeUserLogin: json['active_user_login'] == null
? null
: Typed<FixedEncodedString43>.fromJson(json['active_user_login']),
);
Map<String, dynamic> _$$_ActiveLoginsToJson(_$_ActiveLogins instance) =>
<String, dynamic>{
'user_logins': instance.userLogins.toJson(
(value) => value.toJson(),
),
'active_user_login': instance.activeUserLogin?.toJson(),
};

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../state/auth.dart'; import '../state/active_logins_state.dart';
class LoginPage extends ConsumerWidget { class LoginPage extends ConsumerWidget {
const LoginPage({super.key}); const LoginPage({super.key});

View File

@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import '../pages/pages.dart'; import '../pages/pages.dart';
import '../state/auth.dart'; import '../state/active_logins_state.dart';
/// This notifier is meant to implement the [Listenable] our [GoRouter] needs. /// This notifier is meant to implement the [Listenable] our [GoRouter] needs.
/// ///

View File

@ -0,0 +1,107 @@
import 'dart:async';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:veilid/veilid.dart';
import '../entities/entities.dart';
part 'active_logins_state.g.dart';
@riverpod
class ActiveLoginsState extends _$ActiveLoginsState {
VeilidTableDB? _userLoginsTable;
ActiveLogins _activeLogins;
ActiveLoginsState() : _activeLogins = ActiveLogins.empty();
@override
FutureOr<ActiveLogins> build() async {
_userLoginsTable ??= await Veilid.instance.openTableDB("login_state", 1);
_activeLogins =
(await _userLoginsTable!.loadStringJson(0, "active_logins") ??
ActiveLogins.empty()) as ActiveLogins;
_persistenceRefreshLogic();
return _activeLogins;
}
/// Log out of active user
Future<void> logout() async {
// If no user is active, then logout does nothing
if (_activeLogins.activeUserLogin == null) {
return;
}
// Remove userlogin and set the active user to logged out
final newUserLogins = _activeLogins.userLogins.removeWhere(
(ul) => _activeLogins.activeUserLogin == ul.accountMasterKey);
_activeLogins = _activeLogins.copyWith(
activeUserLogin: null, userLogins: newUserLogins);
// Report changed state
state = AsyncValue.data(_activeLogins);
}
/// Log all users
Future<void> logoutAll() async {
// If no user is active, then logout does nothing
if (_activeLogins.activeUserLogin == null) {
return;
}
// Remove all userlogins and set the active user to logged out
_activeLogins = ActiveLogins.empty();
// Report changed state
state = AsyncValue.data(_activeLogins);
}
/// Log out specific user identified by its master public key
Future<void> logoutUser(TypedKey user) async {
// Remove userlogin and set the active user to logged out
final newUserLogins = _activeLogins.userLogins
.removeWhere((ul) => user == ul.accountMasterKey);
final newActiveUserLogin = _activeLogins.activeUserLogin == user
? null
: _activeLogins.activeUserLogin;
_activeLogins = ActiveLogins(
userLogins: newUserLogins, activeUserLogin: newActiveUserLogin);
// Report changed state
state = AsyncValue.data(_activeLogins);
}
/// Attempt a login and if successful make that user active
Future<void> login(String publicKey, String password) async {
state = await AsyncValue.guard<User?>(() async {
return Future.delayed(
networkRoundTripTime,
() => _dummyUser,
);
});
}
/// Internal method used to listen authentication state changes.
/// When the auth object is in a loading state, nothing happens.
/// When the auth object is in a error state, we choose to remove the token
/// Otherwise, we expect the current auth value to be reflected in our persistence API
void _persistenceRefreshLogic() {
ref.listenSelf((_, next) {
if (next.isLoading) return;
if (next.hasError) {
sharedPreferences.remove(_sharedPrefsKey);
return;
}
final val = next.requireValue;
final isAuthenticated = val == null;
isAuthenticated
? sharedPreferences.remove(_sharedPrefsKey)
: sharedPreferences.setString(_sharedPrefsKey, val.publicKey);
});
}
}
class UnauthorizedException implements Exception {
final String message;
const UnauthorizedException(this.message);
}

View File

@ -0,0 +1,25 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'active_logins_state.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$activeLoginsStateHash() => r'9b8795055e21f15f8fbf13534365725591311cf4';
/// See also [ActiveLoginsState].
@ProviderFor(ActiveLoginsState)
final activeLoginsStateProvider =
AutoDisposeAsyncNotifierProvider<ActiveLoginsState, ActiveLogins>.internal(
ActiveLoginsState.new,
name: r'activeLoginsStateProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$activeLoginsStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$ActiveLoginsState = AutoDisposeAsyncNotifier<ActiveLogins>;
// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions

View File

@ -1,110 +0,0 @@
import 'dart:async';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:shared_preferences/shared_preferences.dart';
class User {
final String publicKey;
final String secretKey;
const User(this.publicKey, this.secretKey);
}
/// A mock of an Authenticated User
const _dummyUser = User("", "");
/// XXXX THIS IS TOTALLY BOGUS FOR NOW
/// This notifier holds and handles the authentication state of the application
class AuthNotifier extends AutoDisposeAsyncNotifier<User?> {
late SharedPreferences sharedPreferences;
static const _sharedPrefsKey = 'token';
/// Mock of the duration of a network request
@override
FutureOr<User?> build() async {
sharedPreferences = await SharedPreferences.getInstance();
_persistenceRefreshLogic();
return await _loginRecoveryAttempt();
}
/// Tries to perform a login with the saved token on the persistant storage.
/// If _anything_ goes wrong, deletes the internal token and returns a [User.signedOut].
Future<User?> _loginRecoveryAttempt() async {
try {
final savedToken = sharedPreferences.getString(_sharedPrefsKey);
if (savedToken == null) {
throw const UnauthorizedException(
"Couldn't find the authentication token");
}
return await _loginWithToken(savedToken);
} catch (_, __) {
await sharedPreferences.remove(_sharedPrefsKey);
return null;
}
}
/// Mock of a request performed on logout (might be common, or not, whatevs).
Future<void> logout() async {
await Future.delayed(networkRoundTripTime);
state = const AsyncValue.data(null);
}
/// Mock of a successful login attempt, which results come from the network.
Future<void> login(String publicKey, String password) async {
state = await AsyncValue.guard<User?>(() async {
return Future.delayed(
networkRoundTripTime,
() => _dummyUser,
);
});
}
/// Mock of a login request performed with a saved token.
/// If such request fails, this method will throw an [UnauthorizedException].
Future<User> _loginWithToken(String token) async {
final logInAttempt = await Future.delayed(
networkRoundTripTime,
() => true,
);
if (logInAttempt) return _dummyUser;
throw const UnauthorizedException('401 Unauthorized or something');
}
/// Internal method used to listen authentication state changes.
/// When the auth object is in a loading state, nothing happens.
/// When the auth object is in a error state, we choose to remove the token
/// Otherwise, we expect the current auth value to be reflected in our persistence API
void _persistenceRefreshLogic() {
ref.listenSelf((_, next) {
if (next.isLoading) return;
if (next.hasError) {
sharedPreferences.remove(_sharedPrefsKey);
return;
}
final val = next.requireValue;
final isAuthenticated = val == null;
isAuthenticated
? sharedPreferences.remove(_sharedPrefsKey)
: sharedPreferences.setString(_sharedPrefsKey, val.publicKey);
});
}
}
final authNotifierProvider =
AutoDisposeAsyncNotifierProvider<AuthNotifier, User?>(() {
return AuthNotifier();
});
class UnauthorizedException implements Exception {
final String message;
const UnauthorizedException(this.message);
}
/// Mock of the duration of a network request
const networkRoundTripTime = Duration(milliseconds: 750);

View File

@ -0,0 +1,109 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:veilid/veilid.dart';
import '../entities/entities.dart';
import '../entities/proto.dart' as proto;
part 'local_account_manager.g.dart';
// Local account manager
class LocalAccountManager {
final VeilidTableDB _localAccountsTable;
final IList<LocalAccount> _localAccounts;
const LocalAccountManager(
{required VeilidTableDB localAccountsTable,
required IList<LocalAccount> localAccounts})
: _localAccountsTable = localAccountsTable,
_localAccounts = localAccounts;
/// Gets or creates a local account manager
static Future<LocalAccountManager> open() async {
final localAccountsTable =
await Veilid.instance.openTableDB("local_account_manager", 1);
final localAccounts =
(await localAccountsTable.loadStringJson(0, "local_accounts") ??
const IListConst([])) as IList<LocalAccount>;
return LocalAccountManager(
localAccountsTable: localAccountsTable, localAccounts: localAccounts);
}
/// Flush things to storage
Future<void> flush() async {}
/// Creates a new master identity and returns it with its secrets
Future<IdentityMasterWithSecrets> newIdentityMaster() async {
final dhtctx = (await Veilid.instance.routingContext())
.withPrivacy()
.withSequencing(Sequencing.ensureOrdered);
final masterRec =
await dhtctx.createDHTRecord(const DHTSchema.dflt(oCnt: 1));
final identityRec =
await dhtctx.createDHTRecord(const DHTSchema.dflt(oCnt: 1));
final crypto = await Veilid.instance.bestCryptoSystem();
assert(masterRec.key.kind == crypto.kind());
assert(identityRec.key.kind == crypto.kind());
// IdentityMaster
final masterRecordKey = masterRec.key;
final masterPublicKey = masterRec.owner;
final masterSecret = masterRec.ownerSecret!;
final masterSigBuf = masterRecordKey.decode()
..addAll(masterPublicKey.decode());
final identityRecordKey = identityRec.key;
final identityPublicKey = identityRec.owner;
final identitySecret = identityRec.ownerSecret!;
final identitySigBuf = identityRecordKey.decode()
..addAll(identityPublicKey.decode());
final identitySignature =
await crypto.sign(masterPublicKey, masterSecret, identitySigBuf);
final masterSignature =
await crypto.sign(identityPublicKey, identitySecret, masterSigBuf);
final identityMaster = IdentityMaster(
identityRecordKey: identityRecordKey,
identityPublicKey: identityPublicKey,
masterRecordKey: masterRecordKey,
masterPublicKey: masterPublicKey,
identitySignature: identitySignature,
masterSignature: masterSignature);
// Write identity master to master dht key
final identityMasterBytes =
Uint8List.fromList(utf8.encode(jsonEncode(identityMaster)));
await dhtctx.setDHTValue(masterRecordKey, 0, identityMasterBytes);
return IdentityMasterWithSecrets(
identityMaster: identityMaster,
masterSecret: masterSecret,
identitySecret: identitySecret);
}
/// Creates a new account associated with master identity
Future<LocalAccount> newAccount(
IdentityMaster identityMaster,
SecretKey identitySecret,
EncryptionKeyType encryptionKeyType,
String encryptionKey) async {
//
return LocalAccount(
identityMaster: identityMaster,
identitySecretKeyBytes: identitySecretBytes,
encryptionKeyType: encryptionKeyType,
biometricsEnabled: false,
hiddenAccount: false,
);
}
}
@riverpod
Future<LocalAccountManager> localAccountManager(LocalAccountManagerRef ref) {
return LocalAccountManager.open();
}

View File

@ -1,2 +1,2 @@
export 'connection_state.dart'; export 'connection_state.dart';
export 'auth.dart'; export 'active_logins_state.dart';