From 2ccebd685936c71d3ab667f91f14dc3aa09b65df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Moli=C5=84ski?= <47773413+damian-molinski@users.noreply.github.com> Date: Mon, 23 Sep 2024 16:04:05 +0200 Subject: [PATCH 1/4] feat: /m4 redirects to /m4/discovery (#868) Co-authored-by: Dominik Toton <166132265+dtscalac@users.noreply.github.com> --- .../lib/routes/guards/milestone_guard.dart | 11 +++++++++-- catalyst_voices/lib/routes/routing/routes.dart | 14 +++++++------- .../lib/src/session/session_bloc.dart | 7 ++++++- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/catalyst_voices/lib/routes/guards/milestone_guard.dart b/catalyst_voices/lib/routes/guards/milestone_guard.dart index a27f5e7083..e661e57aa6 100644 --- a/catalyst_voices/lib/routes/guards/milestone_guard.dart +++ b/catalyst_voices/lib/routes/guards/milestone_guard.dart @@ -12,13 +12,20 @@ final class MilestoneGuard implements RouteGuard { @override FutureOr redirect(BuildContext context, GoRouterState state) { + final location = state.uri.toString(); + + // redirects /m4 page to /m4/discovery + if (location == '/${Routes.currentMilestone}') { + return const DiscoveryRoute().location; + } + // allow milestone sub pages - if (state.uri.toString().startsWith('/${Routes.currentMilestone}')) { + if (location.startsWith('/${Routes.currentMilestone}')) { return null; } // if already at destination skip redirect - if (state.uri.toString() == const ComingSoonRoute().location) { + if (location == const ComingSoonRoute().location) { return null; } diff --git a/catalyst_voices/lib/routes/routing/routes.dart b/catalyst_voices/lib/routes/routing/routes.dart index e44d80c1a1..11d75a1638 100644 --- a/catalyst_voices/lib/routes/routing/routes.dart +++ b/catalyst_voices/lib/routes/routing/routes.dart @@ -11,14 +11,14 @@ import 'package:go_router/go_router.dart'; abstract final class Routes { static const currentMilestone = 'm4'; + static final List routes = [ + ...coming_soon.$appRoutes, + ...login.$appRoutes, + ...spaces.$appRoutes, + ...overall_spaces.$appRoutes, + ]; + static String get initialLocation { return const coming_soon.ComingSoonRoute().location; } - - static List get routes => [ - ...coming_soon.$appRoutes, - ...login.$appRoutes, - ...spaces.$appRoutes, - ...overall_spaces.$appRoutes, - ]; } diff --git a/catalyst_voices/packages/catalyst_voices_blocs/lib/src/session/session_bloc.dart b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/session/session_bloc.dart index 77545c8e8a..949518e234 100644 --- a/catalyst_voices/packages/catalyst_voices_blocs/lib/src/session/session_bloc.dart +++ b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/session/session_bloc.dart @@ -5,7 +5,12 @@ import 'package:flutter_bloc/flutter_bloc.dart'; /// Manages the user session. final class SessionBloc extends Bloc { - SessionBloc() : super(const VisitorSessionState()) { + SessionBloc() + : super( + const ActiveUserSessionState( + user: User(name: 'Account'), + ), + ) { on(_handleSessionEvent); } From 6167e596b5547e77e7fe8ff1ffe64313ddd3a96c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Moli=C5=84ski?= <47773413+damian-molinski@users.noreply.github.com> Date: Mon, 23 Sep 2024 16:19:22 +0200 Subject: [PATCH 2/4] feat(cat-voices): local storage + vault (#864) * feat: WIP - interfaces * refactor: move storage to services package + implement codec * feat: More storage types options * chore: docs * feat: secure storage * feat: export storage classes + fix voices usage * test: StorageStringMixin unit tests * test: first secure storage test * secure storage tests * chore: LockFactor tests * chore: codec tests * chore: SecureStorageVault tests * refactor: renaming of lockFactor variables to lock/unlock * refactor: cleanup vault functions order * refactor: Rename Storage mixin * feat: dummy auth storage which encapsulates keys --------- Co-authored-by: Dominik Toton <166132265+dtscalac@users.noreply.github.com> --- .../lib/dependency/dependencies.dart | 8 +- .../src/credentials_storage_repository.dart | 28 +-- .../lib/src/catalyst_voices_services.dart | 8 +- .../src/secure_storage/secure_storage.dart | 2 - .../secure_storage/secure_storage_keys.dart | 6 - .../secure_storage_service.dart | 22 --- .../lib/src/storage/dummy_auth_storage.dart | 47 +++++ .../lib/src/storage/secure_storage.dart | 50 ++++++ .../lib/src/storage/storage.dart | 45 +++++ .../lib/src/storage/storage_string_mixin.dart | 76 ++++++++ .../lib/src/storage/vault/lock_factor.dart | 87 ++++++++++ .../src/storage/vault/lock_factor_codec.dart | 41 +++++ .../storage/vault/secure_storage_vault.dart | 140 +++++++++++++++ .../lib/src/storage/vault/vault.dart | 25 +++ .../catalyst_voices_services/pubspec.yaml | 2 +- .../test/src/storage/secure_storage_test.dart | 80 +++++++++ .../storage/storage_string_mixin_test.dart | 162 ++++++++++++++++++ .../storage/vault/lock_factor_codec_test.dart | 20 +++ .../src/storage/vault/lock_factor_test.dart | 132 ++++++++++++++ .../vault/secure_storage_vault_test.dart | 125 ++++++++++++++ melos.yaml | 1 + 21 files changed, 1052 insertions(+), 55 deletions(-) delete mode 100644 catalyst_voices/packages/catalyst_voices_services/lib/src/secure_storage/secure_storage.dart delete mode 100644 catalyst_voices/packages/catalyst_voices_services/lib/src/secure_storage/secure_storage_keys.dart delete mode 100644 catalyst_voices/packages/catalyst_voices_services/lib/src/secure_storage/secure_storage_service.dart create mode 100644 catalyst_voices/packages/catalyst_voices_services/lib/src/storage/dummy_auth_storage.dart create mode 100644 catalyst_voices/packages/catalyst_voices_services/lib/src/storage/secure_storage.dart create mode 100644 catalyst_voices/packages/catalyst_voices_services/lib/src/storage/storage.dart create mode 100644 catalyst_voices/packages/catalyst_voices_services/lib/src/storage/storage_string_mixin.dart create mode 100644 catalyst_voices/packages/catalyst_voices_services/lib/src/storage/vault/lock_factor.dart create mode 100644 catalyst_voices/packages/catalyst_voices_services/lib/src/storage/vault/lock_factor_codec.dart create mode 100644 catalyst_voices/packages/catalyst_voices_services/lib/src/storage/vault/secure_storage_vault.dart create mode 100644 catalyst_voices/packages/catalyst_voices_services/lib/src/storage/vault/vault.dart create mode 100644 catalyst_voices/packages/catalyst_voices_services/test/src/storage/secure_storage_test.dart create mode 100644 catalyst_voices/packages/catalyst_voices_services/test/src/storage/storage_string_mixin_test.dart create mode 100644 catalyst_voices/packages/catalyst_voices_services/test/src/storage/vault/lock_factor_codec_test.dart create mode 100644 catalyst_voices/packages/catalyst_voices_services/test/src/storage/vault/lock_factor_test.dart create mode 100644 catalyst_voices/packages/catalyst_voices_services/test/src/storage/vault/secure_storage_vault_test.dart diff --git a/catalyst_voices/lib/dependency/dependencies.dart b/catalyst_voices/lib/dependency/dependencies.dart index 589ca073dd..3c9a6b4ba0 100644 --- a/catalyst_voices/lib/dependency/dependencies.dart +++ b/catalyst_voices/lib/dependency/dependencies.dart @@ -33,7 +33,7 @@ final class Dependencies extends DependencyProvider { void _registerRepositories() { this ..registerSingleton( - CredentialsStorageRepository(secureStorageService: get()), + CredentialsStorageRepository(storage: get()), ) ..registerSingleton( AuthenticationRepository(credentialsStorageRepository: get()), @@ -41,8 +41,8 @@ final class Dependencies extends DependencyProvider { } void _registerServices() { - registerSingleton( - SecureStorageService(), - ); + registerSingleton(const SecureStorage()); + registerSingleton(const SecureStorageVault()); + registerSingleton(const SecureDummyAuthStorage()); } } diff --git a/catalyst_voices/packages/catalyst_voices_repositories/lib/src/credentials_storage_repository.dart b/catalyst_voices/packages/catalyst_voices_repositories/lib/src/credentials_storage_repository.dart index 6367bd42a5..25beccc74b 100644 --- a/catalyst_voices/packages/catalyst_voices_repositories/lib/src/credentials_storage_repository.dart +++ b/catalyst_voices/packages/catalyst_voices_repositories/lib/src/credentials_storage_repository.dart @@ -9,21 +9,18 @@ import 'package:result_type/result_type.dart'; /// It will be replaced by a proper implementation as soon as authentication /// flow will be defined. final class CredentialsStorageRepository { - final SecureStorageService secureStorageService; + final DummyAuthStorage _storage; - const CredentialsStorageRepository({required this.secureStorageService}); + const CredentialsStorageRepository({ + required DummyAuthStorage storage, + }) : _storage = storage; - void get clearSessionData => secureStorageService.deleteAll; + Future get clearSessionData async => _storage.clear(); Future> getSessionData() async { try { - final email = await secureStorageService.get( - SecureStorageKeysConst.dummyEmail, - ); - - final password = await secureStorageService.get( - SecureStorageKeysConst.dummyPassword, - ); + final email = await _storage.readEmail(); + final password = await _storage.readPassword(); if (email == null || password == null) { return Success(null); @@ -44,15 +41,8 @@ final class CredentialsStorageRepository { SessionData sessionData, ) async { try { - await secureStorageService.set( - SecureStorageKeysConst.dummyEmail, - sessionData.email, - ); - - await secureStorageService.set( - SecureStorageKeysConst.dummyPassword, - sessionData.password, - ); + await _storage.writeEmail(sessionData.email); + await _storage.writePassword(sessionData.password); return Success(null); } on SecureStorageError catch (_) { return Failure(SecureStorageError.canNotSaveData); diff --git a/catalyst_voices/packages/catalyst_voices_services/lib/src/catalyst_voices_services.dart b/catalyst_voices/packages/catalyst_voices_services/lib/src/catalyst_voices_services.dart index b9cfd362c0..c86d5ab5db 100644 --- a/catalyst_voices/packages/catalyst_voices_services/lib/src/catalyst_voices_services.dart +++ b/catalyst_voices/packages/catalyst_voices_services/lib/src/catalyst_voices_services.dart @@ -1 +1,7 @@ -export 'secure_storage/secure_storage.dart'; +export 'storage/dummy_auth_storage.dart'; +export 'storage/secure_storage.dart'; +export 'storage/storage.dart'; +export 'storage/vault/lock_factor.dart'; +export 'storage/vault/lock_factor_codec.dart' show LockFactorCodec; +export 'storage/vault/secure_storage_vault.dart'; +export 'storage/vault/vault.dart'; diff --git a/catalyst_voices/packages/catalyst_voices_services/lib/src/secure_storage/secure_storage.dart b/catalyst_voices/packages/catalyst_voices_services/lib/src/secure_storage/secure_storage.dart deleted file mode 100644 index 37c0fba470..0000000000 --- a/catalyst_voices/packages/catalyst_voices_services/lib/src/secure_storage/secure_storage.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'secure_storage_keys.dart'; -export 'secure_storage_service.dart'; diff --git a/catalyst_voices/packages/catalyst_voices_services/lib/src/secure_storage/secure_storage_keys.dart b/catalyst_voices/packages/catalyst_voices_services/lib/src/secure_storage/secure_storage_keys.dart deleted file mode 100644 index 7a5df87dd4..0000000000 --- a/catalyst_voices/packages/catalyst_voices_services/lib/src/secure_storage/secure_storage_keys.dart +++ /dev/null @@ -1,6 +0,0 @@ -final class SecureStorageKeysConst { - static const dummyEmail = 'email'; - static const dummyPassword = 'password'; - - const SecureStorageKeysConst._(); -} diff --git a/catalyst_voices/packages/catalyst_voices_services/lib/src/secure_storage/secure_storage_service.dart b/catalyst_voices/packages/catalyst_voices_services/lib/src/secure_storage/secure_storage_service.dart deleted file mode 100644 index 6c506911c9..0000000000 --- a/catalyst_voices/packages/catalyst_voices_services/lib/src/secure_storage/secure_storage_service.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; - -final class SecureStorageService { - final _storage = const FlutterSecureStorage(); - - SecureStorageService(); - - Future get deleteAll async => _storage.deleteAll(); - - Future delete(String key) { - return _storage.delete(key: key); - } - - Future get(String key) async { - final value = await _storage.read(key: key); - return value; - } - - Future set(String key, String value) async { - return _storage.write(key: key, value: value); - } -} diff --git a/catalyst_voices/packages/catalyst_voices_services/lib/src/storage/dummy_auth_storage.dart b/catalyst_voices/packages/catalyst_voices_services/lib/src/storage/dummy_auth_storage.dart new file mode 100644 index 0000000000..74a5674591 --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_services/lib/src/storage/dummy_auth_storage.dart @@ -0,0 +1,47 @@ +import 'dart:async'; + +import 'package:catalyst_voices_services/src/storage/secure_storage.dart'; + +abstract interface class DummyAuthStorage { + FutureOr readEmail(); + + FutureOr writeEmail(String? value); + + FutureOr readPassword(); + + FutureOr writePassword(String? value); + + Future clear(); +} + +final class SecureDummyAuthStorage extends SecureStorage + implements DummyAuthStorage { + static const _emailKey = 'email'; + static const _passwordKey = 'password'; + + const SecureDummyAuthStorage({ + super.secureStorage, + }); + + @override + FutureOr readEmail() => readString(key: _emailKey); + + @override + FutureOr writeEmail(String? value) { + return writeString(value, key: _emailKey); + } + + @override + FutureOr readPassword() => readString(key: _passwordKey); + + @override + FutureOr writePassword(String? value) { + return writeString(value, key: _passwordKey); + } + + @override + Future clear() async { + await writeEmail(null); + await writePassword(null); + } +} diff --git a/catalyst_voices/packages/catalyst_voices_services/lib/src/storage/secure_storage.dart b/catalyst_voices/packages/catalyst_voices_services/lib/src/storage/secure_storage.dart new file mode 100644 index 0000000000..ca9d3aebbd --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_services/lib/src/storage/secure_storage.dart @@ -0,0 +1,50 @@ +import 'dart:async'; + +import 'package:catalyst_voices_services/src/storage/storage.dart'; +import 'package:catalyst_voices_services/src/storage/storage_string_mixin.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; + +const _keyPrefix = 'SecureStorage'; + +base class SecureStorage with StorageAsStringMixin implements Storage { + final FlutterSecureStorage _secureStorage; + + const SecureStorage({ + FlutterSecureStorage secureStorage = const FlutterSecureStorage(), + }) : _secureStorage = secureStorage; + + @override + Future readString({required String key}) { + final effectiveKey = _buildVaultKey(key); + + return _secureStorage.read(key: effectiveKey); + } + + @override + Future writeString( + String? value, { + required String key, + }) async { + final effectiveKey = _buildVaultKey(key); + + if (value != null) { + await _secureStorage.write(key: effectiveKey, value: value); + } else { + await _secureStorage.delete(key: effectiveKey); + } + } + + @override + FutureOr clear() async { + final all = await _secureStorage.readAll(); + final vaultKeys = List.of(all.keys).where((e) => e.startsWith(_keyPrefix)); + + for (final key in vaultKeys) { + await _secureStorage.delete(key: key); + } + } + + String _buildVaultKey(String key) { + return '$_keyPrefix.$key'; + } +} diff --git a/catalyst_voices/packages/catalyst_voices_services/lib/src/storage/storage.dart b/catalyst_voices/packages/catalyst_voices_services/lib/src/storage/storage.dart new file mode 100644 index 0000000000..7e602b3705 --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_services/lib/src/storage/storage.dart @@ -0,0 +1,45 @@ +//ignore_for_file: avoid_positional_boolean_parameters + +import 'dart:async'; + +import 'package:flutter/foundation.dart'; + +/// Abstract representation of generic storage. This interface does +/// not determinate where data should be stored or how it should be stored. +/// Encrypted or not. +/// +/// Implementation may use local memory / filesystem or shared preferences or +/// any other. +abstract interface class Storage { + FutureOr readString({required String key}); + + FutureOr writeString( + String? value, { + required String key, + }); + + FutureOr readInt({required String key}); + + FutureOr writeInt( + int? value, { + required String key, + }); + + FutureOr readBool({required String key}); + + FutureOr writeBool( + bool? value, { + required String key, + }); + + FutureOr readBytes({required String key}); + + FutureOr writeBytes( + Uint8List? value, { + required String key, + }); + + FutureOr delete({required String key}); + + FutureOr clear(); +} diff --git a/catalyst_voices/packages/catalyst_voices_services/lib/src/storage/storage_string_mixin.dart b/catalyst_voices/packages/catalyst_voices_services/lib/src/storage/storage_string_mixin.dart new file mode 100644 index 0000000000..ccb19f245d --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_services/lib/src/storage/storage_string_mixin.dart @@ -0,0 +1,76 @@ +//ignore_for_file: avoid_positional_boolean_parameters + +import 'dart:async'; +import 'dart:convert'; + +import 'package:catalyst_voices_services/src/storage/storage.dart'; +import 'package:catalyst_voices_services/src/storage/vault/secure_storage_vault.dart'; +import 'package:flutter/foundation.dart'; + +/// Utility mixin which implements all but String read/write of [Storage] +/// interface. Every method is has its mapping to [readString]/[writeString]. +/// +/// See [SecureStorageVault] as example. +mixin StorageAsStringMixin implements Storage { + @override + FutureOr readInt({required String key}) async { + final value = await readString(key: key); + return value != null ? int.parse(value) : null; + } + + @override + FutureOr writeInt( + int? value, { + required String key, + }) { + return writeString(value?.toString(), key: key); + } + + @override + FutureOr readBool({required String key}) async { + final value = await readInt(key: key); + + return switch (value) { + 0 => false, + 1 => true, + _ => null, + }; + } + + @override + FutureOr writeBool( + bool? value, { + required String key, + }) { + final asInt = value != null + ? value + ? 1 + : 0 + : null; + + return writeInt(asInt, key: key); + } + + @override + FutureOr readBytes({required String key}) async { + final base64String = await readString(key: key); + final bytes = base64String != null + ? Uint8List.fromList(base64Decode(base64String)) + : null; + + return bytes; + } + + @override + FutureOr writeBytes( + Uint8List? value, { + required String key, + }) { + final base64String = value != null ? base64Encode(value) : null; + + return writeString(base64String, key: key); + } + + @override + FutureOr delete({required String key}) => writeString(null, key: key); +} diff --git a/catalyst_voices/packages/catalyst_voices_services/lib/src/storage/vault/lock_factor.dart b/catalyst_voices/packages/catalyst_voices_services/lib/src/storage/vault/lock_factor.dart new file mode 100644 index 0000000000..905e44a570 --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_services/lib/src/storage/vault/lock_factor.dart @@ -0,0 +1,87 @@ +import 'package:catalyst_voices_services/src/storage/vault/vault.dart'; + +enum _LockFactorType { voidFactor, password } + +// Note. +// In future we may add MultiLockFactor for bio and password unlock factors + +/// Abstract representation of different factors that can lock [Vault] with. +/// +/// Most common is [PasswordLockFactor] which can be use as standalone factor. +/// +/// This class is serializable to/from json. +sealed class LockFactor { + /// Use [LockFactor.toJson] as parameter for this factory. + factory LockFactor.fromJson(Map json) { + final typeName = json['type']; + final type = _LockFactorType.values.asNameMap()[typeName]; + + return switch (type) { + _LockFactorType.voidFactor => const VoidLockFactor(), + _LockFactorType.password => PasswordLockFactor.fromJson(json), + null => throw ArgumentError('Unknown type name($typeName)', 'json'), + }; + } + + /// Returns true when this [LockFactor] can be used to unlock + /// other [LockFactor]. + bool unlocks(LockFactor factor); + + /// Returns json representation on this [LockFactor]. + /// + /// Should be used with [LockFactor.fromJson]. + Map toJson(); +} + +/// Can not be used to unlock anything. Useful as default value for [LockFactor] +/// variables. +/// +/// [unlocks] always returns false. +final class VoidLockFactor implements LockFactor { + const VoidLockFactor(); + + @override + bool unlocks(LockFactor factor) => false; + + @override + Map toJson() { + return { + 'type': _LockFactorType.voidFactor.name, + }; + } + + @override + String toString() => 'VoidLockFactor'; +} + +/// Password matching [LockFactor]. +/// +/// Only unlocks other [PasswordLockFactor] with matching +/// [PasswordLockFactor._data]. +final class PasswordLockFactor implements LockFactor { + final String _data; + + const PasswordLockFactor(this._data); + + factory PasswordLockFactor.fromJson(Map json) { + return PasswordLockFactor( + json['data'] as String, + ); + } + + @override + bool unlocks(LockFactor factor) { + return factor is PasswordLockFactor && _data == factor._data; + } + + @override + Map toJson() { + return { + 'type': _LockFactorType.password.name, + 'data': _data, + }; + } + + @override + String toString() => 'PasswordLockFactor(data=****)'; +} diff --git a/catalyst_voices/packages/catalyst_voices_services/lib/src/storage/vault/lock_factor_codec.dart b/catalyst_voices/packages/catalyst_voices_services/lib/src/storage/vault/lock_factor_codec.dart new file mode 100644 index 0000000000..c6754ed75e --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_services/lib/src/storage/vault/lock_factor_codec.dart @@ -0,0 +1,41 @@ +import 'dart:convert'; + +import 'package:catalyst_voices_services/src/storage/vault/lock_factor.dart'; + +abstract class LockFactorCodec extends Codec { + const LockFactorCodec(); +} + +/// Uses [LockFactor.toJson] and [LockFactor.fromJson] to serialize to +/// [String] using [json]. +class DefaultLockFactorCodec extends LockFactorCodec { + const DefaultLockFactorCodec(); + + @override + Converter get decoder => const _LockFactorDecoder(); + + @override + Converter get encoder => const _LockFactorEncoder(); +} + +class _LockFactorDecoder extends Converter { + const _LockFactorDecoder(); + + @override + LockFactor convert(String input) { + final json = jsonDecode(input) as Map; + + return LockFactor.fromJson(json); + } +} + +class _LockFactorEncoder extends Converter { + const _LockFactorEncoder(); + + @override + String convert(LockFactor input) { + final json = input.toJson(); + + return jsonEncode(json); + } +} diff --git a/catalyst_voices/packages/catalyst_voices_services/lib/src/storage/vault/secure_storage_vault.dart b/catalyst_voices/packages/catalyst_voices_services/lib/src/storage/vault/secure_storage_vault.dart new file mode 100644 index 0000000000..024b3ebac0 --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_services/lib/src/storage/vault/secure_storage_vault.dart @@ -0,0 +1,140 @@ +import 'dart:async'; + +import 'package:catalyst_voices_services/src/storage/storage_string_mixin.dart'; +import 'package:catalyst_voices_services/src/storage/vault/lock_factor.dart'; +import 'package:catalyst_voices_services/src/storage/vault/lock_factor_codec.dart'; +import 'package:catalyst_voices_services/src/storage/vault/vault.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; + +const _keyPrefix = 'SecureStorageVault'; +const _lockKey = 'LockFactorKey'; +const _unlockKey = 'UnlockFactorKey'; + +// TODO(damian-molinski): Maybe we'll need to encrypt data with LockFactor +/// Implementation of [Vault] that uses [FlutterSecureStorage] as +/// facade for read/write operations. +base class SecureStorageVault with StorageAsStringMixin implements Vault { + final FlutterSecureStorage _secureStorage; + final LockFactorCodec _lockCodec; + + const SecureStorageVault({ + FlutterSecureStorage secureStorage = const FlutterSecureStorage(), + LockFactorCodec lockCodec = const DefaultLockFactorCodec(), + }) : _secureStorage = secureStorage, + _lockCodec = lockCodec; + + /// If storage does not have [LockFactor] this getter will + /// return [VoidLockFactor] as fallback. + Future get _lock => _readLock(_lockKey); + + /// If storage does not have [LockFactor] this getter will + /// return [VoidLockFactor] as fallback. + Future get _unlock => _readLock(_unlockKey); + + @override + Future get isUnlocked async { + final lock = await _lock; + final unlock = await _unlock; + + return unlock.unlocks(lock); + } + + @override + Future lock() => _writeLock(null, key: _unlockKey); + + @override + Future unlock(LockFactor unlock) async { + await _writeLock(unlock, key: _unlockKey); + + return isUnlocked; + } + + @override + Future setLock(LockFactor lock) { + return _writeLock(lock, key: _lockKey); + } + + @override + Future readString({required String key}) => _guardedRead(key: key); + + @override + Future writeString( + String? value, { + required String key, + }) { + return _guardedWrite(value, key: key); + } + + @override + Future clear() async { + final all = await _secureStorage.readAll(); + final vaultKeys = List.of(all.keys).where((e) => e.startsWith(_keyPrefix)); + + for (final key in vaultKeys) { + await _secureStorage.delete(key: key); + } + } + + Future _writeLock( + LockFactor? lock, { + required String key, + }) { + final encodedLock = lock != null ? _lockCodec.encode(lock) : null; + + return _guardedWrite( + encodedLock, + key: key, + requireUnlocked: false, + ); + } + + Future _readLock(String key) async { + final value = await _guardedRead(key: key, requireUnlocked: false); + + return value != null ? _lockCodec.decode(value) : const VoidLockFactor(); + } + + /// Allows operation only when [isUnlocked] it true, otherwise non op. + /// + /// * When [value] is non null writes it to [key]. + /// * When [value] is null then [key] value is deleted. + Future _guardedWrite( + String? value, { + required String key, + bool requireUnlocked = true, + }) async { + final hasAccess = !requireUnlocked || await isUnlocked; + if (!hasAccess) { + return; + } + + final effectiveKey = _buildVaultKey(key); + + if (value != null) { + await _secureStorage.write(key: effectiveKey, value: value); + } else { + await _secureStorage.delete(key: effectiveKey); + } + } + + /// Allows operation only when [isUnlocked] it true, otherwise returns null. + /// + /// Returns value assigned to [key]. May return null if non found for [key]. + Future _guardedRead({ + required String key, + bool requireUnlocked = true, + }) async { + final hasAccess = !requireUnlocked || await isUnlocked; + if (!hasAccess) { + return null; + } + + final effectiveKey = _buildVaultKey(key); + + return _secureStorage.read(key: effectiveKey); + } + + String _buildVaultKey(String key) { + return '$_keyPrefix.$key'; + } +} diff --git a/catalyst_voices/packages/catalyst_voices_services/lib/src/storage/vault/vault.dart b/catalyst_voices/packages/catalyst_voices_services/lib/src/storage/vault/vault.dart new file mode 100644 index 0000000000..35717e704b --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_services/lib/src/storage/vault/vault.dart @@ -0,0 +1,25 @@ +import 'package:catalyst_voices_services/src/storage/storage.dart'; +import 'package:catalyst_voices_services/src/storage/vault/lock_factor.dart'; + +/// Secure version of [Storage] where any read/write methods can take +/// effect only when [isUnlocked] returns true. +/// +/// In order to unlock [Vault] sufficient [LockFactor] have to be +/// set via [unlock] that can unlock [LockFactor] from [setLock]. +/// +/// See [LockFactor.unlocks] for more details. +abstract interface class Vault implements Storage { + /// Returns true when have sufficient [LockFactor] from [unlock]. + Future get isUnlocked; + + /// Deletes unlockFactor if have any. + Future lock(); + + /// Changes [isUnlocked] when [unlock] can unlock [LockFactor] + /// from [setLock]. + Future unlock(LockFactor unlock); + + /// Sets [LockFactor] that which prevents read/write on this [Vault] + /// unless unlocked with matching [LockFactor] via [unlock]. + Future setLock(LockFactor lock); +} diff --git a/catalyst_voices/packages/catalyst_voices_services/pubspec.yaml b/catalyst_voices/packages/catalyst_voices_services/pubspec.yaml index 427e6268e9..45a2d329fe 100644 --- a/catalyst_voices/packages/catalyst_voices_services/pubspec.yaml +++ b/catalyst_voices/packages/catalyst_voices_services/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: chopper: ^7.2.0 flutter: sdk: flutter - flutter_secure_storage: ^9.0.0 + flutter_secure_storage: ^9.2.2 json_annotation: ^4.8.1 rxdart: ^0.27.7 diff --git a/catalyst_voices/packages/catalyst_voices_services/test/src/storage/secure_storage_test.dart b/catalyst_voices/packages/catalyst_voices_services/test/src/storage/secure_storage_test.dart new file mode 100644 index 0000000000..72c8921b2d --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_services/test/src/storage/secure_storage_test.dart @@ -0,0 +1,80 @@ +import 'package:catalyst_voices_services/src/storage/secure_storage.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:test/test.dart'; + +void main() { + late final FlutterSecureStorage flutterSecureStorage; + late final SecureStorage secureStorage; + + setUpAll(() { + FlutterSecureStorage.setMockInitialValues({}); + + flutterSecureStorage = const FlutterSecureStorage(); + secureStorage = SecureStorage(secureStorage: flutterSecureStorage); + }); + + tearDown(() async { + await secureStorage.clear(); + }); + + test('read returns null when no value found for key', () async { + // Given + const key = 'key'; + + // When + final value = await secureStorage.readString(key: key); + + // Then + expect(value, isNull); + }); + + test('read returns stored value when has one', () async { + // Given + const key = 'key'; + const expectedValue = 'qqqq'; + + // When + await secureStorage.writeString(expectedValue, key: key); + final value = await secureStorage.readString(key: key); + + // Then + expect(value, expectedValue); + }); + + test('writing null deletes value', () async { + // Given + const key = 'key'; + const expectedValue = 'qqqq'; + + // When + await secureStorage.writeString(expectedValue, key: key); + await secureStorage.writeString(null, key: key); + final value = await secureStorage.readString(key: key); + + // Then + expect(value, isNull); + }); + + test('clear removes all values for this storage', () async { + // Given + const keyValues = { + 'one': 'qqq', + 'two': 'qqq', + }; + + // When + for (final entity in keyValues.entries) { + await secureStorage.writeString(entity.value, key: entity.key); + } + + await secureStorage.clear(); + + final futures = + keyValues.keys.map((e) => secureStorage.readString(key: e)).toList(); + + final values = await Future.wait(futures); + + // Then + expect(values, everyElement(isNull)); + }); +} diff --git a/catalyst_voices/packages/catalyst_voices_services/test/src/storage/storage_string_mixin_test.dart b/catalyst_voices/packages/catalyst_voices_services/test/src/storage/storage_string_mixin_test.dart new file mode 100644 index 0000000000..8792e0243f --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_services/test/src/storage/storage_string_mixin_test.dart @@ -0,0 +1,162 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:catalyst_voices_services/src/storage/storage.dart'; +import 'package:catalyst_voices_services/src/storage/storage_string_mixin.dart'; +import 'package:test/test.dart'; + +void main() { + const key = 'key'; + + final storage = _TestStorage(); + + setUp(storage.clear); + + group('int', () { + test('read returns null when no value found', () async { + // Given + + // When + final value = await storage.readInt(key: key); + + // Then + expect(value, isNull); + }); + + test('read returns non-null when value found', () async { + // Given + const expected = 1; + + // When + storage._data[key] = '$expected'; + final value = await storage.readInt(key: key); + + // Then + expect(value, expected); + }); + + test('internally keeps correct String', () async { + // Given + const expected = 1; + storage._data[key] = '$expected'; + + // When + await storage.writeInt(expected, key: key); + final value = storage.readString(key: key); + + // Then + expect(value, '$expected'); + }); + }); + + group('bool', () { + test('read returns null when no value found', () async { + // Given + + // When + final value = await storage.readBool(key: key); + + // Then + expect(value, isNull); + }); + + test('read stores false as 0', () async { + // Given + const expected = false; + const expectedString = '0'; + + // When + await storage.writeBool(expected, key: key); + final value = await storage.readString(key: key); + + // Then + expect(value, expectedString); + }); + + test('read stores true as 1', () async { + // Given + const expected = true; + const expectedString = '1'; + + // When + await storage.writeBool(expected, key: key); + final value = await storage.readString(key: key); + + // Then + expect(value, expectedString); + }); + + test('write and read values matches', () async { + // Given + const expected = true; + + // When + await storage.writeBool(expected, key: key); + final value = await storage.readBool(key: key); + + // Then + expect(value, expected); + }); + }); + + group('bytes', () { + test('read returns null when no value found', () async { + // Given + + // When + final value = await storage.readBytes(key: key); + + // Then + expect(value, isNull); + }); + + test('can write and read value correctly', () async { + // Given + final bytes = Uint8List.fromList([0, 0, 0, 0, 0, 1]); + + // When + await storage.writeBytes(bytes, key: key); + final value = await storage.readBytes(key: key); + + // Then + expect(value, bytes); + }); + }); + + test('delete writes null string', () async { + // Given + const randomValue = 'D'; + + // When + await storage.writeString(randomValue, key: key); + await storage.delete(key: key); + final value = await storage.readString(key: key); + + // Then + expect(value, isNull); + }); +} + +class _TestStorage with StorageAsStringMixin implements Storage { + final _data = {}; + + @override + FutureOr clear() { + _data.clear(); + } + + @override + FutureOr readString({required String key}) => _data[key]; + + @override + FutureOr writeString( + String? value, { + required String key, + }) { + if (value != null) { + _data[key] = value; + } else { + _data.remove(key); + } + } +} diff --git a/catalyst_voices/packages/catalyst_voices_services/test/src/storage/vault/lock_factor_codec_test.dart b/catalyst_voices/packages/catalyst_voices_services/test/src/storage/vault/lock_factor_codec_test.dart new file mode 100644 index 0000000000..d4857ba8eb --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_services/test/src/storage/vault/lock_factor_codec_test.dart @@ -0,0 +1,20 @@ +import 'package:catalyst_voices_services/catalyst_voices_services.dart'; +import 'package:catalyst_voices_services/src/storage/vault/lock_factor_codec.dart'; +import 'package:test/expect.dart'; +import 'package:test/scaffolding.dart'; + +void main() { + test('encoding and decoding results in same lock factor', () { + // Given + const lock = PasswordLockFactor('pass1234'); + const LockFactorCodec codec = DefaultLockFactorCodec(); + + // When + final encoded = codec.encoder.convert(lock); + final decoded = codec.decoder.convert(encoded); + + // Then + expect(decoded, isA()); + expect(decoded.unlocks(lock), isTrue); + }); +} diff --git a/catalyst_voices/packages/catalyst_voices_services/test/src/storage/vault/lock_factor_test.dart b/catalyst_voices/packages/catalyst_voices_services/test/src/storage/vault/lock_factor_test.dart new file mode 100644 index 0000000000..3faf9efa23 --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_services/test/src/storage/vault/lock_factor_test.dart @@ -0,0 +1,132 @@ +import 'package:catalyst_voices_services/catalyst_voices_services.dart'; +import 'package:test/test.dart'; + +void main() { + group('LockFactor', () { + test('void lock serialization does work', () { + // Given + const lock = VoidLockFactor(); + + // When + final json = lock.toJson(); + final deserializedFactor = LockFactor.fromJson(json); + + // Then + expect(deserializedFactor, isA()); + }); + + test('description', () { + // Given + const lock = PasswordLockFactor('pass1234'); + + // When + final json = lock.toJson(); + final deserializedFactor = LockFactor.fromJson(json); + + // Then + expect(deserializedFactor, isA()); + expect(deserializedFactor.unlocks(lock), isTrue); + }); + }); + + group('VoidLockFactor', () { + test('does not unlocks any other lock', () { + // Given + const lock = VoidLockFactor(); + const locks = [ + VoidLockFactor(), + PasswordLockFactor('pass1234'), + ]; + + // When + final unlocks = locks.map((e) => lock.unlocks(e)).toList(); + + // Then + expect(unlocks, everyElement(false)); + }); + + test('toJson result has type field', () { + // Given + const lock = VoidLockFactor(); + + // When + final json = lock.toJson(); + + // Then + expect(json.containsKey('type'), isTrue); + }); + + test('toString equals class name', () { + // Given + const lock = VoidLockFactor(); + + // When + final asString = lock.toString(); + + // Then + expect(asString, lock.runtimeType.toString()); + }); + }); + + group('PasswordLockFactor', () { + test('unlocks other PasswordLockFactor with matching data', () { + // Given + const lock = PasswordLockFactor('admin1234'); + const otherLock = PasswordLockFactor('admin1234'); + + // When + final unlocks = lock.unlocks(otherLock); + + // Then + expect(unlocks, isTrue); + }); + + test('does not unlocks other PasswordLockFactor with different data', () { + // Given + const lock = PasswordLockFactor('admin1234'); + const otherLock = PasswordLockFactor('pass1234'); + + // When + final unlocks = lock.unlocks(otherLock); + + // Then + expect(unlocks, isFalse); + }); + + test('does not unlocks other non PasswordLockFactor', () { + // Given + const lock = PasswordLockFactor('admin1234'); + const otherLock = VoidLockFactor(); + + // When + final unlocks = lock.unlocks(otherLock); + + // Then + expect(unlocks, isFalse); + }); + + test('toJson result has type and data field', () { + // Given + const lock = PasswordLockFactor('admin1234'); + + // When + final json = lock.toJson(); + + // Then + expect(json.containsKey('type'), isTrue); + expect(json.containsKey('data'), isTrue); + }); + + test('toString does not contain password', () { + // Given + const password = 'admin1234'; + const lock = PasswordLockFactor(password); + + // When + final asString = lock.toString(); + + // Then + expect(asString, isNot(contains(password))); + }); + }); +} diff --git a/catalyst_voices/packages/catalyst_voices_services/test/src/storage/vault/secure_storage_vault_test.dart b/catalyst_voices/packages/catalyst_voices_services/test/src/storage/vault/secure_storage_vault_test.dart new file mode 100644 index 0000000000..cfba41eb73 --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_services/test/src/storage/vault/secure_storage_vault_test.dart @@ -0,0 +1,125 @@ +import 'package:catalyst_voices_services/src/storage/vault/lock_factor.dart'; +import 'package:catalyst_voices_services/src/storage/vault/secure_storage_vault.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:test/test.dart'; + +void main() { + late final FlutterSecureStorage flutterSecureStorage; + late final SecureStorageVault vault; + + setUpAll(() { + FlutterSecureStorage.setMockInitialValues({}); + + flutterSecureStorage = const FlutterSecureStorage(); + vault = SecureStorageVault(secureStorage: flutterSecureStorage); + }); + + tearDown(() async { + await flutterSecureStorage.deleteAll(); + }); + + test('lock and unlock factor fallbacks to lock state', () async { + // Given + + // When + final isUnlocked = await vault.isUnlocked; + + // Then + expect(isUnlocked, isFalse); + }); + + test('read returns null when not unlocked', () async { + // Given + const key = 'SecureStorageVault.key'; + const value = 'username'; + + // When + await flutterSecureStorage.write(key: key, value: value); + final readValue = await vault.readString(key: key); + + // Then + expect(readValue, isNull); + }); + + test('write wont happen when is locked', () async { + // Given + const key = 'key'; + const fKey = 'SecureStorageVault.$key'; + const value = 'username'; + + // When + await vault.writeString(value, key: key); + final readValue = await flutterSecureStorage.read(key: fKey); + + // Then + expect(readValue, isNull); + }); + + test('unlock update lock and returns null when locked', () async { + // Given + const lock = PasswordLockFactor('pass1234'); + const key = 'key'; + const value = 'username'; + + // When + await vault.setLock(lock); + final isUnlocked = await vault.unlock(lock); + await vault.writeString(value, key: key); + final readValue = await vault.readString(key: key); + + // Then + expect(isUnlocked, isTrue); + expect(readValue, value); + }); + + test('lock makes vault locked', () async { + // Given + const lock = PasswordLockFactor('pass1234'); + + // When + await vault.setLock(lock); + await vault.unlock(lock); + await vault.lock(); + + final isUnlocked = await vault.isUnlocked; + + // Then + expect(isUnlocked, isFalse); + }); + + test('clear removes all vault keys', () async { + // Given + const lock = PasswordLockFactor('pass1234'); + const vaultKeyValues = { + 'one': 'qqq', + 'two': 'qqq', + }; + const nonVaultKeyValues = { + 'three': 'qqq', + }; + + // When + await vault.setLock(lock); + await vault.unlock(lock); + + for (final entity in vaultKeyValues.entries) { + await vault.writeString(entity.value, key: entity.key); + } + + for (final entity in nonVaultKeyValues.entries) { + await flutterSecureStorage.write(key: entity.key, value: entity.value); + } + + await vault.clear(); + + final futures = + vaultKeyValues.keys.map((e) => vault.readString(key: e)).toList(); + + final values = await Future.wait(futures); + final fValues = await flutterSecureStorage.readAll(); + + // Then + expect(values, everyElement(isNull)); + expect(fValues, nonVaultKeyValues); + }); +} diff --git a/melos.yaml b/melos.yaml index 345b88fae7..4a40c3aa22 100644 --- a/melos.yaml +++ b/melos.yaml @@ -28,6 +28,7 @@ command: flutter_localized_locales: ^2.0.5 flutter_quill: ^10.5.13 flutter_quill_extensions: ^10.5.13 + flutter_secure_storage: ^9.2.2 formz: ^0.7.0 intl: ^0.19.0 logging: ^1.2.0 From ac764d6b011b398a949c998987f570695e59db4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Moli=C5=84ski?= <47773413+damian-molinski@users.noreply.github.com> Date: Mon, 23 Sep 2024 20:31:10 +0200 Subject: [PATCH 3/4] feat: discovery cta modal (#869) * feat: discovery alert dialog * feat: dialog barrier color --- .../lib/pages/discovery/discovery_page.dart | 13 ++++++++++++- .../lib/src/themes/catalyst.dart | 5 ++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/catalyst_voices/lib/pages/discovery/discovery_page.dart b/catalyst_voices/lib/pages/discovery/discovery_page.dart index 44dc96f11b..7049932a0d 100644 --- a/catalyst_voices/lib/pages/discovery/discovery_page.dart +++ b/catalyst_voices/lib/pages/discovery/discovery_page.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:catalyst_voices/pages/discovery/current_status_text.dart'; import 'package:catalyst_voices/pages/discovery/toggle_state_text.dart'; import 'package:catalyst_voices/widgets/widgets.dart'; @@ -84,7 +86,16 @@ class _Segment extends StatelessWidget { const Spacer(), VoicesFilledButton( child: const Text('CTA to Model'), - onTap: () {}, + onTap: () { + unawaited( + VoicesDialog.show( + context: context, + builder: (context) { + return const VoicesDesktopInfoDialog(title: Text('')); + }, + ), + ); + }, ), ], ), diff --git a/catalyst_voices/packages/catalyst_voices_brands/lib/src/themes/catalyst.dart b/catalyst_voices/packages/catalyst_voices_brands/lib/src/themes/catalyst.dart index 52149626e6..1e76d0c85a 100644 --- a/catalyst_voices/packages/catalyst_voices_brands/lib/src/themes/catalyst.dart +++ b/catalyst_voices/packages/catalyst_voices_brands/lib/src/themes/catalyst.dart @@ -310,9 +310,8 @@ ThemeData _buildThemeData( backgroundColor: voicesColorScheme.onSurfaceNeutralOpaqueLv0, ), dialogTheme: DialogTheme( - // TODO(damian-molinski): themed value needed. - // We don't have it defined yet. - barrierColor: const Color(0x612A3D61), + // N10-38 + barrierColor: const Color(0x212A3D61), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), clipBehavior: Clip.hardEdge, backgroundColor: voicesColorScheme.elevationsOnSurfaceNeutralLv1White, From d728f10f0d3c66e04d9fdb722944a5c6869c0cec Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Mon, 23 Sep 2024 11:52:01 -0700 Subject: [PATCH 4/4] ci(general): integrates catalyst-forge (#847) --- .github/workflows/ci.yml | 11 +--- blueprint.cue | 51 +++++++++++++++++++ catalyst-gateway/blueprint.cue | 2 + catalyst-gateway/event-db/blueprint.cue | 2 + .../tests/api_tests/blueprint.cue | 2 + catalyst-gateway/tests/blueprint.cue | 2 + .../tests/schemathesis_tests/blueprint.cue | 2 + catalyst_voices/Earthfile | 6 ++- catalyst_voices/blueprint.cue | 2 + .../test/wallet-automation/blueprint.cue | 2 + docs/blueprint.cue | 2 + utilities/wallet-tester/blueprint.cue | 2 + 12 files changed, 75 insertions(+), 11 deletions(-) create mode 100644 blueprint.cue create mode 100644 catalyst-gateway/blueprint.cue create mode 100644 catalyst-gateway/event-db/blueprint.cue create mode 100644 catalyst-gateway/tests/api_tests/blueprint.cue create mode 100644 catalyst-gateway/tests/blueprint.cue create mode 100644 catalyst-gateway/tests/schemathesis_tests/blueprint.cue create mode 100644 catalyst_voices/blueprint.cue create mode 100644 catalyst_voices_packages/catalyst_cardano/catalyst_cardano/test/wallet-automation/blueprint.cue create mode 100644 docs/blueprint.cue create mode 100644 utilities/wallet-tester/blueprint.cue diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6a83c0527f..0d887d7800 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,13 +10,6 @@ permissions: jobs: ci: - uses: input-output-hk/catalyst-ci/.github/workflows/ci.yml@master + uses: input-output-hk/catalyst-forge/.github/workflows/ci.yml@master with: - aws_ecr_registry: 332405224602.dkr.ecr.eu-central-1.amazonaws.com - aws_role_arn: arn:aws:iam::332405224602:role/ci - aws_region: eu-central-1 - secrets: - dockerhub_token: ${{ secrets.DOCKERHUB_TOKEN }} - dockerhub_username: ${{ secrets.DOCKERHUB_USERNAME }} - earthly_runner_address: ${{ secrets.EARTHLY_SATELLITE_ADDRESS }} - earthly_runner_secret: ${{ secrets.EARTHLY_RUNNER_SECRET }} \ No newline at end of file + forge_version: latest \ No newline at end of file diff --git a/blueprint.cue b/blueprint.cue new file mode 100644 index 0000000000..193d3916f6 --- /dev/null +++ b/blueprint.cue @@ -0,0 +1,51 @@ +version: "1.0" +global: { + ci: { + local: [ + "^check(-.*)?$", + "^build(-.*)?$", + "^package(-.*)?$", + "^test(-.*)?$", + "^release(-.*)?$", + "^publish(-.*)?$", + ] + registries: [ + ci.providers.aws.registry, + ] + providers: { + aws: { + region: "eu-central-1" + registry: "332405224602.dkr.ecr.eu-central-1.amazonaws.com" + role: "arn:aws:iam::332405224602:role/ci" + } + + docker: credentials: { + provider: "aws" + path: "global/ci/docker" + } + + earthly: { + credentials: { + provider: "aws" + path: "global/ci/earthly" + } + org: "Catalyst" + satellite: "ci" + version: "0.8.15" + } + + github: registry: "ghcr.io" + } + secrets: [ + { + name: "GITHUB_TOKEN" + optional: true + provider: "env" + path: "GITHUB_TOKEN" + }, + ] + tagging: { + strategy: "commit" + } + } +} diff --git a/catalyst-gateway/blueprint.cue b/catalyst-gateway/blueprint.cue new file mode 100644 index 0000000000..3278906156 --- /dev/null +++ b/catalyst-gateway/blueprint.cue @@ -0,0 +1,2 @@ +version: "1.0.0" +project: name: "gateway" diff --git a/catalyst-gateway/event-db/blueprint.cue b/catalyst-gateway/event-db/blueprint.cue new file mode 100644 index 0000000000..1dad7a98b0 --- /dev/null +++ b/catalyst-gateway/event-db/blueprint.cue @@ -0,0 +1,2 @@ +version: "1.0.0" +project: name: "gateway-event-db" diff --git a/catalyst-gateway/tests/api_tests/blueprint.cue b/catalyst-gateway/tests/api_tests/blueprint.cue new file mode 100644 index 0000000000..d15bf941d5 --- /dev/null +++ b/catalyst-gateway/tests/api_tests/blueprint.cue @@ -0,0 +1,2 @@ +version: "1.0.0" +project: name: "gateway-api-tests" diff --git a/catalyst-gateway/tests/blueprint.cue b/catalyst-gateway/tests/blueprint.cue new file mode 100644 index 0000000000..17b8489385 --- /dev/null +++ b/catalyst-gateway/tests/blueprint.cue @@ -0,0 +1,2 @@ +version: "1.0.0" +project: name: "catalyst-gateway-tests" diff --git a/catalyst-gateway/tests/schemathesis_tests/blueprint.cue b/catalyst-gateway/tests/schemathesis_tests/blueprint.cue new file mode 100644 index 0000000000..4a26b5160d --- /dev/null +++ b/catalyst-gateway/tests/schemathesis_tests/blueprint.cue @@ -0,0 +1,2 @@ +version: "1.0.0" +project: name: "gateway-schema-tests" diff --git a/catalyst_voices/Earthfile b/catalyst_voices/Earthfile index 989dbce65b..f22c850de5 100644 --- a/catalyst_voices/Earthfile +++ b/catalyst_voices/Earthfile @@ -83,6 +83,8 @@ package: publish: FROM +package - ARG tag='latest' - SAVE IMAGE voices-frontend:$tag \ No newline at end of file + ARG container="voices" + ARG tag="latest" + + SAVE IMAGE ${container}:${tag} \ No newline at end of file diff --git a/catalyst_voices/blueprint.cue b/catalyst_voices/blueprint.cue new file mode 100644 index 0000000000..c76d4145ee --- /dev/null +++ b/catalyst_voices/blueprint.cue @@ -0,0 +1,2 @@ +version: "1.0.0" +project: name: "voices" diff --git a/catalyst_voices_packages/catalyst_cardano/catalyst_cardano/test/wallet-automation/blueprint.cue b/catalyst_voices_packages/catalyst_cardano/catalyst_cardano/test/wallet-automation/blueprint.cue new file mode 100644 index 0000000000..cb4463fa63 --- /dev/null +++ b/catalyst_voices_packages/catalyst_cardano/catalyst_cardano/test/wallet-automation/blueprint.cue @@ -0,0 +1,2 @@ +version: "1.0.0" +project: name: "voices-wallet-automation-test" diff --git a/docs/blueprint.cue b/docs/blueprint.cue new file mode 100644 index 0000000000..d5db755573 --- /dev/null +++ b/docs/blueprint.cue @@ -0,0 +1,2 @@ +version: "1.0.0" +project: name: "voices-docs" diff --git a/utilities/wallet-tester/blueprint.cue b/utilities/wallet-tester/blueprint.cue new file mode 100644 index 0000000000..263a5d2095 --- /dev/null +++ b/utilities/wallet-tester/blueprint.cue @@ -0,0 +1,2 @@ +version: "1.0.0" +project: name: "voices-wallet-tester"