Merge branch 'main' into smart_dedupe
This commit is contained in:
@@ -142,6 +142,22 @@ var _updateFreeUserStorage = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
var _sendMail = &cobra.Command{
|
||||
Use: "send-mail <to-email> <from-email> <from-name>",
|
||||
Args: cobra.ExactArgs(3),
|
||||
Short: "Sends a test mail via the admin api",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
recoverWithLog()
|
||||
var flags = &model.AdminActionForUser{}
|
||||
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
||||
if f.Name == "admin-user" {
|
||||
flags.AdminEmail = f.Value.String()
|
||||
}
|
||||
})
|
||||
return ctrl.SendTestMail(context.Background(), *flags, args[0], args[1], args[2])
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(_adminCmd)
|
||||
_ = _userDetailsCmd.MarkFlagRequired("admin-user")
|
||||
@@ -159,5 +175,6 @@ func init() {
|
||||
_updateFreeUserStorage.Flags().StringP("user", "u", "", "The email of the user to update subscription for. (required)")
|
||||
// add a flag with no value --no-limit
|
||||
_updateFreeUserStorage.Flags().String("no-limit", "True", "When true, sets 100TB as storage limit, and expiry to current date + 100 years")
|
||||
_adminCmd.AddCommand(_userDetailsCmd, _disable2faCmd, _disablePasskeyCmd, _updateFreeUserStorage, _listUsers, _deleteUser)
|
||||
_sendMail.Flags().StringP("admin-user", "a", "", "The email of the admin user. ")
|
||||
_adminCmd.AddCommand(_userDetailsCmd, _disable2faCmd, _disablePasskeyCmd, _updateFreeUserStorage, _listUsers, _deleteUser, _sendMail)
|
||||
}
|
||||
|
||||
@@ -139,5 +139,28 @@ func (c *Client) UpdateFreePlanSub(ctx context.Context, userDetails *models.User
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (c *Client) SendTestMail(ctx context.Context, toEmail, fromEmail, fromName string) error {
|
||||
body := map[string]interface{}{
|
||||
"to": []string{toEmail},
|
||||
"fromName": fromName,
|
||||
"fromEmail": fromEmail,
|
||||
"subject": "Test mail from Ente",
|
||||
"body": "This is a test mail from Ente",
|
||||
}
|
||||
r, err := c.restClient.R().
|
||||
SetContext(ctx).
|
||||
SetBody(body).
|
||||
Post("/admin/mail")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.IsError() {
|
||||
return &ApiError{
|
||||
StatusCode: r.StatusCode(),
|
||||
Message: r.String(),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -156,6 +156,23 @@ func (c *ClICtrl) UpdateFreeStorage(ctx context.Context, params model.AdminActio
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClICtrl) SendTestMail(ctx context.Context, params model.AdminActionForUser, to, from, fromName string) error {
|
||||
accountCtx, err := c.buildAdminContext(ctx, params.AdminEmail)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.SendTestMail(accountCtx, to, from, fromName)
|
||||
if err != nil {
|
||||
if apiErr, ok := err.(*api.ApiError); ok && apiErr.StatusCode == 400 && strings.Contains(apiErr.Message, "Token is too old") {
|
||||
fmt.Printf("Error: old admin token, please re-authenticate using `ente account add` \n")
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Successfully sent test email to %s\n", to)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClICtrl) buildAdminContext(ctx context.Context, adminEmail string) (context.Context, error) {
|
||||
accounts, err := c.GetAccounts(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -171,6 +171,8 @@ smtp:
|
||||
email:
|
||||
# Optional name for sender
|
||||
sender-name:
|
||||
# Optional encryption
|
||||
encryption:
|
||||
```
|
||||
|
||||
| Variable | Description | Default |
|
||||
@@ -181,6 +183,7 @@ smtp:
|
||||
| `smtp.password` | SMTP auth password | |
|
||||
| `smtp.email` | Sender email address | |
|
||||
| `smtp.sender-name` | Custom name for email sender | |
|
||||
| `smtp.encryption` | Encryption method (tls, ssl) | |
|
||||
| `transmail.key` | Zeptomail API key | |
|
||||
|
||||
### WebAuthn Passkey Support
|
||||
|
||||
45
mobile/.gitignore
vendored
Normal file
45
mobile/.gitignore
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
melos_*.iml
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.packages
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
@@ -1040,6 +1040,13 @@
|
||||
"MistralAI"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Mobile01",
|
||||
"slug": "mobile01",
|
||||
"altNames": [
|
||||
"M01"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Mozilla"
|
||||
},
|
||||
|
||||
26
mobile/apps/auth/assets/custom-icons/icons/mobile01.svg
Normal file
26
mobile/apps/auth/assets/custom-icons/icons/mobile01.svg
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0"?>
|
||||
<svg width="320" height="280" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" version="1.1">
|
||||
|
||||
<g class="layer">
|
||||
<title>Layer 1</title>
|
||||
<g id="Layer1000">
|
||||
<g id="Layer1002">
|
||||
<g id="Layer1003">
|
||||
<path d="m123.08,34.29c-66.43,0 -120.27,53.85 -120.27,120.27c0,66.43 53.85,120.27 120.27,120.27c66.47,0 120.32,-53.85 120.32,-120.27c0,-66.43 -53.85,-120.27 -120.32,-120.27zm0,215.67c-52.67,0 -95.36,-42.73 -95.36,-95.4c0,-52.67 42.68,-95.4 95.36,-95.4c52.72,0 95.4,42.73 95.4,95.4c0,52.67 -42.68,95.4 -95.4,95.4z" fill="#2a5e00" fill-rule="evenodd" id="path7"/>
|
||||
<g id="Layer1004">
|
||||
<g id="Layer1005">
|
||||
<path d="m138.72,146.29l59.61,-41.47l7.78,33.7l-67.39,7.78z" fill="#2a5e00" fill-rule="evenodd" id="path8"/>
|
||||
<path d="m110.88,146.29l-59.61,-41.47l-7.78,33.7l67.39,7.78z" fill="#2a5e00" fill-rule="evenodd" id="path9"/>
|
||||
</g>
|
||||
<path d="m43.95,192.02l74.62,49.75l87.12,-78.8l-161.75,29.05z" fill="#2a5e00" fill-rule="evenodd" id="path10"/>
|
||||
</g>
|
||||
<path d="m94.24,59.29l-30.48,-55.1l54.26,33.24l-23.79,21.86z" fill="#2a5e00" fill-rule="evenodd" id="path11"/>
|
||||
<path d="m202.64,78.1l30.43,-55.1l-54.22,33.24l23.79,21.86z" fill="#2a5e00" fill-rule="evenodd" id="path12"/>
|
||||
</g>
|
||||
<path d="m275.63,274.67l29.35,0l0,-240.76l-29.35,0l0,240.76z" fill="#2a5e00" fill-rule="evenodd" id="path13"/>
|
||||
<path d="m317.94,125.93c0,15.3 -12.33,27.63 -27.63,27.63c-15.26,0 -27.63,-12.33 -27.63,-27.63c0,-15.26 12.37,-27.63 27.63,-27.63c15.3,0 27.63,12.37 27.63,27.63z" fill="#2a5e00" fill-rule="evenodd" id="path14"/>
|
||||
<path d="m288.84,33.91l-41.76,0l16.76,45.99l23.58,0l1.42,-45.99z" fill="#2a5e00" fill-rule="evenodd" id="path15"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
Submodule mobile/apps/auth/flutter updated: edada7c56e...2663184aa7
@@ -126,6 +126,8 @@ PODS:
|
||||
- sqlite3/perf-threadsafe
|
||||
- sqlite3/rtree
|
||||
- SwiftyGif (5.4.5)
|
||||
- ua_client_hints (1.4.1):
|
||||
- Flutter
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
|
||||
@@ -158,6 +160,7 @@ DEPENDENCIES:
|
||||
- sodium_libs (from `.symlinks/plugins/sodium_libs/ios`)
|
||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
|
||||
- ua_client_hints (from `.symlinks/plugins/ua_client_hints/ios`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
@@ -228,6 +231,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
||||
sqlite3_flutter_libs:
|
||||
:path: ".symlinks/plugins/sqlite3_flutter_libs/darwin"
|
||||
ua_client_hints:
|
||||
:path: ".symlinks/plugins/ua_client_hints/ios"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
|
||||
@@ -268,6 +273,7 @@ SPEC CHECKSUMS:
|
||||
sqlite3: 3e82a2daae39ba3b41ae6ee84a130494585460fc
|
||||
sqlite3_flutter_libs: 2c48c4ee7217fd653251975e43412250d5bcbbe2
|
||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||
ua_client_hints: aeabd123262c087f0ce151ef96fa3ab77bfc8b38
|
||||
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
||||
|
||||
PODFILE CHECKSUM: 78f002751f1a8f65042b8da97902ba4124271c5a
|
||||
|
||||
@@ -111,6 +111,7 @@
|
||||
"importAegisGuide": "Use the \"Export the vault\" option in Aegis's Settings.\n\nIf your vault is encrypted, you will need to enter vault password to decrypt the vault.",
|
||||
"import2FasGuide": "Use the \"Settings->Backup -Export\" option in 2FAS.\n\nIf your backup is encrypted, you will need to enter the password to decrypt the backup",
|
||||
"importLastpassGuide": "Use the \"Transfer accounts\" option within Lastpass Authenticator Settings and press \"Export accounts to file\". Import the JSON downloaded.",
|
||||
"importProtonAuthGuide": "Use the \"Export\" option in Proton Authenticator Settings to export your codes.",
|
||||
"exportCodes": "Export codes",
|
||||
"importLabel": "Import",
|
||||
"importInstruction": "Please select a file that contains a list of your codes in the following format",
|
||||
@@ -519,5 +520,12 @@
|
||||
"algorithm": "Algorithm",
|
||||
"type": "Type",
|
||||
"period": "Period",
|
||||
"digits": "Digits"
|
||||
"digits": "Digits",
|
||||
"importFromGallery": "Import from gallery",
|
||||
"errorCouldNotReadImage": "Could not read the selected image file.",
|
||||
"errorInvalidQRCode": "Invalid QR Code",
|
||||
"errorInvalidQRCodeBody": "The scanned QR code is not a valid 2FA account.",
|
||||
"errorNoQRCode": "No QR code found",
|
||||
"errorGenericTitle": "An Error Occurred",
|
||||
"errorGenericBody": "An unexpected error occurred while importing."
|
||||
}
|
||||
@@ -35,18 +35,22 @@ import 'package:ente_auth/ui/settings_page.dart';
|
||||
import 'package:ente_auth/ui/sort_option_menu.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:ente_auth/utils/totp_util.dart';
|
||||
import 'package:ente_events/event_bus.dart';
|
||||
import 'package:ente_lock_screen/lock_screen_settings.dart';
|
||||
import 'package:ente_lock_screen/ui/app_lock.dart';
|
||||
import 'package:ente_ui/pages/base_home_page.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import 'package:image/image.dart' as img;
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:move_to_background/move_to_background.dart';
|
||||
import 'package:zxing2/qrcode.dart';
|
||||
|
||||
class HomePage extends BaseHomePage {
|
||||
const HomePage({super.key});
|
||||
@@ -62,6 +66,7 @@ class _HomePageState extends State<HomePage> {
|
||||
);
|
||||
bool _hasLoaded = false;
|
||||
bool _isSettingsOpen = false;
|
||||
bool _isImportingFromGallery = false;
|
||||
final Logger _logger = Logger("HomePage");
|
||||
final scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
|
||||
@@ -288,6 +293,70 @@ class _HomePageState extends State<HomePage> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _importFromGallery() async {
|
||||
|
||||
final l10n = AppLocalizations.of(context);
|
||||
|
||||
if (_isImportingFromGallery) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isImportingFromGallery = true;
|
||||
});
|
||||
|
||||
try {
|
||||
final FilePickerResult? result = await FilePicker.platform.pickFiles(
|
||||
type: FileType.image,
|
||||
);
|
||||
|
||||
if (result == null || result.files.single.path == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String imagePath = result.files.single.path!;
|
||||
final rawImage = await File(imagePath).readAsBytes();
|
||||
final image = img.decodeImage(rawImage);
|
||||
|
||||
if (image == null) {
|
||||
await showErrorDialog(context, l10n.error, l10n.errorCouldNotReadImage);
|
||||
return;
|
||||
}
|
||||
|
||||
final source = RGBLuminanceSource(
|
||||
image.width, image.height,
|
||||
image.getBytes(order: img.ChannelOrder.rgba).buffer.asInt32List(),
|
||||
);
|
||||
final bitmap = BinaryBitmap(HybridBinarizer(source));
|
||||
final reader = QRCodeReader();
|
||||
final Result decodeResult = reader.decode(bitmap);
|
||||
final String code = decodeResult.text;
|
||||
try{
|
||||
final newCode = Code.fromOTPAuthUrl(code);
|
||||
await CodeStore.instance.addCode(newCode, shouldSync: false);
|
||||
}
|
||||
catch (e){
|
||||
await showErrorDialog(
|
||||
context, l10n.errorInvalidQRCode, l10n.errorInvalidQRCodeBody,
|
||||
);
|
||||
}
|
||||
}
|
||||
on ReaderException {
|
||||
showToast(context, l10n.errorNoQRCode);
|
||||
}
|
||||
|
||||
catch (e) {
|
||||
await showErrorDialog(
|
||||
context, l10n.errorGenericTitle, l10n.errorGenericBody,
|
||||
);
|
||||
}
|
||||
finally {
|
||||
setState(() {
|
||||
_isImportingFromGallery = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _redirectToScannerPage() async {
|
||||
final Code? code = await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
@@ -745,6 +814,13 @@ class _HomePageState extends State<HomePage> {
|
||||
labelWidget: SpeedDialLabelWidget(context.l10n.enterDetailsManually),
|
||||
onTap: _redirectToManualEntryPage,
|
||||
),
|
||||
SpeedDialChild(
|
||||
child: const Icon(Icons.image),
|
||||
backgroundColor: Theme.of(context).colorScheme.fabBackgroundColor,
|
||||
foregroundColor: Theme.of(context).colorScheme.fabForegroundColor,
|
||||
labelWidget: SpeedDialLabelWidget(context.l10n.importFromGallery),
|
||||
onTap: _importFromGallery,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:ente_auth/ui/settings/data/import/encrypted_ente_import.dart';
|
||||
import 'package:ente_auth/ui/settings/data/import/google_auth_import.dart';
|
||||
import 'package:ente_auth/ui/settings/data/import/lastpass_import.dart';
|
||||
import 'package:ente_auth/ui/settings/data/import/plain_text_import.dart';
|
||||
import 'package:ente_auth/ui/settings/data/import/proton_import.dart';
|
||||
import 'package:ente_auth/ui/settings/data/import/raivo_plain_text_import.dart';
|
||||
import 'package:ente_auth/ui/settings/data/import/two_fas_import.dart';
|
||||
import 'package:ente_auth/ui/settings/data/import_page.dart';
|
||||
@@ -43,6 +44,9 @@ class ImportService {
|
||||
case ImportType.lastpass:
|
||||
await showLastpassImportInstruction(context);
|
||||
break;
|
||||
case ImportType.proton:
|
||||
await showProtonImportInstruction(context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
171
mobile/apps/auth/lib/ui/settings/data/import/proton_import.dart
Normal file
171
mobile/apps/auth/lib/ui/settings/data/import/proton_import.dart
Normal file
@@ -0,0 +1,171 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/models/code.dart';
|
||||
import 'package:ente_auth/services/authenticator_service.dart';
|
||||
import 'package:ente_auth/store/code_store.dart';
|
||||
import 'package:ente_auth/ui/common/progress_dialog.dart';
|
||||
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
|
||||
import 'package:ente_auth/ui/components/dialog_widget.dart';
|
||||
import 'package:ente_auth/ui/components/models/button_type.dart';
|
||||
import 'package:ente_auth/ui/settings/data/import/import_success.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
Future<void> showProtonImportInstruction(BuildContext context) async {
|
||||
final l10n = context.l10n;
|
||||
final result = await showDialogWidget(
|
||||
context: context,
|
||||
title: l10n.importFromApp("Proton Authenticator"),
|
||||
body: l10n.importProtonAuthGuide,
|
||||
buttons: [
|
||||
ButtonWidget(
|
||||
buttonType: ButtonType.primary,
|
||||
labelText: l10n.importSelectJsonFile,
|
||||
isInAlert: true,
|
||||
buttonSize: ButtonSize.large,
|
||||
buttonAction: ButtonAction.first,
|
||||
),
|
||||
ButtonWidget(
|
||||
buttonType: ButtonType.secondary,
|
||||
labelText: context.l10n.cancel,
|
||||
buttonSize: ButtonSize.large,
|
||||
isInAlert: true,
|
||||
buttonAction: ButtonAction.second,
|
||||
),
|
||||
],
|
||||
);
|
||||
if (result?.action != null && result!.action != ButtonAction.cancel) {
|
||||
if (result.action == ButtonAction.first) {
|
||||
await _pickProtonJsonFile(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _pickProtonJsonFile(BuildContext context) async {
|
||||
final l10n = context.l10n;
|
||||
FilePickerResult? result = await FilePicker.platform
|
||||
.pickFiles(dialogTitle: l10n.importSelectJsonFile);
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
final ProgressDialog progressDialog =
|
||||
createProgressDialog(context, l10n.pleaseWait);
|
||||
await progressDialog.show();
|
||||
try {
|
||||
String path = result.files.single.path!;
|
||||
int? count = await _processProtonExportFile(context, path, progressDialog);
|
||||
await progressDialog.hide();
|
||||
if (count != null) {
|
||||
await importSuccessDialog(context, count);
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logger('ProtonImport')
|
||||
.severe('exception while processing proton import', e, s);
|
||||
await progressDialog.hide();
|
||||
await showErrorDialog(
|
||||
context,
|
||||
context.l10n.sorry,
|
||||
"${context.l10n.importFailureDescNew}\n Error: ${e.toString()}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<int?> _processProtonExportFile(
|
||||
BuildContext context,
|
||||
String path,
|
||||
final ProgressDialog dialog,
|
||||
) async {
|
||||
File file = File(path);
|
||||
|
||||
final jsonString = await file.readAsString();
|
||||
final decodedJson = jsonDecode(jsonString);
|
||||
|
||||
// Validate that this is a Proton export
|
||||
if (decodedJson['version'] == null || decodedJson['entries'] == null) {
|
||||
await dialog.hide();
|
||||
await showErrorDialog(
|
||||
context,
|
||||
'Invalid Proton export',
|
||||
'The selected file is not a valid Proton Authenticator export.',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
final parsedCodes = <Code>[];
|
||||
final entries = decodedJson['entries'] as List;
|
||||
|
||||
for (var entry in entries) {
|
||||
try {
|
||||
final content = entry['content'];
|
||||
if (content == null) {
|
||||
continue; // Skip entries without content
|
||||
}
|
||||
|
||||
final entryType = content['entry_type'] as String?;
|
||||
if (entryType != 'Totp' && entryType != 'Steam') {
|
||||
// log warning
|
||||
Logger('ProtonImport').warning('Unsupported entry type: $entryType');
|
||||
continue; // Skip non-TOTP and non-Steam entries
|
||||
}
|
||||
|
||||
Code code;
|
||||
|
||||
if (entryType == 'Steam') {
|
||||
// Handle Steam entries with steam:// format
|
||||
final steamUri = content['uri'] as String?;
|
||||
if (steamUri == null || !steamUri.startsWith('steam://')) {
|
||||
continue; // Skip invalid Steam URIs
|
||||
}
|
||||
|
||||
final secret = steamUri.split('steam://')[1];
|
||||
final name = content['name'] as String? ?? '';
|
||||
|
||||
code = Code.fromAccountAndSecret(
|
||||
Type.steam,
|
||||
'', // Steam doesn't typically have separate account
|
||||
name, // Use name as issuer
|
||||
secret,
|
||||
null,
|
||||
Code.steamDigits,
|
||||
);
|
||||
} else {
|
||||
// Handle TOTP entries with otpauth:// format
|
||||
final otpUri = content['uri'] as String?;
|
||||
if (otpUri == null || !otpUri.startsWith('otpauth://')) {
|
||||
continue; // Skip invalid OTP URIs
|
||||
}
|
||||
// Create code from OTP auth URL
|
||||
code = Code.fromOTPAuthUrl(otpUri);
|
||||
}
|
||||
|
||||
// Add note if present
|
||||
final note = entry['note'] as String?;
|
||||
if (note != null && note.isNotEmpty) {
|
||||
code = code.copyWith(
|
||||
display: code.display.copyWith(note: note),
|
||||
);
|
||||
}
|
||||
|
||||
parsedCodes.add(code);
|
||||
} catch (e, s) {
|
||||
Logger('ProtonImport').warning('Failed to parse entry', e, s);
|
||||
// Continue processing other entries
|
||||
}
|
||||
}
|
||||
|
||||
// Add all parsed codes to the store
|
||||
for (final code in parsedCodes) {
|
||||
await CodeStore.instance.addCode(code, shouldSync: false);
|
||||
}
|
||||
|
||||
// Trigger sync
|
||||
unawaited(AuthenticatorService.instance.onlineSync());
|
||||
|
||||
return parsedCodes.length;
|
||||
}
|
||||
@@ -17,6 +17,7 @@ enum ImportType {
|
||||
twoFas,
|
||||
bitwarden,
|
||||
lastpass,
|
||||
proton,
|
||||
}
|
||||
|
||||
class ImportCodePage extends StatelessWidget {
|
||||
@@ -29,6 +30,7 @@ class ImportCodePage extends StatelessWidget {
|
||||
ImportType.aegis,
|
||||
ImportType.bitwarden,
|
||||
ImportType.googleAuthenticator,
|
||||
ImportType.proton,
|
||||
ImportType.ravio,
|
||||
ImportType.lastpass,
|
||||
];
|
||||
@@ -51,6 +53,8 @@ class ImportCodePage extends StatelessWidget {
|
||||
return 'Bitwarden';
|
||||
case ImportType.lastpass:
|
||||
return 'LastPass Authenticator';
|
||||
case ImportType.proton:
|
||||
return 'Proton Authenticator';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -201,6 +201,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: charcode
|
||||
sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -466,7 +474,7 @@ packages:
|
||||
source: path
|
||||
version: "1.0.0"
|
||||
ente_utils:
|
||||
dependency: transitive
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
path: "../../packages/utils"
|
||||
relative: true
|
||||
@@ -532,10 +540,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_picker
|
||||
sha256: ef9908739bdd9c476353d6adff72e88fd00c625f5b959ae23f7567bd5137db0a
|
||||
sha256: e7e16c9d15c36330b94ca0e2ad8cb61f93cd5282d0158c09805aed13b5452f22
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.2.0"
|
||||
version: "10.3.2"
|
||||
file_saver:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -951,7 +959,7 @@ packages:
|
||||
source: hosted
|
||||
version: "0.1.0"
|
||||
image:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image
|
||||
sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928"
|
||||
@@ -2021,6 +2029,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
zxing2:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: zxing2
|
||||
sha256: "2677c49a3b9ca9457cb1d294fd4bd5041cac6aab8cdb07b216ba4e98945c684f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.4"
|
||||
sdks:
|
||||
dart: ">=3.7.2 <4.0.0"
|
||||
flutter: ">=3.29.0"
|
||||
|
||||
@@ -50,7 +50,7 @@ dependencies:
|
||||
expansion_tile_card: ^3.0.0
|
||||
ffi: ^2.1.0
|
||||
figma_squircle: ^0.6.3
|
||||
file_picker: ^10.2.0
|
||||
file_picker: ^10.3.2
|
||||
file_saver: ^0.3.1
|
||||
fixnum: ^1.1.0
|
||||
fk_user_agent: # no package updates on pub.dev
|
||||
@@ -84,6 +84,7 @@ dependencies:
|
||||
google_nav_bar: ^5.0.5 #supported
|
||||
gradient_borders: ^1.0.0
|
||||
http: ^1.1.0
|
||||
image: ^4.5.4
|
||||
intl: ^0.20.2
|
||||
io: ^1.0.4
|
||||
json_annotation: ^4.5.0
|
||||
@@ -130,6 +131,7 @@ dependencies:
|
||||
win32: ^5.1.1
|
||||
window_manager: ^0.5.0
|
||||
xdg_directories: ^1.0.4
|
||||
zxing2: ^0.2.4
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^2.1.11
|
||||
|
||||
22
mobile/apps/auth/pubspec_overrides.yaml
Normal file
22
mobile/apps/auth/pubspec_overrides.yaml
Normal file
@@ -0,0 +1,22 @@
|
||||
# melos_managed_dependency_overrides: ente_accounts,ente_base,ente_configuration,ente_events,ente_lock_screen,ente_logging,ente_network,ente_strings,ente_ui,ente_utils
|
||||
dependency_overrides:
|
||||
ente_accounts:
|
||||
path: ../../packages/accounts
|
||||
ente_base:
|
||||
path: ../../packages/base
|
||||
ente_configuration:
|
||||
path: ../../packages/configuration
|
||||
ente_events:
|
||||
path: ../../packages/events
|
||||
ente_lock_screen:
|
||||
path: ../../packages/lock_screen
|
||||
ente_logging:
|
||||
path: ../../packages/logging
|
||||
ente_network:
|
||||
path: ../../packages/network
|
||||
ente_strings:
|
||||
path: ../../packages/strings
|
||||
ente_ui:
|
||||
path: ../../packages/ui
|
||||
ente_utils:
|
||||
path: ../../packages/utils
|
||||
22
mobile/apps/locker/pubspec_overrides.yaml
Normal file
22
mobile/apps/locker/pubspec_overrides.yaml
Normal file
@@ -0,0 +1,22 @@
|
||||
# melos_managed_dependency_overrides: ente_accounts,ente_base,ente_configuration,ente_events,ente_lock_screen,ente_logging,ente_network,ente_strings,ente_ui,ente_utils
|
||||
dependency_overrides:
|
||||
ente_accounts:
|
||||
path: ../../packages/accounts
|
||||
ente_base:
|
||||
path: ../../packages/base
|
||||
ente_configuration:
|
||||
path: ../../packages/configuration
|
||||
ente_events:
|
||||
path: ../../packages/events
|
||||
ente_lock_screen:
|
||||
path: ../../packages/lock_screen
|
||||
ente_logging:
|
||||
path: ../../packages/logging
|
||||
ente_network:
|
||||
path: ../../packages/network
|
||||
ente_strings:
|
||||
path: ../../packages/strings
|
||||
ente_ui:
|
||||
path: ../../packages/ui
|
||||
ente_utils:
|
||||
path: ../../packages/utils
|
||||
@@ -1000,20 +1000,33 @@ class FilesDB with SqlDbBase {
|
||||
|
||||
final batch = localIDsList.sublist(i, endIndex);
|
||||
final placeholders = List.filled(batch.length, '?').join(',');
|
||||
final List<String> alreadyUploaded = [];
|
||||
// find localIDs that are already uploaded
|
||||
final result = await db.execute('''
|
||||
SELECT DISTINCT $columnLocalID
|
||||
FROM $filesTable
|
||||
WHERE $columnLocalID IN ($placeholders)
|
||||
AND ($columnUploadedFileID IS NOT NULL AND $columnUploadedFileID != -1)
|
||||
''');
|
||||
|
||||
for (final row in result) {
|
||||
alreadyUploaded.add(row[columnLocalID] as String);
|
||||
}
|
||||
final uploadedPlaceholders =
|
||||
alreadyUploaded.map((id) => "'$id'").join(',');
|
||||
final r = await db.execute(
|
||||
'''
|
||||
DELETE FROM $filesTable
|
||||
WHERE $columnLocalID IN ($placeholders)
|
||||
AND ($columnCollectionID IS NULL OR $columnCollectionID = -1)
|
||||
WHERE $columnLocalID IN ($uploadedPlaceholders)
|
||||
AND ($columnUploadedFileID IS NULL OR $columnUploadedFileID = -1)
|
||||
''',
|
||||
batch,
|
||||
);
|
||||
|
||||
if (r.isNotEmpty) {
|
||||
_logger
|
||||
.fine("Batch ${(i ~/ batchSize) + 1}: Removed ${r.length} files");
|
||||
_logger.warning(
|
||||
"Batch ${(i ~/ batchSize) + 1}: Removed duplicate ${r.length} files",
|
||||
);
|
||||
totalRemoved += r.length;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
import "dart:convert";
|
||||
import "dart:io";
|
||||
import "dart:math";
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
@@ -102,6 +103,7 @@ class UserService {
|
||||
data: {
|
||||
"email": email,
|
||||
"purpose": isChangeEmail ? "change" : purpose ?? "",
|
||||
"mobile": Platform.isIOS || Platform.isAndroid,
|
||||
},
|
||||
);
|
||||
await dialog.hide();
|
||||
|
||||
@@ -124,6 +124,10 @@ class _AppLockState extends State<AppLock> with WidgetsBindingObserver {
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
],
|
||||
onGenerateRoute: (settings) {
|
||||
// On Android disabling deep links doesn't work, so this function
|
||||
// also gets triggered like /?generatedId=xyz&mainKey=abcd
|
||||
// Related: https://github.com/flutter/flutter/issues/119938
|
||||
|
||||
switch (settings.name) {
|
||||
case '/lock-screen':
|
||||
return PageRouteBuilder(
|
||||
@@ -135,7 +139,7 @@ class _AppLockState extends State<AppLock> with WidgetsBindingObserver {
|
||||
this.widget.builder(settings.arguments),
|
||||
);
|
||||
}
|
||||
return PageRouteBuilder(pageBuilder: (_, __, ___) => this._lockScreen);
|
||||
return null;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ class GalleryFileWidget extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _GalleryFileWidgetState extends State<GalleryFileWidget> {
|
||||
static const borderRadius = BorderRadius.all(Radius.circular(1));
|
||||
late bool _isFileSelected;
|
||||
|
||||
@override
|
||||
@@ -92,7 +93,7 @@ class _GalleryFileWidgetState extends State<GalleryFileWidget> {
|
||||
children: [
|
||||
ClipRRect(
|
||||
key: ValueKey(heroTag),
|
||||
borderRadius: BorderRadius.circular(1),
|
||||
borderRadius: borderRadius,
|
||||
child: Hero(
|
||||
tag: heroTag,
|
||||
flightShuttleBuilder: (
|
||||
@@ -104,13 +105,13 @@ class _GalleryFileWidgetState extends State<GalleryFileWidget> {
|
||||
) =>
|
||||
thumbnailWidget,
|
||||
transitionOnUserGestures: true,
|
||||
child: ColorFiltered(
|
||||
colorFilter: const ColorFilter.mode(
|
||||
Color.fromARGB(102, 0, 0, 0),
|
||||
BlendMode.darken,
|
||||
),
|
||||
child: thumbnailWidget,
|
||||
),
|
||||
child: thumbnailWidget,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Color.fromARGB(102, 0, 0, 0),
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
@@ -126,7 +127,7 @@ class _GalleryFileWidgetState extends State<GalleryFileWidget> {
|
||||
)
|
||||
: ClipRRect(
|
||||
key: ValueKey(heroTag),
|
||||
borderRadius: BorderRadius.circular(1),
|
||||
borderRadius: borderRadius,
|
||||
child: Hero(
|
||||
tag: heroTag,
|
||||
flightShuttleBuilder: (
|
||||
|
||||
33
mobile/apps/photos/pubspec_overrides.yaml
Normal file
33
mobile/apps/photos/pubspec_overrides.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
# melos_managed_dependency_overrides: ffi,flutter_sodium,intl,js,media_kit,media_kit_libs_ios_video,media_kit_libs_video,media_kit_video,protobuf,video_player,watcher,win32
|
||||
dependency_overrides:
|
||||
ffi: 2.1.0
|
||||
flutter_sodium:
|
||||
git:
|
||||
url: https://github.com/ente-io/flutter_sodium
|
||||
ref: v2-embeddings-only
|
||||
intl: ^0.20.2
|
||||
js: ^0.6.7
|
||||
media_kit:
|
||||
git:
|
||||
url: https://github.com/media-kit/media-kit
|
||||
path: media_kit
|
||||
media_kit_libs_ios_video:
|
||||
git:
|
||||
url: https://github.com/media-kit/media-kit
|
||||
path: libs/ios/media_kit_libs_ios_video
|
||||
media_kit_libs_video:
|
||||
git:
|
||||
url: https://github.com/media-kit/media-kit
|
||||
path: libs/universal/media_kit_libs_video
|
||||
media_kit_video:
|
||||
git:
|
||||
url: https://github.com/media-kit/media-kit
|
||||
path: media_kit_video
|
||||
protobuf: ^3.1.0
|
||||
video_player:
|
||||
git:
|
||||
url: https://github.com/ente-io/packages.git
|
||||
ref: android_video_roation_fix
|
||||
path: packages/video_player/video_player/
|
||||
watcher: ^1.1.0
|
||||
win32: 5.10.1
|
||||
@@ -1,3 +1,4 @@
|
||||
- Neeraj: Fix for double enteries for local file
|
||||
- (prtk) Fix widget initial launch on iOS
|
||||
- Similar images debug screen (Settings > Backup > Free up space > Similar images)
|
||||
- (prtk) Upgrade Flutter version to 3.32.8
|
||||
|
||||
70
mobile/melos.yaml
Normal file
70
mobile/melos.yaml
Normal file
@@ -0,0 +1,70 @@
|
||||
# A name for your monorepo
|
||||
name: ente_workspace
|
||||
|
||||
# Location of your packages, using glob patterns.
|
||||
# Melos will find any pubspec.yaml file inside these directories.
|
||||
packages:
|
||||
- apps/*
|
||||
- packages/*
|
||||
|
||||
# Scripts that can be run with `melos run <script_name>`
|
||||
scripts:
|
||||
# The very first command you should run. It links all your local packages
|
||||
# together and runs `flutter pub get` everywhere.
|
||||
bootstrap:
|
||||
run: melos bootstrap
|
||||
description: Bootstrap the workspace, linking local packages.
|
||||
|
||||
# --- GLOBAL COMMANDS (run on all apps & packages) ---
|
||||
|
||||
get:all:
|
||||
run: melos exec -- "flutter pub get"
|
||||
description: Run "flutter pub get" in all projects.
|
||||
|
||||
clean:all:
|
||||
run: melos exec -- "flutter clean"
|
||||
description: Run "flutter clean" in all projects.
|
||||
|
||||
# --- APP-SPECIFIC CLEAN COMMANDS ---
|
||||
# These scripts use `--scope` to target only a specific app.
|
||||
# IMPORTANT: The scope name (e.g., "photos") must match the `name`
|
||||
# field in that app's `pubspec.yaml` file.
|
||||
|
||||
clean:photos:
|
||||
run: melos exec --scope="photos" -- "flutter clean"
|
||||
description: Clean the 'photos' app.
|
||||
|
||||
clean:auth:
|
||||
run: melos exec --scope="auth" -- "flutter clean"
|
||||
description: Clean the 'auth' app.
|
||||
|
||||
clean:locker:
|
||||
run: melos exec --scope="locker" -- "flutter clean"
|
||||
description: Clean the 'locker' app.
|
||||
|
||||
|
||||
run:photos:apk:
|
||||
run: melos exec --scope="photos" -- "flutter run -t lib/main.dart --flavor independent"
|
||||
description: Run the 'photos' app in independent mode.
|
||||
|
||||
run:auth:apk:
|
||||
run: melos exec --scope="auth" -- "flutter run -t lib/main.dart --flavor independent"
|
||||
description: Run the 'auth' app in independent mode.
|
||||
|
||||
run:locker:apk:
|
||||
run: melos exec --scope="locker" -- "flutter run -t lib/main.dart --flavor independent"
|
||||
description: Run the 'locker' app in independent mode.
|
||||
|
||||
# --- APP-SPECIFIC BUILD COMMANDS ---
|
||||
|
||||
build:photos:apk:
|
||||
run: melos exec --scope="photos" -- "flutter build apk --release"
|
||||
description: Build a release APK for the 'photos' app.
|
||||
|
||||
build:auth:appbundle:
|
||||
run: melos exec --scope="auth" -- "flutter build appbundle --release"
|
||||
description: Build a release AppBundle for the 'auth' app.
|
||||
|
||||
build:locker:ios:
|
||||
run: melos exec --scope="locker" -- "flutter build ios --release"
|
||||
description: Build a release iOS archive for the 'locker' app.
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
import "dart:convert";
|
||||
import "dart:io";
|
||||
import "dart:math";
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
@@ -88,6 +89,7 @@ class UserService {
|
||||
data: {
|
||||
"email": email,
|
||||
"purpose": isChangeEmail ? "change" : purpose ?? "",
|
||||
"mobile": Platform.isIOS || Platform.isAndroid,
|
||||
},
|
||||
);
|
||||
await dialog.hide();
|
||||
|
||||
@@ -231,7 +231,7 @@ packages:
|
||||
source: path
|
||||
version: "1.0.0"
|
||||
ente_logging:
|
||||
dependency: transitive
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
path: "../logging"
|
||||
relative: true
|
||||
|
||||
20
mobile/packages/accounts/pubspec_overrides.yaml
Normal file
20
mobile/packages/accounts/pubspec_overrides.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
# melos_managed_dependency_overrides: ente_base,ente_configuration,ente_events,ente_lock_screen,ente_logging,ente_network,ente_strings,ente_ui,ente_utils
|
||||
dependency_overrides:
|
||||
ente_base:
|
||||
path: ../base
|
||||
ente_configuration:
|
||||
path: ../configuration
|
||||
ente_events:
|
||||
path: ../events
|
||||
ente_lock_screen:
|
||||
path: ../lock_screen
|
||||
ente_logging:
|
||||
path: ../logging
|
||||
ente_network:
|
||||
path: ../network
|
||||
ente_strings:
|
||||
path: ../strings
|
||||
ente_ui:
|
||||
path: ../ui
|
||||
ente_utils:
|
||||
path: ../utils
|
||||
8
mobile/packages/configuration/pubspec_overrides.yaml
Normal file
8
mobile/packages/configuration/pubspec_overrides.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
# melos_managed_dependency_overrides: ente_base,ente_events,ente_logging
|
||||
dependency_overrides:
|
||||
ente_base:
|
||||
path: ../base
|
||||
ente_events:
|
||||
path: ../events
|
||||
ente_logging:
|
||||
path: ../logging
|
||||
@@ -201,7 +201,7 @@ packages:
|
||||
source: path
|
||||
version: "1.0.0"
|
||||
ente_base:
|
||||
dependency: transitive
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
path: "../base"
|
||||
relative: true
|
||||
@@ -231,14 +231,14 @@ packages:
|
||||
source: path
|
||||
version: "1.0.0"
|
||||
ente_logging:
|
||||
dependency: transitive
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
path: "../logging"
|
||||
relative: true
|
||||
source: path
|
||||
version: "1.0.0"
|
||||
ente_network:
|
||||
dependency: transitive
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
path: "../network"
|
||||
relative: true
|
||||
|
||||
20
mobile/packages/lock_screen/pubspec_overrides.yaml
Normal file
20
mobile/packages/lock_screen/pubspec_overrides.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
# melos_managed_dependency_overrides: ente_accounts,ente_base,ente_configuration,ente_events,ente_logging,ente_network,ente_strings,ente_ui,ente_utils
|
||||
dependency_overrides:
|
||||
ente_accounts:
|
||||
path: ../accounts
|
||||
ente_base:
|
||||
path: ../base
|
||||
ente_configuration:
|
||||
path: ../configuration
|
||||
ente_events:
|
||||
path: ../events
|
||||
ente_logging:
|
||||
path: ../logging
|
||||
ente_network:
|
||||
path: ../network
|
||||
ente_strings:
|
||||
path: ../strings
|
||||
ente_ui:
|
||||
path: ../ui
|
||||
ente_utils:
|
||||
path: ../utils
|
||||
@@ -130,7 +130,7 @@ packages:
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
ente_base:
|
||||
dependency: transitive
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
path: "../base"
|
||||
relative: true
|
||||
@@ -160,7 +160,7 @@ packages:
|
||||
source: path
|
||||
version: "1.0.0"
|
||||
ente_logging:
|
||||
dependency: transitive
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
path: "../logging"
|
||||
relative: true
|
||||
|
||||
10
mobile/packages/network/pubspec_overrides.yaml
Normal file
10
mobile/packages/network/pubspec_overrides.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
# melos_managed_dependency_overrides: ente_base,ente_configuration,ente_events,ente_logging
|
||||
dependency_overrides:
|
||||
ente_base:
|
||||
path: ../base
|
||||
ente_configuration:
|
||||
path: ../configuration
|
||||
ente_events:
|
||||
path: ../events
|
||||
ente_logging:
|
||||
path: ../logging
|
||||
@@ -161,7 +161,7 @@ packages:
|
||||
source: git
|
||||
version: "1.0.0"
|
||||
ente_events:
|
||||
dependency: transitive
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
path: "../events"
|
||||
relative: true
|
||||
|
||||
14
mobile/packages/ui/pubspec_overrides.yaml
Normal file
14
mobile/packages/ui/pubspec_overrides.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
# melos_managed_dependency_overrides: ente_base,ente_configuration,ente_events,ente_logging,ente_strings,ente_utils
|
||||
dependency_overrides:
|
||||
ente_base:
|
||||
path: ../base
|
||||
ente_configuration:
|
||||
path: ../configuration
|
||||
ente_events:
|
||||
path: ../events
|
||||
ente_logging:
|
||||
path: ../logging
|
||||
ente_strings:
|
||||
path: ../strings
|
||||
ente_utils:
|
||||
path: ../utils
|
||||
@@ -138,7 +138,7 @@ packages:
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
ente_base:
|
||||
dependency: transitive
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
path: "../base"
|
||||
relative: true
|
||||
@@ -161,7 +161,7 @@ packages:
|
||||
source: git
|
||||
version: "1.0.0"
|
||||
ente_events:
|
||||
dependency: transitive
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
path: "../events"
|
||||
relative: true
|
||||
|
||||
14
mobile/packages/utils/pubspec_overrides.yaml
Normal file
14
mobile/packages/utils/pubspec_overrides.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
# melos_managed_dependency_overrides: ente_base,ente_configuration,ente_events,ente_logging,ente_strings,ente_ui
|
||||
dependency_overrides:
|
||||
ente_base:
|
||||
path: ../base
|
||||
ente_configuration:
|
||||
path: ../configuration
|
||||
ente_events:
|
||||
path: ../events
|
||||
ente_logging:
|
||||
path: ../logging
|
||||
ente_strings:
|
||||
path: ../strings
|
||||
ente_ui:
|
||||
path: ../ui
|
||||
285
mobile/pubspec.lock
Normal file
285
mobile/pubspec.lock
Normal file
@@ -0,0 +1,285 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
ansi_styles:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ansi_styles
|
||||
sha256: "9c656cc12b3c27b17dd982b2cc5c0cfdfbdabd7bc8f3ae5e8542d9867b47ce8a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.2+1"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.0"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: charcode
|
||||
sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: checked_yaml
|
||||
sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.4"
|
||||
cli_launcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cli_launcher
|
||||
sha256: "17d2744fb9a254c49ec8eda582536abe714ea0131533e24389843a4256f82eac"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.2+1"
|
||||
cli_util:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cli_util
|
||||
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.2"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
conventional_commit:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: conventional_commit
|
||||
sha256: c40b1b449ce2a63fa2ce852f35e3890b1e182f5951819934c0e4a66254bc0dc3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.1+1"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: graphs
|
||||
sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.0"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: io
|
||||
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.9.0"
|
||||
melos:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: melos
|
||||
sha256: "4280dc46bd5b741887cce1e67e5c1a6aaf3c22310035cf5bd33dceeeda62ed22"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.3"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
mustache_template:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mustache_template
|
||||
sha256: a46e26f91445bfb0b60519be280555b06792460b27b19e2b19ad5b9740df5d1c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.6"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pool
|
||||
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
process:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: process
|
||||
sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.5"
|
||||
prompts:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: prompts
|
||||
sha256: "3773b845e85a849f01e793c4fc18a45d52d7783b4cb6c0569fad19f9d0a774a1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
pub_updater:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_updater
|
||||
sha256: "739a0161d73a6974c0675b864fb0cf5147305f7b077b7f03a58fa7a9ab3e7e7d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
pubspec_parse:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pubspec_parse
|
||||
sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.0"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.1"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
yaml_edit:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml_edit
|
||||
sha256: fb38626579fb345ad00e674e2af3a5c9b0cc4b9bfb8fd7f7ff322c7c9e62aef5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
sdks:
|
||||
dart: ">=3.8.0 <4.0.0"
|
||||
10
mobile/pubspec.yaml
Normal file
10
mobile/pubspec.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
# This pubspec.yaml is for the root of your monorepo.
|
||||
# Its purpose is to lock the version of the melos tool.
|
||||
name: ente_workspace
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
|
||||
dev_dependencies:
|
||||
melos: ^6.0.0
|
||||
@@ -255,6 +255,8 @@ smtp:
|
||||
# Optional override for the sender name in the emails. If specified, it will
|
||||
# be used for all emails sent by the instance (default is email specific).
|
||||
sender-name:
|
||||
# Optional encryption method. If tls or ssl is chosen, encryption is enabled.
|
||||
encryption:
|
||||
|
||||
# Zoho Zeptomail config (optional)
|
||||
#
|
||||
|
||||
@@ -7,6 +7,7 @@ package email
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
@@ -47,6 +48,7 @@ func sendViaSMTP(toEmails []string, fromName string, fromEmail string, subject s
|
||||
smtpPassword := viper.GetString("smtp.password")
|
||||
smtpEmail := viper.GetString("smtp.email")
|
||||
smtpSenderName := viper.GetString("smtp.sender-name")
|
||||
smtpEncryption := viper.GetString("smtp.encryption")
|
||||
|
||||
var emailMessage string
|
||||
var auth smtp.Auth = nil
|
||||
@@ -104,7 +106,7 @@ func sendViaSMTP(toEmails []string, fromName string, fromEmail string, subject s
|
||||
|
||||
// Send the email to each recipient
|
||||
for _, toEmail := range toEmails {
|
||||
err := smtp.SendMail(smtpServer+":"+smtpPort, auth, fromEmail, []string{toEmail}, []byte(emailMessage))
|
||||
err := sendMailWithEncryption(smtpServer, smtpPort, auth, fromEmail, []string{toEmail}, []byte(emailMessage), smtpEncryption)
|
||||
if err != nil {
|
||||
errMsg := err.Error()
|
||||
for i := range knownInvalidEmailErrors {
|
||||
@@ -119,6 +121,76 @@ func sendViaSMTP(toEmails []string, fromName string, fromEmail string, subject s
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendMailWithEncryption sends an email with the specified encryption type
|
||||
// encryption can be one of:
|
||||
// - "tls" or "ssl": Uses TLS/SSL encryption for the entire connection
|
||||
// - "" (empty string) or any other value: No encryption
|
||||
func sendMailWithEncryption(host, port string, auth smtp.Auth, from string, to []string, msg []byte, encryption string) error {
|
||||
addr := host + ":" + port
|
||||
|
||||
switch strings.ToLower(encryption) {
|
||||
case "tls", "ssl":
|
||||
// For TLS/SSL, establish a secure connection directly
|
||||
tlsConfig := &tls.Config{
|
||||
ServerName: host,
|
||||
}
|
||||
conn, err := tls.Dial("tcp", addr, tlsConfig)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "failed to establish TLS connection")
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client, err := smtp.NewClient(conn, host)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "failed to create SMTP client over TLS")
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
return sendWithClient(client, auth, from, to, msg)
|
||||
|
||||
default:
|
||||
// No encryption, use standard SendMail
|
||||
return smtp.SendMail(addr, auth, from, to, msg)
|
||||
}
|
||||
}
|
||||
|
||||
// sendWithClient sends an email using an established SMTP client
|
||||
func sendWithClient(client *smtp.Client, auth smtp.Auth, from string, to []string, msg []byte) error {
|
||||
if auth != nil {
|
||||
if err := client.Auth(auth); err != nil {
|
||||
return stacktrace.Propagate(err, "authentication failed")
|
||||
}
|
||||
}
|
||||
|
||||
if err := client.Mail(from); err != nil {
|
||||
return stacktrace.Propagate(err, "failed to set sender")
|
||||
}
|
||||
|
||||
for _, addr := range to {
|
||||
if err := client.Rcpt(addr); err != nil {
|
||||
return stacktrace.Propagate(err, "failed to add recipient")
|
||||
}
|
||||
}
|
||||
|
||||
w, err := client.Data()
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "failed to create message writer")
|
||||
}
|
||||
|
||||
_, err = w.Write(msg)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "failed to write message")
|
||||
}
|
||||
|
||||
err = w.Close()
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "failed to close message writer")
|
||||
}
|
||||
|
||||
err = client.Quit()
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
|
||||
func sendViaTransmail(toEmails []string, fromName string, fromEmail string, subject string, htmlBody string, inlineImages []map[string]interface{}) error {
|
||||
if len(toEmails) == 0 {
|
||||
return ente.ErrBadRequest
|
||||
|
||||
Reference in New Issue
Block a user