Add 'client/' from commit '92f38b5810'

git-subtree-dir: client
git-subtree-mainline: 024017338e
git-subtree-split: 92f38b5810
This commit is contained in:
mitchell 2019-07-11 02:41:03 -04:00
commit 66ec035ee0
90 changed files with 3817 additions and 0 deletions

78
client/lib/main.dart Normal file
View file

@ -0,0 +1,78 @@
import 'package:flutter/cupertino.dart';
import 'package:provider/provider.dart';
import 'repositories/grpc_credentials_client.dart';
import 'repositories/secure_storage_config.dart';
import 'screens/authentication.dart';
import 'screens/credential.dart';
import 'screens/credentials.dart';
import 'screens/config.dart';
import 'screens/home.dart';
import 'types/abstracts.dart';
import 'types/screen_arguments.dart';
void main() => runApp(Selfpass());
class Selfpass extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Provider<ConfigRepo>(
builder: (BuildContext context) => SecureStorageConfig(),
child: CupertinoApp(
title: 'Selfpass',
onGenerateRoute: (RouteSettings settings) {
String title;
WidgetBuilder builder;
switch (settings.name) {
case '/':
title = 'Authentication';
builder = (BuildContext context) => Authentication();
break;
case '/home':
title = 'Hosts';
builder = (BuildContext context) => Provider<CredentialsRepo>(
builder: (BuildContext context) =>
GRPCCredentialsClient.cached(config: settings.arguments),
child: Home(),
);
break;
case '/credentials':
title = 'Credentials';
builder = (BuildContext context) => Provider<CredentialsRepo>(
builder: (BuildContext context) =>
GRPCCredentialsClient.cached(),
child: Credentials(settings.arguments),
);
break;
case '/credential':
title = 'Credential';
builder = (BuildContext context) => Provider<CredentialsRepo>(
builder: (BuildContext context) =>
GRPCCredentialsClient.cached(),
child: Credential(settings.arguments),
);
break;
case '/config':
final ConfigScreenArguments arguments = settings.arguments == null
? ConfigScreenArguments()
: settings.arguments;
title = 'Configuration';
builder = (BuildContext context) =>
Config(arguments.connectionConfig, arguments.privateKey);
break;
}
return CupertinoPageRoute(builder: builder, title: title);
},
),
);
}
}

View file

@ -0,0 +1,74 @@
///
// Generated code. Do not modify.
// source: timestamp.proto
///
// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name
import 'dart:core' as $core
show bool, Deprecated, double, int, List, Map, override, pragma, String;
import 'package:fixnum/fixnum.dart';
import 'package:protobuf/protobuf.dart' as $pb;
import 'dart:core' as $core show DateTime, Duration;
class Timestamp extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo('Timestamp',
package: const $pb.PackageName('google.protobuf'))
..aInt64(1, 'seconds')
..a<$core.int>(2, 'nanos', $pb.PbFieldType.O3)
..hasRequiredFields = false;
Timestamp._() : super();
factory Timestamp() => create();
factory Timestamp.fromBuffer($core.List<$core.int> i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(i, r);
factory Timestamp.fromJson($core.String i,
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(i, r);
Timestamp clone() => Timestamp()..mergeFromMessage(this);
Timestamp copyWith(void Function(Timestamp) updates) =>
super.copyWith((message) => updates(message as Timestamp));
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static Timestamp create() => Timestamp._();
Timestamp createEmptyInstance() => create();
static $pb.PbList<Timestamp> createRepeated() => $pb.PbList<Timestamp>();
static Timestamp getDefault() => _defaultInstance ??= create()..freeze();
static Timestamp _defaultInstance;
Int64 get seconds => $_getI64(0);
set seconds(Int64 v) {
$_setInt64(0, v);
}
$core.bool hasSeconds() => $_has(0);
void clearSeconds() => clearField(1);
$core.int get nanos => $_get(1, 0);
set nanos($core.int v) {
$_setSignedInt32(1, v);
}
$core.bool hasNanos() => $_has(1);
void clearNanos() => clearField(2);
/// Converts an instance to [DateTime].
///
/// The result is in UTC time zone and has microsecond precision, as
/// [DateTime] does not support nanosecond precision.
$core.DateTime toDateTime() => $core.DateTime.fromMicrosecondsSinceEpoch(
seconds.toInt() * $core.Duration.microsecondsPerSecond + nanos ~/ 1000,
isUtc: true);
/// Creates a new instance from [dateTime].
///
/// Time zone information will not be preserved.
static Timestamp fromDateTime($core.DateTime dateTime) {
$core.int micros = dateTime.microsecondsSinceEpoch;
return Timestamp()
..seconds = Int64(micros ~/ $core.Duration.microsecondsPerSecond)
..nanos = (micros % $core.Duration.microsecondsPerSecond).toInt() * 1000;
}
}

View file

@ -0,0 +1,6 @@
///
// Generated code. Do not modify.
// source: timestamp.proto
///
// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name

View file

@ -0,0 +1,13 @@
///
// Generated code. Do not modify.
// source: timestamp.proto
///
// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name
const Timestamp$json = const {
'1': 'Timestamp',
'2': const [
const {'1': 'seconds', '3': 1, '4': 1, '5': 3, '10': 'seconds'},
const {'1': 'nanos', '3': 2, '4': 1, '5': 5, '10': 'nanos'},
],
};

View file

@ -0,0 +1,384 @@
///
// Generated code. Do not modify.
// source: service.proto
///
// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name
import 'dart:core' as $core show bool, Deprecated, double, int, List, Map, override, pragma, String;
import 'package:protobuf/protobuf.dart' as $pb;
import 'google/protobuf/timestamp.pb.dart' as $1;
class DeleteResponse extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo('DeleteResponse', package: const $pb.PackageName('selfpass.credentials'))
..aOB(1, 'success')
..hasRequiredFields = false
;
DeleteResponse._() : super();
factory DeleteResponse() => create();
factory DeleteResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory DeleteResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
DeleteResponse clone() => DeleteResponse()..mergeFromMessage(this);
DeleteResponse copyWith(void Function(DeleteResponse) updates) => super.copyWith((message) => updates(message as DeleteResponse));
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static DeleteResponse create() => DeleteResponse._();
DeleteResponse createEmptyInstance() => create();
static $pb.PbList<DeleteResponse> createRepeated() => $pb.PbList<DeleteResponse>();
static DeleteResponse getDefault() => _defaultInstance ??= create()..freeze();
static DeleteResponse _defaultInstance;
$core.bool get success => $_get(0, false);
set success($core.bool v) { $_setBool(0, v); }
$core.bool hasSuccess() => $_has(0);
void clearSuccess() => clearField(1);
}
class GetAllMetadataRequest extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo('GetAllMetadataRequest', package: const $pb.PackageName('selfpass.credentials'))
..aOS(1, 'sourceHost')
..hasRequiredFields = false
;
GetAllMetadataRequest._() : super();
factory GetAllMetadataRequest() => create();
factory GetAllMetadataRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory GetAllMetadataRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
GetAllMetadataRequest clone() => GetAllMetadataRequest()..mergeFromMessage(this);
GetAllMetadataRequest copyWith(void Function(GetAllMetadataRequest) updates) => super.copyWith((message) => updates(message as GetAllMetadataRequest));
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static GetAllMetadataRequest create() => GetAllMetadataRequest._();
GetAllMetadataRequest createEmptyInstance() => create();
static $pb.PbList<GetAllMetadataRequest> createRepeated() => $pb.PbList<GetAllMetadataRequest>();
static GetAllMetadataRequest getDefault() => _defaultInstance ??= create()..freeze();
static GetAllMetadataRequest _defaultInstance;
$core.String get sourceHost => $_getS(0, '');
set sourceHost($core.String v) { $_setString(0, v); }
$core.bool hasSourceHost() => $_has(0);
void clearSourceHost() => clearField(1);
}
class IdRequest extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo('IdRequest', package: const $pb.PackageName('selfpass.credentials'))
..aOS(1, 'id')
..hasRequiredFields = false
;
IdRequest._() : super();
factory IdRequest() => create();
factory IdRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory IdRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
IdRequest clone() => IdRequest()..mergeFromMessage(this);
IdRequest copyWith(void Function(IdRequest) updates) => super.copyWith((message) => updates(message as IdRequest));
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static IdRequest create() => IdRequest._();
IdRequest createEmptyInstance() => create();
static $pb.PbList<IdRequest> createRepeated() => $pb.PbList<IdRequest>();
static IdRequest getDefault() => _defaultInstance ??= create()..freeze();
static IdRequest _defaultInstance;
$core.String get id => $_getS(0, '');
set id($core.String v) { $_setString(0, v); }
$core.bool hasId() => $_has(0);
void clearId() => clearField(1);
}
class UpdateRequest extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo('UpdateRequest', package: const $pb.PackageName('selfpass.credentials'))
..aOS(1, 'id')
..a<CredentialRequest>(2, 'credential', $pb.PbFieldType.OM, CredentialRequest.getDefault, CredentialRequest.create)
..hasRequiredFields = false
;
UpdateRequest._() : super();
factory UpdateRequest() => create();
factory UpdateRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory UpdateRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
UpdateRequest clone() => UpdateRequest()..mergeFromMessage(this);
UpdateRequest copyWith(void Function(UpdateRequest) updates) => super.copyWith((message) => updates(message as UpdateRequest));
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static UpdateRequest create() => UpdateRequest._();
UpdateRequest createEmptyInstance() => create();
static $pb.PbList<UpdateRequest> createRepeated() => $pb.PbList<UpdateRequest>();
static UpdateRequest getDefault() => _defaultInstance ??= create()..freeze();
static UpdateRequest _defaultInstance;
$core.String get id => $_getS(0, '');
set id($core.String v) { $_setString(0, v); }
$core.bool hasId() => $_has(0);
void clearId() => clearField(1);
CredentialRequest get credential => $_getN(1);
set credential(CredentialRequest v) { setField(2, v); }
$core.bool hasCredential() => $_has(1);
void clearCredential() => clearField(2);
}
class DumpResponse extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo('DumpResponse', package: const $pb.PackageName('selfpass.credentials'))
..a<$core.List<$core.int>>(1, 'contents', $pb.PbFieldType.OY)
..hasRequiredFields = false
;
DumpResponse._() : super();
factory DumpResponse() => create();
factory DumpResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory DumpResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
DumpResponse clone() => DumpResponse()..mergeFromMessage(this);
DumpResponse copyWith(void Function(DumpResponse) updates) => super.copyWith((message) => updates(message as DumpResponse));
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static DumpResponse create() => DumpResponse._();
DumpResponse createEmptyInstance() => create();
static $pb.PbList<DumpResponse> createRepeated() => $pb.PbList<DumpResponse>();
static DumpResponse getDefault() => _defaultInstance ??= create()..freeze();
static DumpResponse _defaultInstance;
$core.List<$core.int> get contents => $_getN(0);
set contents($core.List<$core.int> v) { $_setBytes(0, v); }
$core.bool hasContents() => $_has(0);
void clearContents() => clearField(1);
}
class EmptyRequest extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo('EmptyRequest', package: const $pb.PackageName('selfpass.credentials'))
..hasRequiredFields = false
;
EmptyRequest._() : super();
factory EmptyRequest() => create();
factory EmptyRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory EmptyRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
EmptyRequest clone() => EmptyRequest()..mergeFromMessage(this);
EmptyRequest copyWith(void Function(EmptyRequest) updates) => super.copyWith((message) => updates(message as EmptyRequest));
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static EmptyRequest create() => EmptyRequest._();
EmptyRequest createEmptyInstance() => create();
static $pb.PbList<EmptyRequest> createRepeated() => $pb.PbList<EmptyRequest>();
static EmptyRequest getDefault() => _defaultInstance ??= create()..freeze();
static EmptyRequest _defaultInstance;
}
class Metadata extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo('Metadata', package: const $pb.PackageName('selfpass.credentials'))
..aOS(1, 'id')
..a<$1.Timestamp>(2, 'createdAt', $pb.PbFieldType.OM, $1.Timestamp.getDefault, $1.Timestamp.create)
..a<$1.Timestamp>(3, 'updatedAt', $pb.PbFieldType.OM, $1.Timestamp.getDefault, $1.Timestamp.create)
..aOS(4, 'primary')
..aOS(5, 'sourceHost')
..aOS(6, 'loginUrl')
..aOS(7, 'tag')
..hasRequiredFields = false
;
Metadata._() : super();
factory Metadata() => create();
factory Metadata.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory Metadata.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
Metadata clone() => Metadata()..mergeFromMessage(this);
Metadata copyWith(void Function(Metadata) updates) => super.copyWith((message) => updates(message as Metadata));
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static Metadata create() => Metadata._();
Metadata createEmptyInstance() => create();
static $pb.PbList<Metadata> createRepeated() => $pb.PbList<Metadata>();
static Metadata getDefault() => _defaultInstance ??= create()..freeze();
static Metadata _defaultInstance;
$core.String get id => $_getS(0, '');
set id($core.String v) { $_setString(0, v); }
$core.bool hasId() => $_has(0);
void clearId() => clearField(1);
$1.Timestamp get createdAt => $_getN(1);
set createdAt($1.Timestamp v) { setField(2, v); }
$core.bool hasCreatedAt() => $_has(1);
void clearCreatedAt() => clearField(2);
$1.Timestamp get updatedAt => $_getN(2);
set updatedAt($1.Timestamp v) { setField(3, v); }
$core.bool hasUpdatedAt() => $_has(2);
void clearUpdatedAt() => clearField(3);
$core.String get primary => $_getS(3, '');
set primary($core.String v) { $_setString(3, v); }
$core.bool hasPrimary() => $_has(3);
void clearPrimary() => clearField(4);
$core.String get sourceHost => $_getS(4, '');
set sourceHost($core.String v) { $_setString(4, v); }
$core.bool hasSourceHost() => $_has(4);
void clearSourceHost() => clearField(5);
$core.String get loginUrl => $_getS(5, '');
set loginUrl($core.String v) { $_setString(5, v); }
$core.bool hasLoginUrl() => $_has(5);
void clearLoginUrl() => clearField(6);
$core.String get tag => $_getS(6, '');
set tag($core.String v) { $_setString(6, v); }
$core.bool hasTag() => $_has(6);
void clearTag() => clearField(7);
}
class Credential extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo('Credential', package: const $pb.PackageName('selfpass.credentials'))
..aOS(1, 'id')
..a<$1.Timestamp>(2, 'createdAt', $pb.PbFieldType.OM, $1.Timestamp.getDefault, $1.Timestamp.create)
..a<$1.Timestamp>(3, 'updatedAt', $pb.PbFieldType.OM, $1.Timestamp.getDefault, $1.Timestamp.create)
..aOS(4, 'primary')
..aOS(5, 'username')
..aOS(6, 'email')
..aOS(7, 'password')
..aOS(8, 'sourceHost')
..aOS(9, 'loginUrl')
..aOS(10, 'tag')
..aOS(11, 'otpSecret')
..hasRequiredFields = false
;
Credential._() : super();
factory Credential() => create();
factory Credential.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory Credential.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
Credential clone() => Credential()..mergeFromMessage(this);
Credential copyWith(void Function(Credential) updates) => super.copyWith((message) => updates(message as Credential));
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static Credential create() => Credential._();
Credential createEmptyInstance() => create();
static $pb.PbList<Credential> createRepeated() => $pb.PbList<Credential>();
static Credential getDefault() => _defaultInstance ??= create()..freeze();
static Credential _defaultInstance;
$core.String get id => $_getS(0, '');
set id($core.String v) { $_setString(0, v); }
$core.bool hasId() => $_has(0);
void clearId() => clearField(1);
$1.Timestamp get createdAt => $_getN(1);
set createdAt($1.Timestamp v) { setField(2, v); }
$core.bool hasCreatedAt() => $_has(1);
void clearCreatedAt() => clearField(2);
$1.Timestamp get updatedAt => $_getN(2);
set updatedAt($1.Timestamp v) { setField(3, v); }
$core.bool hasUpdatedAt() => $_has(2);
void clearUpdatedAt() => clearField(3);
$core.String get primary => $_getS(3, '');
set primary($core.String v) { $_setString(3, v); }
$core.bool hasPrimary() => $_has(3);
void clearPrimary() => clearField(4);
$core.String get username => $_getS(4, '');
set username($core.String v) { $_setString(4, v); }
$core.bool hasUsername() => $_has(4);
void clearUsername() => clearField(5);
$core.String get email => $_getS(5, '');
set email($core.String v) { $_setString(5, v); }
$core.bool hasEmail() => $_has(5);
void clearEmail() => clearField(6);
$core.String get password => $_getS(6, '');
set password($core.String v) { $_setString(6, v); }
$core.bool hasPassword() => $_has(6);
void clearPassword() => clearField(7);
$core.String get sourceHost => $_getS(7, '');
set sourceHost($core.String v) { $_setString(7, v); }
$core.bool hasSourceHost() => $_has(7);
void clearSourceHost() => clearField(8);
$core.String get loginUrl => $_getS(8, '');
set loginUrl($core.String v) { $_setString(8, v); }
$core.bool hasLoginUrl() => $_has(8);
void clearLoginUrl() => clearField(9);
$core.String get tag => $_getS(9, '');
set tag($core.String v) { $_setString(9, v); }
$core.bool hasTag() => $_has(9);
void clearTag() => clearField(10);
$core.String get otpSecret => $_getS(10, '');
set otpSecret($core.String v) { $_setString(10, v); }
$core.bool hasOtpSecret() => $_has(10);
void clearOtpSecret() => clearField(11);
}
class CredentialRequest extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo('CredentialRequest', package: const $pb.PackageName('selfpass.credentials'))
..aOS(1, 'primary')
..aOS(2, 'username')
..aOS(3, 'email')
..aOS(4, 'password')
..aOS(5, 'sourceHost')
..aOS(6, 'loginUrl')
..aOS(7, 'tag')
..aOS(8, 'otpSecret')
..hasRequiredFields = false
;
CredentialRequest._() : super();
factory CredentialRequest() => create();
factory CredentialRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory CredentialRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
CredentialRequest clone() => CredentialRequest()..mergeFromMessage(this);
CredentialRequest copyWith(void Function(CredentialRequest) updates) => super.copyWith((message) => updates(message as CredentialRequest));
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static CredentialRequest create() => CredentialRequest._();
CredentialRequest createEmptyInstance() => create();
static $pb.PbList<CredentialRequest> createRepeated() => $pb.PbList<CredentialRequest>();
static CredentialRequest getDefault() => _defaultInstance ??= create()..freeze();
static CredentialRequest _defaultInstance;
$core.String get primary => $_getS(0, '');
set primary($core.String v) { $_setString(0, v); }
$core.bool hasPrimary() => $_has(0);
void clearPrimary() => clearField(1);
$core.String get username => $_getS(1, '');
set username($core.String v) { $_setString(1, v); }
$core.bool hasUsername() => $_has(1);
void clearUsername() => clearField(2);
$core.String get email => $_getS(2, '');
set email($core.String v) { $_setString(2, v); }
$core.bool hasEmail() => $_has(2);
void clearEmail() => clearField(3);
$core.String get password => $_getS(3, '');
set password($core.String v) { $_setString(3, v); }
$core.bool hasPassword() => $_has(3);
void clearPassword() => clearField(4);
$core.String get sourceHost => $_getS(4, '');
set sourceHost($core.String v) { $_setString(4, v); }
$core.bool hasSourceHost() => $_has(4);
void clearSourceHost() => clearField(5);
$core.String get loginUrl => $_getS(5, '');
set loginUrl($core.String v) { $_setString(5, v); }
$core.bool hasLoginUrl() => $_has(5);
void clearLoginUrl() => clearField(6);
$core.String get tag => $_getS(6, '');
set tag($core.String v) { $_setString(6, v); }
$core.bool hasTag() => $_has(6);
void clearTag() => clearField(7);
$core.String get otpSecret => $_getS(7, '');
set otpSecret($core.String v) { $_setString(7, v); }
$core.bool hasOtpSecret() => $_has(7);
void clearOtpSecret() => clearField(8);
}

View file

@ -0,0 +1,6 @@
///
// Generated code. Do not modify.
// source: service.proto
///
// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name

View file

@ -0,0 +1,158 @@
///
// Generated code. Do not modify.
// source: service.proto
///
// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name
import 'dart:async' as $async;
import 'dart:core' as $core show int, String, List;
import 'package:grpc/service_api.dart' as $grpc;
import 'service.pb.dart' as $0;
export 'service.pb.dart';
class CredentialServiceClient extends $grpc.Client {
static final _$getAllMetadata =
$grpc.ClientMethod<$0.GetAllMetadataRequest, $0.Metadata>(
'/selfpass.credentials.CredentialService/GetAllMetadata',
($0.GetAllMetadataRequest value) => value.writeToBuffer(),
($core.List<$core.int> value) => $0.Metadata.fromBuffer(value));
static final _$get = $grpc.ClientMethod<$0.IdRequest, $0.Credential>(
'/selfpass.credentials.CredentialService/Get',
($0.IdRequest value) => value.writeToBuffer(),
($core.List<$core.int> value) => $0.Credential.fromBuffer(value));
static final _$create =
$grpc.ClientMethod<$0.CredentialRequest, $0.Credential>(
'/selfpass.credentials.CredentialService/Create',
($0.CredentialRequest value) => value.writeToBuffer(),
($core.List<$core.int> value) => $0.Credential.fromBuffer(value));
static final _$update = $grpc.ClientMethod<$0.UpdateRequest, $0.Credential>(
'/selfpass.credentials.CredentialService/Update',
($0.UpdateRequest value) => value.writeToBuffer(),
($core.List<$core.int> value) => $0.Credential.fromBuffer(value));
static final _$delete = $grpc.ClientMethod<$0.IdRequest, $0.DeleteResponse>(
'/selfpass.credentials.CredentialService/Delete',
($0.IdRequest value) => value.writeToBuffer(),
($core.List<$core.int> value) => $0.DeleteResponse.fromBuffer(value));
CredentialServiceClient($grpc.ClientChannel channel,
{$grpc.CallOptions options})
: super(channel, options: options);
$grpc.ResponseStream<$0.Metadata> getAllMetadata(
$0.GetAllMetadataRequest request,
{$grpc.CallOptions options}) {
final call = $createCall(
_$getAllMetadata, $async.Stream.fromIterable([request]),
options: options);
return $grpc.ResponseStream(call);
}
$grpc.ResponseFuture<$0.Credential> get($0.IdRequest request,
{$grpc.CallOptions options}) {
final call = $createCall(_$get, $async.Stream.fromIterable([request]),
options: options);
return $grpc.ResponseFuture(call);
}
$grpc.ResponseFuture<$0.Credential> create($0.CredentialRequest request,
{$grpc.CallOptions options}) {
final call = $createCall(_$create, $async.Stream.fromIterable([request]),
options: options);
return $grpc.ResponseFuture(call);
}
$grpc.ResponseFuture<$0.Credential> update($0.UpdateRequest request,
{$grpc.CallOptions options}) {
final call = $createCall(_$update, $async.Stream.fromIterable([request]),
options: options);
return $grpc.ResponseFuture(call);
}
$grpc.ResponseFuture<$0.DeleteResponse> delete($0.IdRequest request,
{$grpc.CallOptions options}) {
final call = $createCall(_$delete, $async.Stream.fromIterable([request]),
options: options);
return $grpc.ResponseFuture(call);
}
}
abstract class CredentialServiceBase extends $grpc.Service {
$core.String get $name => 'selfpass.credentials.CredentialService';
CredentialServiceBase() {
$addMethod($grpc.ServiceMethod<$0.GetAllMetadataRequest, $0.Metadata>(
'GetAllMetadata',
getAllMetadata_Pre,
false,
true,
($core.List<$core.int> value) =>
$0.GetAllMetadataRequest.fromBuffer(value),
($0.Metadata value) => value.writeToBuffer()));
$addMethod($grpc.ServiceMethod<$0.IdRequest, $0.Credential>(
'Get',
get_Pre,
false,
false,
($core.List<$core.int> value) => $0.IdRequest.fromBuffer(value),
($0.Credential value) => value.writeToBuffer()));
$addMethod($grpc.ServiceMethod<$0.CredentialRequest, $0.Credential>(
'Create',
create_Pre,
false,
false,
($core.List<$core.int> value) => $0.CredentialRequest.fromBuffer(value),
($0.Credential value) => value.writeToBuffer()));
$addMethod($grpc.ServiceMethod<$0.UpdateRequest, $0.Credential>(
'Update',
update_Pre,
false,
false,
($core.List<$core.int> value) => $0.UpdateRequest.fromBuffer(value),
($0.Credential value) => value.writeToBuffer()));
$addMethod($grpc.ServiceMethod<$0.IdRequest, $0.DeleteResponse>(
'Delete',
delete_Pre,
false,
false,
($core.List<$core.int> value) => $0.IdRequest.fromBuffer(value),
($0.DeleteResponse value) => value.writeToBuffer()));
}
$async.Stream<$0.Metadata> getAllMetadata_Pre(
$grpc.ServiceCall call, $async.Future request) async* {
yield* getAllMetadata(call, (await request) as $0.GetAllMetadataRequest);
}
$async.Future<$0.Credential> get_Pre(
$grpc.ServiceCall call, $async.Future request) async {
return get(call, await request);
}
$async.Future<$0.Credential> create_Pre(
$grpc.ServiceCall call, $async.Future request) async {
return create(call, await request);
}
$async.Future<$0.Credential> update_Pre(
$grpc.ServiceCall call, $async.Future request) async {
return update(call, await request);
}
$async.Future<$0.DeleteResponse> delete_Pre(
$grpc.ServiceCall call, $async.Future request) async {
return delete(call, await request);
}
$async.Stream<$0.Metadata> getAllMetadata(
$grpc.ServiceCall call, $0.GetAllMetadataRequest request);
$async.Future<$0.Credential> get(
$grpc.ServiceCall call, $0.IdRequest request);
$async.Future<$0.Credential> create(
$grpc.ServiceCall call, $0.CredentialRequest request);
$async.Future<$0.Credential> update(
$grpc.ServiceCall call, $0.UpdateRequest request);
$async.Future<$0.DeleteResponse> delete(
$grpc.ServiceCall call, $0.IdRequest request);
}

View file

@ -0,0 +1,124 @@
///
// Generated code. Do not modify.
// source: service.proto
///
// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name
const DeleteResponse$json = const {
'1': 'DeleteResponse',
'2': const [
const {'1': 'success', '3': 1, '4': 1, '5': 8, '10': 'success'},
],
};
const GetAllMetadataRequest$json = const {
'1': 'GetAllMetadataRequest',
'2': const [
const {'1': 'source_host', '3': 1, '4': 1, '5': 9, '10': 'sourceHost'},
],
};
const IdRequest$json = const {
'1': 'IdRequest',
'2': const [
const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
],
};
const UpdateRequest$json = const {
'1': 'UpdateRequest',
'2': const [
const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
const {
'1': 'credential',
'3': 2,
'4': 1,
'5': 11,
'6': '.selfpass.credentials.CredentialRequest',
'10': 'credential'
},
],
};
const DumpResponse$json = const {
'1': 'DumpResponse',
'2': const [
const {'1': 'contents', '3': 1, '4': 1, '5': 12, '10': 'contents'},
],
};
const EmptyRequest$json = const {
'1': 'EmptyRequest',
};
const Metadata$json = const {
'1': 'Metadata',
'2': const [
const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
const {
'1': 'created_at',
'3': 2,
'4': 1,
'5': 11,
'6': '.google.protobuf.Timestamp',
'10': 'createdAt'
},
const {
'1': 'updated_at',
'3': 3,
'4': 1,
'5': 11,
'6': '.google.protobuf.Timestamp',
'10': 'updatedAt'
},
const {'1': 'primary', '3': 4, '4': 1, '5': 9, '10': 'primary'},
const {'1': 'source_host', '3': 5, '4': 1, '5': 9, '10': 'sourceHost'},
const {'1': 'login_url', '3': 6, '4': 1, '5': 9, '10': 'loginUrl'},
const {'1': 'tag', '3': 7, '4': 1, '5': 9, '10': 'tag'},
],
};
const Credential$json = const {
'1': 'Credential',
'2': const [
const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
const {
'1': 'created_at',
'3': 2,
'4': 1,
'5': 11,
'6': '.google.protobuf.Timestamp',
'10': 'createdAt'
},
const {
'1': 'updated_at',
'3': 3,
'4': 1,
'5': 11,
'6': '.google.protobuf.Timestamp',
'10': 'updatedAt'
},
const {'1': 'primary', '3': 4, '4': 1, '5': 9, '10': 'primary'},
const {'1': 'username', '3': 5, '4': 1, '5': 9, '10': 'username'},
const {'1': 'email', '3': 6, '4': 1, '5': 9, '10': 'email'},
const {'1': 'password', '3': 7, '4': 1, '5': 9, '10': 'password'},
const {'1': 'source_host', '3': 8, '4': 1, '5': 9, '10': 'sourceHost'},
const {'1': 'login_url', '3': 9, '4': 1, '5': 9, '10': 'loginUrl'},
const {'1': 'tag', '3': 10, '4': 1, '5': 9, '10': 'tag'},
const {'1': 'otp_secret', '3': 11, '4': 1, '5': 9, '10': 'otpSecret'},
],
};
const CredentialRequest$json = const {
'1': 'CredentialRequest',
'2': const [
const {'1': 'primary', '3': 1, '4': 1, '5': 9, '10': 'primary'},
const {'1': 'username', '3': 2, '4': 1, '5': 9, '10': 'username'},
const {'1': 'email', '3': 3, '4': 1, '5': 9, '10': 'email'},
const {'1': 'password', '3': 4, '4': 1, '5': 9, '10': 'password'},
const {'1': 'source_host', '3': 5, '4': 1, '5': 9, '10': 'sourceHost'},
const {'1': 'login_url', '3': 6, '4': 1, '5': 9, '10': 'loginUrl'},
const {'1': 'tag', '3': 7, '4': 1, '5': 9, '10': 'tag'},
const {'1': 'otp_secret', '3': 8, '4': 1, '5': 9, '10': 'otpSecret'},
],
};

View file

@ -0,0 +1,75 @@
syntax = "proto3";
package selfpass.credentials;
option go_package = "protobuf";
import "google/protobuf/timestamp.proto";
service CredentialService {
rpc GetAllMetadata (GetAllMetadataRequest) returns (stream Metadata);
rpc Get (IdRequest) returns (Credential);
rpc Create (CredentialRequest) returns (Credential);
rpc Update (UpdateRequest) returns (Credential);
rpc Delete (IdRequest) returns (DeleteResponse);
// rpc Dump (EmptyRequest) returns (DumpResponse);
}
message DeleteResponse {
bool success = 1;
}
message GetAllMetadataRequest {
string source_host = 1;
}
message IdRequest {
string id = 1;
}
message UpdateRequest {
string id = 1;
CredentialRequest credential = 2;
}
message DumpResponse {
bytes contents = 1;
}
message EmptyRequest {
}
message Metadata {
string id = 1;
google.protobuf.Timestamp created_at = 2;
google.protobuf.Timestamp updated_at = 3;
string primary = 4;
string source_host = 5;
string login_url = 6;
string tag = 7;
}
message Credential {
string id = 1;
google.protobuf.Timestamp created_at = 2;
google.protobuf.Timestamp updated_at = 3;
string primary = 4;
string username = 5;
string email = 6;
string password = 7;
string source_host = 8;
string login_url = 9;
string tag = 10;
string otp_secret = 11;
}
message CredentialRequest {
string primary = 1;
string username = 2;
string email = 3;
string password = 4;
string source_host = 5;
string login_url = 6;
string tag = 7;
string otp_secret = 8;
}

View file

@ -0,0 +1,83 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:grpc/grpc.dart';
import '../protobuf/service.pbgrpc.dart' as grpc;
import '../protobuf/service.pb.dart' as protobuf;
import '../types/abstracts.dart';
import '../types/connection_config.dart';
import '../types/credential.dart';
class GRPCCredentialsClient implements CredentialsRepo {
static GRPCCredentialsClient _cached;
grpc.CredentialServiceClient _client;
GRPCCredentialsClient(ConnectionConfig config) {
final caCert = utf8.encode(config.caCertificate);
final cert = utf8.encode(config.certificate);
final privateCert = utf8.encode(config.privateCertificate);
final splitHost = config.host.split(':');
final hostname = splitHost[0];
final port = int.parse(splitHost[1]);
_client = grpc.CredentialServiceClient(ClientChannel(
hostname,
port: port,
options: ChannelOptions(
credentials: _ChannelCredentials(caCert, cert, privateCert),
),
));
}
factory GRPCCredentialsClient.cached({ConnectionConfig config}) =>
config == null ? _cached : _cached = GRPCCredentialsClient(config);
Stream<Metadata> getAllMetadata(String sourceHost) {
final request = grpc.GetAllMetadataRequest();
request.sourceHost = sourceHost;
return _client.getAllMetadata(request).map<Metadata>(
(protobuf.Metadata pbMetadata) => Metadata.fromProtobuf(pbMetadata));
}
Future<Credential> get(String id) async {
final request = grpc.IdRequest();
request.id = id;
return Credential.fromProtobuf(await _client.get(request));
}
Future<Credential> create(CredentialInput input) async {
return Credential();
}
Future<Credential> update(String id, CredentialInput input) async {
return Credential();
}
Future<void> delete(String id) {
final request = grpc.IdRequest();
request.id = id;
return _client.delete(request);
}
}
class _ChannelCredentials extends ChannelCredentials {
final List<int> _key;
final List<int> _cert;
const _ChannelCredentials(List<int> caCert, this._cert, this._key)
: super.secure(certificates: caCert);
@override
SecurityContext get securityContext {
return super.securityContext
..usePrivateKeyBytes(_key)
..useCertificateChainBytes(_cert);
}
}

View file

@ -0,0 +1,91 @@
import 'dart:convert';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import '../types/abstracts.dart';
import '../types/connection_config.dart';
import '../utils/crypto.dart' as crypto;
class SecureStorageConfig implements ConfigRepo {
static const _keyPrivateKey = "private_key";
static const _keyConnectionConfig = "connection_config";
static const _keyPassword = "password";
final _storage = FlutterSecureStorage();
bool _passwordMatched = false;
String _password;
String get password {
_checkIfPasswordMatched();
return _password;
}
Future<void> setPrivateKey(String key) {
_checkIfPasswordMatched();
return _storage.write(key: _keyPrivateKey, value: key.replaceAll('-', ''));
}
Future<String> get privateKey {
_checkIfPasswordMatched();
return _storage.read(key: _keyPrivateKey);
}
Future<void> setPassword(String password) {
_checkIfPasswordMatched();
_password = password;
return _storage.write(
key: _keyPassword, value: crypto.hashPassword(password));
}
Future<bool> get passwordIsSet async {
final passHash = await _storage.read(key: _keyPassword);
if (passHash != null) {
return true;
}
_passwordMatched = true;
return false;
}
Future<bool> matchesPasswordHash(String password) async {
_passwordMatched = crypto.matchHashedPassword(
await _storage.read(key: _keyPassword), password);
if (_passwordMatched) _password = password;
return _passwordMatched;
}
Future<void> setConnectionConfig(ConnectionConfig config) {
_checkIfPasswordMatched();
return _storage.write(
key: _keyConnectionConfig, value: json.encode(config));
}
Future<ConnectionConfig> get connectionConfig async {
_checkIfPasswordMatched();
final connConfig = await _storage.read(key: _keyConnectionConfig);
if (connConfig == null) {
return null;
}
return ConnectionConfig.fromJson(json.decode(connConfig));
}
Future<void> deleteAll() {
_checkIfPasswordMatched();
return _storage.deleteAll();
}
void _checkIfPasswordMatched() {
if (_passwordMatched) return;
throw Exception('password not matched yet');
}
}

View file

@ -0,0 +1,129 @@
import 'package:flutter/cupertino.dart';
import 'package:provider/provider.dart';
import '../types/abstracts.dart';
import '../widgets/text_field.dart';
class Authentication extends StatefulWidget {
@override
_AuthenticationState createState() => _AuthenticationState();
}
class _AuthenticationState extends State<Authentication> {
final TextEditingController _passwordController = TextEditingController();
final TextEditingController _confirmController = TextEditingController();
bool _invalid = false;
bool _passesDontMatch = false;
ConfigRepo _config;
Future<bool> _passwordIsSet;
@override
didChangeDependencies() {
super.didChangeDependencies();
_config = Provider.of<ConfigRepo>(context);
_passwordIsSet = _config.passwordIsSet;
}
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 50.0),
child: FutureBuilder<bool>(
future: _passwordIsSet,
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) =>
snapshot.connectionState == ConnectionState.done
? Column(
children: _buildColumnChildren(context, snapshot.data))
: Center(child: CupertinoActivityIndicator()),
),
),
);
}
List<Widget> _buildColumnChildren(BuildContext context, bool passwordIsSet) {
List<Widget> children = [
const Spacer(flex: 4),
const Flexible(child: Text('Master password:')),
Flexible(
child: TextField(
maxLines: 1,
obscure: true,
autofocus: true,
controller: _passwordController,
),
),
];
if (!passwordIsSet) {
children.add(const Flexible(child: Text('Re-enter password:')));
children.add(Flexible(
child: TextField(
maxLines: 1,
obscure: true,
controller: _confirmController,
),
));
}
if (_invalid) {
children.add(const Flexible(
child: Text(
'invalid masterpass',
style: TextStyle(color: CupertinoColors.destructiveRed),
),
));
}
if (_passesDontMatch) {
children.add(const Flexible(
child: Text(
'passwords don\'t match',
style: TextStyle(color: CupertinoColors.destructiveRed),
),
));
}
children.add(CupertinoButton(
child: Text('Enter'),
onPressed: _buildEnterPressedBuilder(context),
));
if (_passesDontMatch) {
children.add(const Spacer(flex: 1));
} else if (_invalid || !passwordIsSet) {
children.add(const Spacer(flex: 2));
} else {
children.add(const Spacer(flex: 3));
}
return children;
}
VoidCallback _buildEnterPressedBuilder(BuildContext context) {
return () async {
if (await _passwordIsSet) {
if (await _config.matchesPasswordHash(_passwordController.text)) {
Navigator.of(context).pushReplacementNamed('/home',
arguments: await _config.connectionConfig);
return;
}
this.setState(() => _invalid = true);
return;
}
if (_passwordController.text != _confirmController.text) {
this.setState(() {
_passesDontMatch = true;
});
return;
}
_config.setPassword(_passwordController.text);
_passwordIsSet = Future<bool>.value(true);
Navigator.of(context).pushReplacementNamed('/config');
};
}
}

View file

@ -0,0 +1,135 @@
import 'package:flutter/cupertino.dart';
import 'package:provider/provider.dart';
import '../types/abstracts.dart';
import '../types/connection_config.dart';
import '../widgets/text_field.dart';
class Config extends StatefulWidget {
final ConnectionConfig connectionConfig;
final String privateKey;
const Config(this.connectionConfig, this.privateKey, {Key key})
: super(key: key);
@override
State createState() => _ConfigState(this.connectionConfig, this.privateKey);
}
class _ConfigState extends State<Config> {
TextEditingController _hostController;
TextEditingController _caCertController;
TextEditingController _certController;
TextEditingController _privateCertController;
TextEditingController _privateKeyController;
ConnectionConfig _connectionConfig;
String _privateKey;
ConfigRepo _config;
_ConfigState(this._connectionConfig, this._privateKey) {
if (_connectionConfig == null) {
_connectionConfig = ConnectionConfig();
}
_hostController = TextEditingController(text: _connectionConfig.host);
_certController =
TextEditingController(text: _connectionConfig.certificate);
_caCertController =
TextEditingController(text: _connectionConfig.caCertificate);
_privateCertController =
TextEditingController(text: _connectionConfig.privateCertificate);
_privateKeyController = TextEditingController(text: _privateKey);
}
@override
didChangeDependencies() async {
super.didChangeDependencies();
_config = Provider.of<ConfigRepo>(context);
}
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: _connectionConfig.host == null
? null
: CupertinoNavigationBar(
trailing: GestureDetector(
onTap: _buildResetAllHandler(context),
child: Text('Reset App',
style: TextStyle(color: CupertinoColors.destructiveRed)),
),
),
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 30),
child: ListView(children: [
Container(margin: EdgeInsets.only(top: 10), child: Text('Host:')),
TextField(maxLines: 1, controller: _hostController),
Container(
margin: EdgeInsets.only(top: 5), child: Text('Private key:')),
TextField(maxLines: 1, controller: _privateKeyController),
Container(
margin: EdgeInsets.only(top: 5), child: Text('CA certificate:')),
TextField(maxLines: 5, controller: _caCertController),
Container(
margin: EdgeInsets.only(top: 5),
child: Text('Client certificate:')),
TextField(maxLines: 5, controller: _certController),
Container(
margin: EdgeInsets.only(top: 5),
child: Text('Private certificate:')),
TextField(maxLines: 5, controller: _privateCertController),
CupertinoButton(
child: Text('Save'), onPressed: _makeSaveOnPressed(context)),
]),
),
);
}
GestureTapCallback _buildResetAllHandler(BuildContext context) {
return () {
showCupertinoDialog(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
content: Text('Are you sure?'),
actions: [
CupertinoDialogAction(
isDefaultAction: true,
child: Text('Cancel'),
onPressed: () => Navigator.of(context).pop(),
),
CupertinoDialogAction(
isDestructiveAction: true,
child: Text('Confirm'),
onPressed: () async {
_connectionConfig = null;
await _config.deleteAll();
Navigator.of(context)
.pushNamedAndRemoveUntil('/', ModalRoute.withName('/'));
},
),
],
),
);
};
}
VoidCallback _makeSaveOnPressed(BuildContext context) {
return () async {
final connConfig = ConnectionConfig(
host: _hostController.text,
certificate: _certController.text,
caCertificate: _caCertController.text,
privateCertificate: _privateCertController.text,
);
await _config.setConnectionConfig(connConfig);
await _config.setPrivateKey(_privateKeyController.text);
Navigator.of(context)
.pushReplacementNamed('/home', arguments: connConfig);
};
}
}

View file

@ -0,0 +1,177 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:otp/otp.dart';
import '../types/credential.dart' as types;
import '../widgets/text_field.dart';
class Credential extends StatefulWidget {
final types.Credential credential;
const Credential(this.credential, {Key key}) : super(key: key);
@override
State createState() => _CredentialState(credential);
}
class _CredentialState extends State<Credential> {
_CredentialControllers _controllers;
Map<String, _FieldBuildConfig> _fieldMap;
types.Credential _credential;
_CredentialState(this._credential) : super();
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: Container(
padding: const EdgeInsets.only(top: 15, bottom: 30, left: 30),
child: ListView(
children: _buildFieldRows(context),
),
),
);
}
Map<String, _FieldBuildConfig> _buildFieldMap(
_CredentialControllers controllers,
types.Credential credential,
) {
final fieldMap = {
'Id:': _FieldBuildConfig(mutable: false, text: credential.meta.id),
'Created:': _FieldBuildConfig(
mutable: false,
copyable: false,
text: credential.meta.createdAt.toString(),
),
'Updated:': _FieldBuildConfig(
mutable: false,
copyable: false,
text: credential.meta.updatedAt.toString(),
),
'Host:': _FieldBuildConfig(controller: controllers.sourceHost),
'Primary:': _FieldBuildConfig(controller: controllers.primary),
};
if (credential.meta.tag != null && credential.meta.tag != '') {
fieldMap['Tag'] = _FieldBuildConfig(controller: controllers.tag);
}
if (credential.username != null && credential.username != '') {
fieldMap['User:'] = _FieldBuildConfig(controller: controllers.username);
}
if (credential.email != null && credential.email != '') {
fieldMap['Email:'] = _FieldBuildConfig(controller: controllers.email);
}
fieldMap['Password:'] =
_FieldBuildConfig(controller: controllers.password, obscured: true);
if (credential.otpSecret != null && credential.otpSecret != '') {
fieldMap['OTP Secret:'] = _FieldBuildConfig(
controller: controllers.otpSecret, obscured: true, otp: true);
}
return fieldMap;
}
List<Widget> _buildFieldRows(BuildContext context) {
List<Widget> rows = [];
_controllers = _CredentialControllers.fromCredential(_credential);
_fieldMap = _buildFieldMap(_controllers, _credential);
_fieldMap.forEach((key, value) {
rows.add(Container(
margin: EdgeInsets.only(top: 2.5),
child: Text(key, style: TextStyle(fontWeight: FontWeight.w600)),
));
final List<Widget> widgets = [
Expanded(
flex: 3,
child: value.mutable
? TextField(
maxLines: 1,
controller: value.controller,
obscure: value.obscured)
: Container(
margin: EdgeInsets.symmetric(vertical: 10),
child: Text(value.text)),
),
];
if (value.copyable) {
widgets.add(Expanded(
child: CupertinoButton(
child: Text(value.otp ? 'OTP' : 'Copy'),
onPressed: () => Clipboard.setData(ClipboardData(
text: value.otp
? OTP
.generateTOTPCode(value.controller.text,
DateTime.now().millisecondsSinceEpoch)
.toString()
: value.mutable ? value.controller.text : value.text,
)),
),
));
}
rows.add(Row(children: widgets));
});
return rows;
}
}
class _FieldBuildConfig {
final TextEditingController controller;
final String text;
final bool mutable;
final bool copyable;
final bool obscured;
final bool otp;
const _FieldBuildConfig({
this.mutable = true,
this.copyable = true,
this.obscured = false,
this.otp = false,
this.controller,
this.text,
});
}
class _CredentialControllers {
final TextEditingController sourceHost;
final TextEditingController primary;
final TextEditingController tag;
final TextEditingController username;
final TextEditingController email;
final TextEditingController password;
final TextEditingController otpSecret;
const _CredentialControllers({
this.sourceHost,
this.primary,
this.tag,
this.username,
this.email,
this.password,
this.otpSecret,
});
factory _CredentialControllers.fromCredential(types.Credential credential) =>
_CredentialControllers(
sourceHost: TextEditingController(text: credential.meta.sourceHost),
primary: TextEditingController(text: credential.meta.primary),
tag: TextEditingController(text: credential.meta.tag),
username: TextEditingController(text: credential.username),
email: TextEditingController(text: credential.email),
password: TextEditingController(text: credential.password),
otpSecret: TextEditingController(text: credential.otpSecret),
);
}

View file

@ -0,0 +1,72 @@
import 'package:flutter/cupertino.dart';
import 'package:provider/provider.dart';
import '../types/abstracts.dart';
import '../types/credential.dart';
import '../utils/crypto.dart' as crypto;
import '../widgets/tappable_text_list.dart';
class Credentials extends StatelessWidget {
final List<Metadata> metadatas;
const Credentials(this.metadatas);
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: TappableTextList(tappableText: _buildTappableText(context)),
navigationBar: CupertinoNavigationBar(),
);
}
Map<String, GestureTapCallback> _buildTappableText(BuildContext context) {
final makeOnTapHandler = (String id) => () async {
showCupertinoDialog(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
content: Column(
children: [
Text('Decrypting credential...'),
Container(
margin: EdgeInsets.only(top: 10),
child: CupertinoActivityIndicator()),
],
)),
);
final config = Provider.of<ConfigRepo>(context);
final client = Provider.of<CredentialsRepo>(context);
final Future<String> privateKey = config.privateKey;
final String password = config.password;
final credential = await client.get(id);
credential.password = crypto.decryptPassword(
password, await privateKey, credential.password);
if (credential.otpSecret != null && credential.otpSecret != '') {
credential.otpSecret = crypto.decryptPassword(
password, await privateKey, credential.otpSecret);
}
Navigator.of(context)
..pop()
..pushNamed('/credential', arguments: credential);
};
Map<String, GestureTapCallback> tappableText = {};
metadatas.forEach((Metadata metadata) {
var primary = metadata.primary;
if (metadata.tag != null && metadata.tag != '') {
primary += "-" + metadata.tag;
}
tappableText[primary] = makeOnTapHandler(metadata.id);
});
return tappableText;
}
}

View file

@ -0,0 +1,139 @@
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:provider/provider.dart';
import '../types/abstracts.dart';
import '../types/credential.dart';
import '../types/screen_arguments.dart';
import '../widgets/tappable_text_list.dart';
class Home extends StatefulWidget {
const Home({Key key}) : super(key: key);
@override
State createState() => _HomeState();
}
class _HomeState extends State<Home> with WidgetsBindingObserver {
CredentialsRepo _client;
ConfigRepo _config;
Future<List<Metadata>> _metadatas;
bool _stateIsPaused = false;
Timer _pausedStateTimer;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_config = Provider.of<ConfigRepo>(context);
_client = Provider.of<CredentialsRepo>(context);
_metadatas = _client.getAllMetadata('').toList();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
_stateIsPaused = state == AppLifecycleState.paused;
if (_stateIsPaused) {
_pausedStateTimer = _newPausedStateTimer();
return;
}
if (_pausedStateTimer != null) _pausedStateTimer.cancel();
}
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: FutureBuilder<List<Metadata>>(
future: _metadatas,
builder: (
BuildContext context,
AsyncSnapshot<List<Metadata>> snapshot,
) =>
(snapshot.connectionState == ConnectionState.done)
? TappableTextList(
tappableText: _buildTappableText(context, snapshot.data))
: Center(child: CupertinoActivityIndicator()),
),
navigationBar: CupertinoNavigationBar(
leading: GestureDetector(
child: Align(
child: Text('Lock',
style: TextStyle(color: CupertinoColors.destructiveRed)),
alignment: Alignment(-0.9, 0)),
onTap: _makeLockOnTapHandler(context),
),
trailing: GestureDetector(
child: Icon(CupertinoIcons.gear),
onTap: _makeConfigOnTapHandler(context),
),
),
);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
if (_pausedStateTimer != null) _pausedStateTimer.cancel();
super.dispose();
}
Timer _newPausedStateTimer() {
const checkPeriod = 30;
return Timer(Duration(seconds: checkPeriod), () {
Navigator.of(context)
.pushNamedAndRemoveUntil('/', ModalRoute.withName('/home'));
});
}
Map<String, GestureTapCallback> _buildTappableText(
BuildContext context,
List<Metadata> metadatas,
) {
final Map<String, List<Metadata>> metaMap = {};
metadatas.sort((a, b) => a.id.compareTo(b.id));
for (var metadata in metadatas) {
final source = metadata.sourceHost;
if (metaMap[source] == null) {
metaMap[source] = [metadata];
} else {
metaMap[source].add(metadata);
}
}
final handleOnTap = (List<Metadata> metadatas) => () async =>
Navigator.of(context).pushNamed('/credentials', arguments: metadatas);
final Map<String, GestureTapCallback> tappableText = {};
metaMap.forEach((String key, List<Metadata> value) =>
tappableText[key] = handleOnTap(value));
return tappableText;
}
GestureTapCallback _makeLockOnTapHandler(BuildContext context) {
return () => Navigator.of(context).pushReplacementNamed('/');
}
GestureTapCallback _makeConfigOnTapHandler(BuildContext context) {
return () async => Navigator.of(context).pushNamed('/config',
arguments: ConfigScreenArguments(
connectionConfig: await _config.connectionConfig,
privateKey: await _config.privateKey));
}
}

View file

@ -0,0 +1,27 @@
import 'dart:async';
import 'credential.dart';
import 'connection_config.dart';
abstract class CredentialsRepo {
Stream<Metadata> getAllMetadata(String sourceHost);
Future<Credential> get(String id);
Future<Credential> create(CredentialInput input);
Future<Credential> update(String id, CredentialInput input);
Future<void> delete(String id);
}
abstract class ConfigRepo {
Future<void> setPrivateKey(String key);
Future<String> get privateKey;
String get password;
Future<void> setPassword(String password);
Future<bool> get passwordIsSet;
Future<bool> matchesPasswordHash(String password);
Future<void> setConnectionConfig(ConnectionConfig config);
Future<ConnectionConfig> get connectionConfig;
Future<void> deleteAll();
}

View file

@ -0,0 +1,27 @@
class ConnectionConfig {
String host;
String caCertificate;
String certificate;
String privateCertificate;
ConnectionConfig({
this.host,
this.caCertificate,
this.certificate,
this.privateCertificate,
});
ConnectionConfig.fromJson(Map<String, dynamic> json) {
host = json['host'];
caCertificate = json['caCertificate'];
certificate = json['certificate'];
privateCertificate = json['privateCertificate'];
}
Map<String, dynamic> toJson() => {
'host': host,
'caCertificate': caCertificate,
'certificate': certificate,
'privateCertificate': privateCertificate,
};
}

View file

@ -0,0 +1,94 @@
import '../protobuf/service.pb.dart' as protobuf;
class Metadata {
String id;
String sourceHost;
DateTime createdAt;
DateTime updatedAt;
String primary;
String loginUrl;
String tag;
Metadata({
this.id,
this.sourceHost,
this.createdAt,
this.updatedAt,
this.primary,
this.loginUrl,
this.tag,
});
Metadata.fromProtobuf(protobuf.Metadata metadata) {
id = metadata.id;
createdAt = metadata.createdAt.toDateTime();
updatedAt = metadata.updatedAt.toDateTime();
sourceHost = metadata.sourceHost;
primary = metadata.primary;
loginUrl = metadata.loginUrl;
tag = metadata.tag;
}
@override
String toString() => "id: $id";
}
class MetadataInput {
String sourceHost;
String primary;
String loginUrl;
String tag;
MetadataInput({this.sourceHost, this.primary, this.loginUrl, this.tag});
}
class Credential {
Metadata meta;
String username;
String email;
String password;
String otpSecret;
Credential({
this.meta,
this.username,
this.email,
this.password,
this.otpSecret,
});
Credential.fromProtobuf(protobuf.Credential credential) {
meta = Metadata(
id: credential.id,
createdAt: credential.createdAt.toDateTime(),
updatedAt: credential.updatedAt.toDateTime(),
sourceHost: credential.sourceHost,
primary: credential.primary,
loginUrl: credential.loginUrl,
tag: credential.tag,
);
username = credential.username;
email = credential.email;
password = credential.password;
otpSecret = credential.otpSecret;
}
@override
String toString() => "meta: $meta\n";
}
class CredentialInput {
MetadataInput meta;
String username;
String email;
String password;
String otpSecret;
CredentialInput({
this.meta,
this.username,
this.email,
this.password,
this.otpSecret,
});
}

View file

@ -0,0 +1,8 @@
import 'connection_config.dart';
class ConfigScreenArguments {
final ConnectionConfig connectionConfig;
final String privateKey;
const ConfigScreenArguments({this.connectionConfig, this.privateKey});
}

View file

@ -0,0 +1,42 @@
import 'dart:math';
import 'dart:convert';
import 'dart:typed_data';
import 'package:crypt/crypt.dart';
import 'package:encrypt/encrypt.dart';
import 'package:password_hash/password_hash.dart';
String hashPassword(String password) {
const saltSize = 16;
const saltIntMax = 256;
final random = Random.secure();
final saltInts =
List<int>.generate(saltSize, (_) => random.nextInt(saltIntMax));
final salt = base64.encode(saltInts);
return Crypt.sha256(password, salt: salt).toString();
}
bool matchHashedPassword(String hashedPassword, String password) =>
Crypt(hashedPassword).match(password);
String decryptPassword(String masterpass, privateKey, ciphertext) {
final key =
PBKDF2().generateKey(masterpass, privateKey, pbkdf2Rounds, keySize);
var cipherbytes = base64.decode(ciphertext);
final iv =
IV(Uint8List.fromList(cipherbytes.getRange(0, aesBlockSize).toList()));
cipherbytes = Uint8List.fromList(
cipherbytes.getRange(aesBlockSize, cipherbytes.length).toList());
final encrypter = Encrypter(AES(Key(key), mode: AESMode.cbc));
return encrypter.decrypt(Encrypted(cipherbytes), iv: iv);
}
const pbkdf2Rounds = 4096;
const keySize = 32;
const aesBlockSize = 16;

View file

@ -0,0 +1,35 @@
import 'package:flutter/cupertino.dart';
class TappableTextList extends StatelessWidget {
final Map<String, GestureTapCallback> tappableText;
TappableTextList({Key key, this.tappableText}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView(
children: _buildListChildren(context),
);
}
List<Widget> _buildListChildren(BuildContext context) {
List<Widget> widgets = [];
tappableText.forEach((String text, GestureTapCallback handleOnTap) {
widgets.add(GestureDetector(
onTap: handleOnTap,
child: Container(
padding: const EdgeInsets.symmetric(vertical: 15.0),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(color: CupertinoColors.lightBackgroundGray),
),
),
child: Text(text, textAlign: TextAlign.center),
),
));
});
return widgets;
}
}

View file

@ -0,0 +1,57 @@
import 'package:flutter/cupertino.dart';
typedef OnSubmittedBuilder = ValueChanged<String> Function(
BuildContext context,
);
class TextField extends StatelessWidget {
final OnSubmittedBuilder onSubmittedBuilder;
final TextEditingController controller;
final OverlayVisibilityMode clearButtonMode;
final Widget prefix;
final Widget suffix;
final bool obscure;
final bool autofocus;
final bool autocorrect;
final int minLines;
final int maxLines;
const TextField({
this.onSubmittedBuilder,
this.controller,
this.obscure = false,
this.autofocus = false,
this.minLines,
this.maxLines,
this.autocorrect = false,
this.prefix,
this.suffix,
this.clearButtonMode = OverlayVisibilityMode.editing,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 5.0),
child: CupertinoTextField(
decoration: BoxDecoration(
border: Border.all(color: CupertinoColors.black),
borderRadius: const BorderRadius.all(Radius.circular(5.0)),
),
clearButtonMode: clearButtonMode,
textAlign: TextAlign.start,
onSubmitted: this.onSubmittedBuilder != null
? onSubmittedBuilder(context)
: null,
controller: controller,
obscureText: obscure,
autofocus: autofocus,
autocorrect: autocorrect,
minLines: minLines,
maxLines: maxLines,
prefix: prefix,
suffix: suffix,
),
);
}
}