Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77632b3241 | ||
|
|
d02e02a326 | ||
|
|
7fe314a16a | ||
|
|
504fa939d2 | ||
|
|
17f5a7996a |
@@ -398,5 +398,11 @@
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters."
|
||||
},
|
||||
"noInternetConnection": "No internet connection",
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain": "Please check your internet connection and try again."
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain": "Please check your internet connection and try again.",
|
||||
"signOutFromOtherDevices": "Sign out from other devices",
|
||||
"signOutOtherBody": "If you think someone might know your password, you can force all other devices using your account to sign out.",
|
||||
"signOutOtherDevices": "Sign out other devices",
|
||||
"doNotSignOut": "Do not sign out",
|
||||
"hearUsWhereTitle": "How did you hear about Ente? (optional)",
|
||||
"hearUsExplanation": "We don't track app installs. It'd help if you told us where you found us!"
|
||||
}
|
||||
|
||||
@@ -84,10 +84,12 @@
|
||||
"importFromApp": "从 {appName} 导入代码",
|
||||
"importGoogleAuthGuide": "使用“转移帐户”选项将您的帐户从 Google 身份验证器导出到二维码。然后使用另一台设备扫描二维码。\n\n提示:您可以使用笔记本电脑的网络摄像头拍摄二维码的照片。",
|
||||
"importSelectJsonFile": "选择 JSON 文件",
|
||||
"importSelectAppExport": "选择 {appName} 的导出文件",
|
||||
"importEnteEncGuide": "选择从ente导出的加密JSON文件",
|
||||
"importRaivoGuide": "使用 Raivo 设置中的“将 OTP 导出到 Zip 存档”选项。\n\n解压 zip 文件并导入 JSON 文件。",
|
||||
"importBitwardenGuide": "使用 Bitwarden 工具中的“导出保管库”选项并导入未加密的 JSON 文件。",
|
||||
"importAegisGuide": "在Aegis的设置中使用\"导出密码库\"选项。\n\n如果您的密码库已加密,您需要输入密码才能解密密码库。",
|
||||
"import2FasGuide": "使用 2FAS 中的“设置 -> 备份 - 导出”选项。\n\n如果您的备份已被加密,则需要输入密码才能解密备份",
|
||||
"exportCodes": "导出代码",
|
||||
"importLabel": "导入",
|
||||
"importInstruction": "请以以下格式选择包含代码列表的文件",
|
||||
@@ -395,5 +397,9 @@
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters."
|
||||
},
|
||||
"noInternetConnection": "无互联网连接",
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain": "请检查您的互联网连接,然后重试。"
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain": "请检查您的互联网连接,然后重试。",
|
||||
"signOutFromOtherDevices": "从其他设备退出登录",
|
||||
"signOutOtherBody": "如果你认为有人可能知道你的密码,你可以强制所有使用你账户的其他设备退出登录。",
|
||||
"signOutOtherDevices": "登出其他设备",
|
||||
"doNotSignOut": "不要退登"
|
||||
}
|
||||
@@ -45,6 +45,7 @@ import "package:uuid/uuid.dart";
|
||||
class UserService {
|
||||
static const keyHasEnabledTwoFactor = "has_enabled_two_factor";
|
||||
static const keyUserDetails = "user_details";
|
||||
static const kReferralSource = "referral_source";
|
||||
static const kCanDisableEmailMFA = "can_disable_email_mfa";
|
||||
static const kIsEmailMFAEnabled = "is_email_mfa_enabled";
|
||||
final SRP6GroupParameters kDefaultSrpGroup = SRP6StandardGroups.rfc5054_4096;
|
||||
@@ -270,13 +271,17 @@ class UserService {
|
||||
}) async {
|
||||
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
|
||||
await dialog.show();
|
||||
final verifyData = {
|
||||
"email": _config.getEmail(),
|
||||
"ott": ott,
|
||||
};
|
||||
if (!_config.isLoggedIn()) {
|
||||
verifyData["source"] = 'auth:' + _getRefSource();
|
||||
}
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/users/verify-email",
|
||||
data: {
|
||||
"email": _config.getEmail(),
|
||||
"ott": ott,
|
||||
},
|
||||
data: verifyData,
|
||||
);
|
||||
await dialog.hide();
|
||||
if (response.statusCode == 200) {
|
||||
@@ -441,6 +446,7 @@ class UserService {
|
||||
Future<void> registerOrUpdateSrp(
|
||||
Uint8List loginKey, {
|
||||
SetKeysRequest? setKeysRequest,
|
||||
bool logOutOtherDevices = false,
|
||||
}) async {
|
||||
try {
|
||||
final String username = const Uuid().v4().toString();
|
||||
@@ -496,6 +502,7 @@ class UserService {
|
||||
'setupID': setupSRPResponse.setupID,
|
||||
'srpM1': base64Encode(SRP6Util.encodeBigInt(clientM!)),
|
||||
'updatedKeyAttr': setKeysRequest.toMap(),
|
||||
'logOutOtherDevices': logOutOtherDevices,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -608,8 +615,9 @@ class UserService {
|
||||
|
||||
Future<void> updateKeyAttributes(
|
||||
KeyAttributes keyAttributes,
|
||||
Uint8List loginKey,
|
||||
) async {
|
||||
Uint8List loginKey, {
|
||||
required bool logoutOtherDevices,
|
||||
}) async {
|
||||
try {
|
||||
final setKeyRequest = SetKeysRequest(
|
||||
kekSalt: keyAttributes.kekSalt,
|
||||
@@ -618,11 +626,11 @@ class UserService {
|
||||
memLimit: keyAttributes.memLimit,
|
||||
opsLimit: keyAttributes.opsLimit,
|
||||
);
|
||||
await registerOrUpdateSrp(loginKey, setKeysRequest: setKeyRequest);
|
||||
// await _enteDio.put(
|
||||
// "/users/keys",
|
||||
// data: setKeyRequest.toMap(),
|
||||
// );
|
||||
await registerOrUpdateSrp(
|
||||
loginKey,
|
||||
setKeysRequest: setKeyRequest,
|
||||
logOutOtherDevices: logoutOtherDevices,
|
||||
);
|
||||
await _config.setKeyAttributes(keyAttributes);
|
||||
} catch (e) {
|
||||
_logger.severe(e);
|
||||
@@ -887,4 +895,12 @@ class UserService {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setRefSource(String refSource) async {
|
||||
await _preferences.setString(kReferralSource, refSource);
|
||||
}
|
||||
|
||||
String _getRefSource() {
|
||||
return _preferences.getString(kReferralSource) ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@ import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:ente_auth/ui/common/dynamic_fab.dart';
|
||||
import 'package:ente_auth/ui/common/web_page.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:password_strength/password_strength.dart';
|
||||
@@ -30,6 +32,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
String? _email;
|
||||
String? _password;
|
||||
String _cnfPassword = '';
|
||||
String _referralSource = '';
|
||||
double _passwordStrength = 0.0;
|
||||
bool _emailIsValid = false;
|
||||
bool _hasAgreedToTOS = true;
|
||||
@@ -104,6 +107,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
onPressedFunction: () {
|
||||
_config.setVolatilePassword(_passwordController1.text);
|
||||
UserService.instance.setEmail(_email!);
|
||||
UserService.instance.setRefSource(_referralSource);
|
||||
UserService.instance
|
||||
.sendOtt(context, _email!, isCreateAccountScreen: true);
|
||||
FocusScope.of(context).unfocus();
|
||||
@@ -325,6 +329,51 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 0, horizontal: 20),
|
||||
child: Text(
|
||||
context.l10n.hearUsWhereTitle,
|
||||
style: getEnteTextTheme(context).smallFaint,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
|
||||
child: TextFormField(
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
decoration: InputDecoration(
|
||||
fillColor: null,
|
||||
filled: true,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 14,
|
||||
),
|
||||
border: UnderlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
suffixIcon: InkWell(
|
||||
onTap: () {
|
||||
showToast(
|
||||
context,
|
||||
context.l10n.hearUsExplanation,
|
||||
);
|
||||
},
|
||||
child: Icon(
|
||||
Icons.info_outline_rounded,
|
||||
color: getEnteColorScheme(context).strokeMuted,
|
||||
),
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
_referralSource = value.trim();
|
||||
},
|
||||
autocorrect: false,
|
||||
keyboardType: TextInputType.text,
|
||||
textInputAction: TextInputAction.next,
|
||||
),
|
||||
),
|
||||
const Divider(thickness: 1),
|
||||
const SizedBox(height: 12),
|
||||
_getAgreement(),
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/ui/account/recovery_key_page.dart';
|
||||
import 'package:ente_auth/ui/common/dynamic_fab.dart';
|
||||
import 'package:ente_auth/ui/common/web_page.dart';
|
||||
import 'package:ente_auth/ui/components/models/button_type.dart';
|
||||
import 'package:ente_auth/ui/home_page.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/navigation_util.dart';
|
||||
@@ -24,8 +25,7 @@ enum PasswordEntryMode {
|
||||
class PasswordEntryPage extends StatefulWidget {
|
||||
final PasswordEntryMode mode;
|
||||
|
||||
const PasswordEntryPage({required this.mode, Key? key})
|
||||
: super(key: key);
|
||||
const PasswordEntryPage({required this.mode, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<PasswordEntryPage> createState() => _PasswordEntryPageState();
|
||||
@@ -180,10 +180,11 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
.copyWith(fontSize: 14),
|
||||
tags: {
|
||||
'underline': StyledTextTag(
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
@@ -356,10 +357,11 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: context.l10n.howItWorks,
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -374,13 +376,18 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
}
|
||||
|
||||
void _updatePassword() async {
|
||||
final logOutFromOthers = await logOutFromOtherDevices(context);
|
||||
final dialog =
|
||||
createProgressDialog(context, context.l10n.generatingEncryptionKeys);
|
||||
await dialog.show();
|
||||
try {
|
||||
final result = await Configuration.instance
|
||||
.getAttributesForNewPassword(_passwordController1.text);
|
||||
await UserService.instance.updateKeyAttributes(result.item1, result.item2);
|
||||
await UserService.instance.updateKeyAttributes(
|
||||
result.item1,
|
||||
result.item2,
|
||||
logoutOtherDevices: logOutFromOthers,
|
||||
);
|
||||
await dialog.hide();
|
||||
showShortToast(context, context.l10n.passwordChangedSuccessfully);
|
||||
Navigator.of(context).pop();
|
||||
@@ -394,13 +401,34 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> logOutFromOtherDevices(BuildContext context) async {
|
||||
bool logOutFromOther = true;
|
||||
await showChoiceDialog(
|
||||
context,
|
||||
title: context.l10n.signOutFromOtherDevices,
|
||||
body: context.l10n.signOutOtherBody,
|
||||
isDismissible: false,
|
||||
firstButtonLabel: context.l10n.signOutOtherDevices,
|
||||
firstButtonType: ButtonType.critical,
|
||||
firstButtonOnTap: () async {
|
||||
logOutFromOther = true;
|
||||
},
|
||||
secondButtonLabel: context.l10n.doNotSignOut,
|
||||
secondButtonOnTap: () async {
|
||||
logOutFromOther = false;
|
||||
},
|
||||
);
|
||||
return logOutFromOther;
|
||||
}
|
||||
|
||||
Future<void> _showRecoveryCodeDialog(String password) async {
|
||||
final l10n = context.l10n;
|
||||
final dialog =
|
||||
createProgressDialog(context, l10n.generatingEncryptionKeysTitle);
|
||||
await dialog.show();
|
||||
try {
|
||||
final KeyGenResult result = await Configuration.instance.generateKey(password);
|
||||
final KeyGenResult result =
|
||||
await Configuration.instance.generateKey(password);
|
||||
Configuration.instance.setVolatilePassword(null);
|
||||
await dialog.hide();
|
||||
onDone() async {
|
||||
|
||||
@@ -10,6 +10,9 @@ A user on GitHub has written a guide to export our data from Authy (morpheus on
|
||||
## Exporting from Authy
|
||||
To export your data, please follow [this guide](https://gist.github.com/gboudreau/94bb0c11a6209c82418d01a59d958c93). This will create a new JSON file with all your Authy TOTP data in it. **Do not share this file with anyone!**
|
||||
|
||||
Or, you can [use this tool by Neeraj](https://github.com/ua741/authy-export/releases/tag/v0.0.4) to simplify things and skip to importing to ente Authenticator.
|
||||
### *Do note that these tools may not export ALL of your codes. Make sure that all your accounts have been imported successfully before deleting any codes from your Authy account!*
|
||||
|
||||
## Converting the export for ente Authenticator
|
||||
So now that you have the JSON file, does that mean it can be imported into ente Authenticator? Nope.
|
||||
|
||||
@@ -17,7 +20,7 @@ This is because the code in the guide exports your Authy data for Bitwarden, not
|
||||
|
||||
<img width="454" alt="ente Authenticator Screenshot" src="https://github.com/gweeeen/auth/assets/41323182/30566a69-cfa0-4de0-9f0d-95967d4c5cad">
|
||||
|
||||
So, this means that even if you try to import this file, nothing will happen. But don't worry, I've written a program in Python that converts the JSON file into a TXT file that ente Authenticator can use!
|
||||
So, this means that even if you try to import this file, nothing will happen. But don't worry, I've written a program in Python that converts the JSON file into a TXT file that ente Authenticator can use! (It's definitely not written **professionaly**, but hey it gets the job done so I'm happy with that.)
|
||||
|
||||
You can download my program [here](https://github.com/gweeeen/ducky/blob/main/duckys_other_stuff/authy_to_ente.py). Or if you **really like making life hard**, then you can make a new Python file and copy this code to it:
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: ente_auth
|
||||
description: ente two-factor authenticator
|
||||
version: 2.0.25+225
|
||||
version: 2.0.26+226
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
|
||||
Reference in New Issue
Block a user