Compare commits

..

40 Commits

Author SHA1 Message Date
Vishnu Mohandas
1b7b6e457b v2.0.30 (#409) 2024-01-04 10:18:57 +05:30
Zxhir
61c53e9d10 Add the TCPShield logo (#408)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Description

<!--- Describe your changes in detail -->

This PR adds the TCPShield. TCPShield is a highly available DDoS
protection platform, mainly used in Minecraft servers. I have also
re-aranged NextDNS and Skiff in the custom-icons.json to make it in
alphabetical order.

<!--- Put an `x` in all the boxes that apply: -->

- [x] 🖼️ New icon
- [ ]  New feature (non-breaking change which adds functionality)
- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore
2024-01-04 09:20:44 +05:30
Antoni Siek
13fe460069 fix: bitwarden imports (#406)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Description

<!--- Describe your changes in detail -->

There are two issues with Bitwarden imports ATM:

1. TOTP secrets in Bitwarden are sometimes saved in a format of
`otpauth://[...]` and sometimes in a format of secret only. Ente auth
assumes that only the second one is used, which is not true and causes
an exception. To make both methods work as excepted, in the code, I'm
checking which format is used and create `Code` object accordingly.
2. Sometimes items in Bitwarden exports are not accounts, but rather
notes. In that case, the `item` variable in the
`_processBitwardenExportFile` method does not contain the `login`
attribute, which causes an exception. I've added a check to make sure
that the item we're parsing is indeed an account.

It is my first time making something in Dart, so please don't be too
harsh about my code :)

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ] 🖼️ New icon
- [ ]  New feature (non-breaking change which adds functionality)
- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore
2024-01-03 12:53:26 +05:30
Aadarsh Patel
6b4e4b6822 Fix: dark icon hard to see (#407)
## Type of Change

- [x]  New feature (non-breaking change which adds functionality)

## Description

Fall-back to theme's icon-color when the icon-color is too light or
dark. Fixes #403

Logic
1. If RGB values are almost equal (`#000000`, `#0F0F11`, `#212121`,
`#27272A`, `#464949`, `#FFFFFF`)
2. Compute its luminance/brightness (cache this value as it is an
expensive task)
3. If its too bright or dark, return theme's icon-color

I've manually set the threshold values for brightness in light-theme to
be `0.7` and in dark-theme to be `0.05`



https://github.com/ente-io/auth/assets/53324291/aa1e8413-631d-4039-8c08-f8c4d1856fdb

Co-authored-by: aadarsh-patel <aadarsh@zuzu.in>
2024-01-03 12:48:23 +05:30
Nikunj Kumar Nakum
8b9592c06e Update custom-icons.json (#405)
## Description
added following custom icons
1 epic games
2 brave creators
3 gitlab
4 rust users forum
5 uphold

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [x] 🖼️ New icon
2023-12-28 10:53:43 +05:30
Neeraj Gupta
4a0301fe46 Build decrypt binary for all major arch (#401)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Description

<!--- Describe your changes in detail -->

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ] 🖼️ New icon
- [ ]  New feature (non-breaking change which adds functionality)
- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore
2023-12-19 09:41:29 +05:30
Neeraj Gupta
14820ad7a0 Make screen scrollable (#400)
## Description
Fixes https://github.com/ente-io/auth/issues/398

<!--- Describe your changes in detail -->

## Type of Change


- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)
2023-12-19 09:05:02 +05:30
Vishnu Mohandas
974c34a569 Ignore spaces within provider names (#395)
Fixes #393.

Tested with an entry within Simple Icons ("Deutsche Bahn") and an entry
within our Custom Icons ("Anycoin Direct").
2023-12-18 21:37:39 +05:30
github-actions[bot]
4dc31c3e60 New Translations (#397)
New translations via [Crowdin GH
Action](https://github.com/crowdin/github-action)

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-12-18 21:35:53 +05:30
github-actions[bot]
fa60a22443 New Translations (#396)
New translations via [Crowdin GH
Action](https://github.com/crowdin/github-action)

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-12-18 10:34:15 +05:30
Lukas Bestle
cf55c48b4c Add several custom icons (#394)
## Description

Added custom icons that don't meet the requirements of Simple Icons
(especially because they are full-color).

The Airtable and Discourse icons are an upgrade to the single-color
icons in Simple Icons, the others are not available yet at all.

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [x] 🖼️ New icon
- [ ]  New feature (non-breaking change which adds functionality)
- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore
2023-12-17 17:17:32 +05:30
github-actions[bot]
56290b1df6 New Translations (#391)
New translations via [Crowdin GH
Action](https://github.com/crowdin/github-action)

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-12-15 14:09:16 +05:30
github-actions[bot]
8d468dbc91 New Translations (#389)
New translations via [Crowdin GH
Action](https://github.com/crowdin/github-action)

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-12-07 08:03:24 +05:30
Muhammed Ayimen Abdul Latheef
1a654fe748 Added: When the app is in the background, hided te contents of the app (#387)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Description

Added: Hide the contents of the app, when is it in the background - Both
android and ios

In android, it shows a blank white/black background, when in background

In iOS, it shows the launch icon, when in background


![photo_2023-12-06_12-53-02](https://github.com/ente-io/auth/assets/54765537/4f787e6a-984c-4eae-9028-303a32009838)

Android Physical Device


![photo_2023-12-06_12-52-55](https://github.com/ente-io/auth/assets/54765537/b8802cf5-0db0-473e-8be8-8cf3bf7b29a5)

Android stimulator

Also, compared the bitwarden apps in android, the flow is same

iOS Device: Need to verify 
@ua741 @vishnukvmd Could you run on iOS devices and verify it?

Closes #47 

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ] 🖼️ New icon
- [x]  New feature (non-breaking change which adds functionality)
- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore
2023-12-06 13:50:36 +05:30
github-actions[bot]
1606b74b40 New Translations (#388) 2023-12-06 09:47:32 +05:30
Vishnu Mohandas
8d82851741 Update CONTRIBUTION.md (#385) 2023-12-04 10:48:51 +05:30
github-actions[bot]
a6ca5697d9 New Translations (#381)
New translations via [Crowdin GH
Action](https://github.com/crowdin/github-action)

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-12-04 08:12:11 +05:30
github-actions[bot]
e01ba961a6 New Translations (#378)
New translations via [Crowdin GH
Action](https://github.com/crowdin/github-action)

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-11-27 22:43:46 +05:30
Neeraj Gupta
a256bc9922 EmailVerification: Improve copy for warning (#377) 2023-11-27 22:02:18 +05:30
Neeraj Gupta
77632b3241 Bump version 2.0.26+226 2023-11-27 18:39:22 +05:30
Neeraj Gupta
d02e02a326 Sign up: Add field for 'hear us from' info (#376) 2023-11-27 18:06:49 +05:30
github-actions[bot]
7fe314a16a New Translations (#375)
New translations via [Crowdin GH
Action](https://github.com/crowdin/github-action)

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-11-27 18:04:23 +05:30
green
504fa939d2 Add CLI tool + disclaimer to Authy guide (#373)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Description
Adds a hyperlink to Neeraj's CLI tool shown in ente Discord, as well as
a disclaimer to check all accounts have been imported before deleting
Authy data
<!--- Describe your changes in detail -->

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ] 🖼️ New icon
- [ ]  New feature (non-breaking change which adds functionality)
- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [x] 📝 Documentation
- [ ] 🗑️ Chore
2023-11-27 08:33:49 +05:30
Neeraj Gupta
17f5a7996a Change Password: Confirm before signing out from other devices (#374) 2023-11-26 16:27:25 +05:30
Neeraj Gupta
cf6b4f5423 Enable import for 2fas v4 (#372) 2023-11-26 14:19:33 +05:30
github-actions[bot]
ca46765760 New Translations (#371)
New translations via [Crowdin GH
Action](https://github.com/crowdin/github-action)

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-11-26 13:40:47 +05:30
Neeraj Gupta
4de3287f55 Support importing 2FAS export (v3) (#370)
Tested locally.

Planning to extract the strings separately while merging common strings
from other import sections.
2023-11-26 13:38:47 +05:30
Neeraj Gupta
cdc27e061b Fix: Add missing await (#368) 2023-11-24 22:07:04 +05:30
Tanguy
78b3d239eb Add Proxmox logo (#366)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

New logo + refractor

<!--- Describe your changes in detail -->

Add Proxmox logo. Remove test icon. Refractor X icon data with altNames.

SVGs with mix-blend-mode appear to be glitchy in emulators but not on
real devices
(https://github.com/dnfield/flutter_svg/issues/922#issuecomment-1552104687).
By the way, SVGs using mix-blend-mode get pixelated. This is probably
because of `flutter_svg` library
(https://github.com/dnfield/flutter_svg/issues/985). I didn't go into
any depth, I didn't feel like digging into the code.

<!--- Put an `x` in all the boxes that apply: -->

- [x] 🖼️ New icon
- [ ]  New feature (non-breaking change which adds functionality)
- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [x] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore
2023-11-24 10:42:28 +05:30
github-actions[bot]
005d3b0eca New Translations (#364)
New translations via [Crowdin GH
Action](https://github.com/crowdin/github-action)

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-11-23 07:20:03 +05:30
github-actions[bot]
23ba69c6a6 New Translations (#363)
New translations via [Crowdin GH
Action](https://github.com/crowdin/github-action)

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-11-21 10:35:26 +05:30
Neeraj Gupta
d644ee97e1 Bump version 2.0.22+222 2023-11-20 18:15:10 +05:30
Neeraj Gupta
0afd0d63b3 Show hint that double tapping on hidden code makes it visible (#362)
Related to #331
2023-11-20 18:14:13 +05:30
Neeraj Gupta
f04d9b94d9 Add device auth for sign in to backup action (#361)
Related to #337
2023-11-20 18:08:41 +05:30
github-actions[bot]
8cd4ec30af New Translations (#360)
New translations via [Crowdin GH
Action](https://github.com/crowdin/github-action)

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-11-20 17:35:33 +05:30
Neeraj Gupta
4215810cf8 Add fix for loginViaSRP (#359)
Port fixes from photos-app
2023-11-20 17:21:38 +05:30
Neeraj Gupta
1f0c2d2aa6 Fix lockscreen issue on iPAD (#358)
Tested internally on Simulator and via Test flight.
2023-11-20 15:53:46 +05:30
Zxhir
dc7782fb0c Add the Pingvin Share logo (#356)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Description

This PR adds the Pingvin Share icon. Pingvin Share is a self-hosted file
sharing platform, their GitHub is
[here](https://github.com/stonith404/pingvin-share) if you want to check
it out.

<!--- Describe your changes in detail -->

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [x] 🖼️ New icon
- [ ]  New feature (non-breaking change which adds functionality)
- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore
2023-11-18 19:51:26 +05:30
github-actions[bot]
c7ba0c5f33 New Translations (#355) 2023-11-18 19:04:56 +05:30
Neeraj Gupta
5d372607a4 Revert "Analyze qr code image (#336)" (#354)
This reverts commit 233858ad09, reversing
changes made to f97bf015f1.

Reason: media_scanner depends on MLKit that underhood also makes
connection to firebase.
2023-11-17 09:09:22 +05:30
74 changed files with 2446 additions and 1071 deletions

12
.vscode/settings.json vendored
View File

@@ -1,7 +1,7 @@
{
"workbench.colorCustomizations": {
"activityBar.background": "#44116A",
"titleBar.activeBackground": "#5F1895",
"titleBar.activeForeground": "#FDFBFE"
}
}
"workbench.colorCustomizations": {
"activityBar.background": "#44116A",
"titleBar.activeBackground": "#5F1895",
"titleBar.activeForeground": "#FDFBFE"
}
}

View File

@@ -1,36 +1,49 @@
# Contributing
Thank you for showing interest in contributing to ente Authenticator. There are a couple of ways to help
out. This document contains some general guidelines for each type of
contribution.
Thank you for showing interest in contributing to ente Authenticator. There are
a couple of ways to help out. This document contains some general guidelines for
each type of contribution.
## Translations
[![Crowdin](https://badges.crowdin.net/ente-authenticator-app/localized.svg)](https://crowdin.com/project/ente-authenticator-app)
We use [Crowdin](https://crowdin.com/project/ente-authenticator-app) to crowdsource
translations of ente Authenticator.
If your language is not listed for translation, feel free to [create a GitHub issue](https://github.com/ente-io/auth/issues/new?title=Request+for+New+Language+Translation&body=Language+name%3A) to have it added.
We use [Crowdin](https://crowdin.com/project/ente-authenticator-app) to
crowdsource translations of ente Authenticator. If your language is not listed
for translation, feel free to [create a GitHub
issue](https://github.com/ente-io/auth/issues/new?title=Request+for+New+Language+Translation&body=Language+name%3A)
to have it added.
## Icons
ente Auth supports the icon pack provided by
[simple-icons](https://github.com/simple-icons/simple-icons).
If you would like to add your own custom icon, please open a pull-request
with the relevant SVG and color
code ([example PR](https://github.com/ente-io/auth/pull/213/files)).
If you would like to add your own custom icon, please open a pull-request with
the relevant SVG placed within `assets/custom-icons/icons` and add the
corresponding entry within `assets/custom-icons/_data/custom-icons.json`.
This JSON file contains the following attributes:
| Attribute | Usecase | Required |
|---|---|---|
| `title` | Name of the service. | Yes |
| `slug` | If the icon's SVG file has a name different from the `title` | No |
| `hex` | Color code for the icon | No |
| `altNames` | If the same service goes by different names or has different instances (eg. Mastodon) | No |
Here is an [example PR](https://github.com/ente-io/auth/pull/213/files).
## Development
If you're planning on adding a new feature or making other changes, please
discuss it with us by creating [an
issue](https://github.com/ente-io/auth/issues/new)
on GitHub. Discussing your idea with us first ensures that everyone is on the
same page before you start working on your change.
issue](https://github.com/ente-io/auth/issues/new) on GitHub. Discussing your
idea with us first ensures that everyone is on the same page before you start
working on your change.
### 💻 Setup
### Setup
1. [Install Flutter v3.10.6](https://flutter.dev/docs/get-started/install)
2. Clone this repository with `git clone git@github.com:ente-io/auth.git`
@@ -43,9 +56,12 @@ same page before you start working on your change.
#### Localization
If the feature requires adding new strings, you can do that by following these steps:
If the feature requires adding new strings, you can do that by following these
steps:
1. Add a new entry inside [app_en.arb](https://github.com/ente-io/auth/blob/main/lib/l10n/arb/app_en.arb) (Remember to save)
1. Add a new entry inside
[app_en.arb](https://github.com/ente-io/auth/blob/main/lib/l10n/arb/app_en.arb)
(Remember to save)
2. In your dart file, add follwing import
```dart
import "package:ente_auth/l10n/l10n.dart";

View File

@@ -53,14 +53,12 @@
</queries>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission
android:name="android.permission.READ_MEDIA_IMAGES" />
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32"/>
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29"
tools:ignore="ScopedStorage" />
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29"
tools:ignore="ScopedStorage"/>
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
</manifest>

View File

@@ -7,6 +7,9 @@
"title": "Addy.io",
"slug": "addy_io"
},
{
"title": "Airtable"
},
{
"title": "Anycoin Direct",
"slug": "anycoindirect"
@@ -27,6 +30,10 @@
{
"title": "Bitwarden"
},
{
"title": "Brave Creators",
"slug": "brave_creators"
},
{
"title": "Bybit"
},
@@ -43,10 +50,22 @@
"slug": "controld",
"hex": "5FD800"
},
{
"title": "Discourse"
},
{
"title": "dus.net",
"slug": "dusnet"
},
{
"title": "ente",
"hex": "1DB954"
},
{
"title": "Epic Games",
"slug": "epic_games",
"hex": "000000"
},
{
"title": "Filen",
"hex": "858585"
@@ -59,12 +78,19 @@
"title": "GitHub",
"hex": "858585"
},
{
"title": "GitLab",
"slug": "gitlab"
},
{
"title": "Google"
},
{
"title": "ING"
},
{
"title": "INWX"
},
{
"title": "Instagram"
},
@@ -91,9 +117,9 @@
"title": "KuCoin",
"hex": "01BC8D"
},
{
{
"title": "La Poste",
"slug": "laposte"
"slug": "laposte"
},
{
"title": "Mastodon",
@@ -107,6 +133,9 @@
{
"title": "Mozilla"
},
{
"title": "NextDNS"
},
{
"title": "ngrok",
"hex": "858585"
@@ -133,6 +162,10 @@
"title": "Peerberry",
"hex": "03E5A5"
},
{
"title": "Pingvin Share",
"hex": "485099"
},
{
"title": "Plutus",
"hex": "DEC685"
@@ -159,13 +192,31 @@
{
"title": "Proton"
},
{
"title": "Proxmox"
},
{
"title": "Revolt",
"hex": "858585"
},
{
"title": "Rust Language Forum",
"slug": "rust_language_forum",
"hex": "000000"
},
{
"title": "service-bw"
},
{
"title": "SimpleLogin"
},
{
"title": "Sipgate"
},
{
"title": "Skiff",
"hex": "EF5A3C"
},
{
"title": "Snapchat"
},
@@ -174,6 +225,11 @@
"slug": "standardnotes",
"hex": "2173E6"
},
{
"title": "TCPShield",
"slug": "tcpshield",
"hex": "FFFFFF"
},
{
"title": "Techlore"
},
@@ -202,10 +258,6 @@
"title": "Twingate",
"hex": "858585"
},
{
"title": "Twitter",
"slug": "x"
},
{
"title": "Ubisoft",
"hex": "4285f4"
@@ -214,6 +266,11 @@
"title": "Unity",
"hex": "858585"
},
{
"title": "Uphold",
"slug": "uphold",
"hex": "6FE68A"
},
{
"title": "WHMCS"
},
@@ -225,17 +282,9 @@
"title": "Wise"
},
{
"title": "X"
},
{
"title": "NextDNS"
},
{
"title": "Skiff",
"hex": "EF5A3C"
},
{
"title": "zzz_dev_test_icon"
"title": "X",
"altNames": ["twitter"],
"slug": "x"
}
]
}

View File

@@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Airtable</title><path fill="#ffba05" d="M11.992 1.966c-.434 0-.87.086-1.28.257L1.779 5.917c-.503.208-.49.908.012 1.116l8.982 3.558a3.266 3.266 0 0 0 2.454 0l8.982-3.558c.503-.196.503-.908.012-1.116l-8.957-3.694a3.255 3.255 0 0 0-1.272-.257z"/><path fill="#39caff" d="M23.4 8.056a.589.589 0 0 0-.222.045l-10.012 3.877a.612.612 0 0 0-.38.564v8.896a.6.6 0 0 0 .821.552L23.62 18.1a.583.583 0 0 0 .38-.551V8.653a.6.6 0 0 0-.6-.596z"/><path fill="#dc043b" d="M.676 8.095a.644.644 0 0 0-.48.19C.086 8.396 0 8.53 0 8.69v8.355c0 .442.515.737.908.54l6.27-3.006.307-.147 2.969-1.436c.466-.22.43-.908-.061-1.092L.883 8.138a.57.57 0 0 0-.207-.044z"/><path fill-opacity="0.25" d="M10.451,12.997l-2.972,1.434l-7.287,-6.144c0.046,-0.046 0.098,-0.084 0.152,-0.114c0.15,-0.09 0.363,-0.114 0.545,-0.042l9.512,3.769c0.483,0.191 0.521,0.869 0.05,1.097"/></svg>

After

Width:  |  Height:  |  Size: 918 B

View File

@@ -0,0 +1,114 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
width="96"
height="96"
viewBox="0 0 96 96"
xml:space="preserve"
id="svg6"
sodipodi:docname="brave.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="8.09375"
inkscape:cx="48"
inkscape:cy="48"
inkscape:window-width="1920"
inkscape:window-height="991"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg6" />
<desc
id="desc1">Created with Fabric.js 5.3.0</desc>
<defs
id="defs1">
</defs>
<g
transform="translate(48.065,48.066)"
id="r-F4iK9D9hoWKR6dRPdW9">
<g
id="g6">
<g
transform="translate(-0.065,-0.066)"
id="Ii0aKVyTWvdbjVfK_ZaN0">
<linearGradient
id="SVGID_0"
gradientUnits="userSpaceOnUse"
x1="0.671"
y1="64.319"
x2="55.200001"
y2="64.319">
<stop
offset="0%"
style="stop-color:rgb(255,85,0);stop-opacity: 1"
id="stop1" />
<stop
offset="41%"
style="stop-color:rgb(255,85,0);stop-opacity: 1"
id="stop2" />
<stop
offset="58.199999999999996%"
style="stop-color:rgb(255,32,0);stop-opacity: 1"
id="stop3" />
<stop
offset="100%"
style="stop-color:rgb(255,32,0);stop-opacity: 1"
id="stop4" />
</linearGradient>
<path
style="opacity:1;fill:url(#SVGID_0);fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0"
transform="translate(-27.935,-31.934)"
d="m 53.292,15.321 1.5,-3.676 c 0,0 -1.909,-2.043 -4.227,-4.358 C 48.248,4.972 43.34,6.334 43.34,6.334 L 37.751,0 H 18.12 l -5.589,6.334 c 0,0 -4.908,-1.362 -7.225,0.953 C 2.988,9.602 1.08,11.645 1.08,11.645 l 1.5,3.676 -1.91,5.447 c 0,0 5.614,21.236 6.272,23.83 1.295,5.106 2.181,7.08 5.862,9.668 3.68,2.587 10.36,7.08 11.45,7.762 1.091,0.68 2.455,1.84 3.682,1.84 1.227,0 2.59,-1.16 3.68,-1.84 1.091,-0.681 7.77,-5.175 11.452,-7.762 3.68,-2.587 4.567,-4.562 5.862,-9.668 0.657,-2.594 6.27,-23.83 6.27,-23.83 z"
stroke-linecap="round"
id="path4" />
</g>
<g
transform="translate(-0.064,-1.7095)"
id="yFALuw9WCf5yfG8AbQIoW">
<path
style="opacity:1;fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0"
transform="translate(-27.936,-30.2905)"
d="m 34.888,11.508 c 0.818,0 6.885,-1.157 6.885,-1.157 0,0 7.189,8.68 7.189,10.536 0,1.534 -0.619,2.134 -1.347,2.842 -0.152,0.148 -0.31,0.3 -0.467,0.468 l -5.39,5.717 c -0.05786,0.06078 -0.116534,0.120787 -0.176,0.18 -0.538,0.54 -1.33,1.336 -0.772,2.658 l 0.115,0.269 c 0.613,1.432 1.37,3.2 0.407,4.99 -1.025,1.906 -2.78,3.178 -3.905,2.967 -1.124,-0.21 -3.766,-1.589 -4.737,-2.218 -0.971,-0.63 -4.05,-3.166 -4.05,-4.137 0,-0.809 2.214,-2.155 3.29,-2.81 0.214,-0.13 0.383,-0.232 0.48,-0.298 0.111,-0.075 0.297,-0.19 0.526,-0.332 0.981,-0.61 2.754,-1.71 2.799,-2.197 0.055,-0.602 0.034,-0.778 -0.758,-2.264 -0.168,-0.316 -0.365,-0.654 -0.568,-1.004 -0.754,-1.295 -1.598,-2.745 -1.41,-3.784 0.21,-1.173 2.05,-1.845 3.608,-2.415 0.194,-0.07 0.385,-0.14 0.567,-0.209 l 1.623,-0.609 c 1.556,-0.582 3.284,-1.229 3.57,-1.36 0.394,-0.181 0.292,-0.355 -0.903,-0.468 -0.193439,-0.01897 -0.386775,-0.03897 -0.58,-0.06 -1.48,-0.157 -4.209,-0.446 -5.535,-0.077 -0.261,0.073 -0.553,0.152 -0.86,0.235 -1.49,0.403 -3.317,0.897 -3.493,1.182 -0.03,0.05 -0.06,0.093 -0.089,0.133 -0.168,0.238 -0.277,0.394 -0.091,1.406 0.055,0.302 0.169,0.895 0.31,1.629 0.41,2.148 1.053,5.498 1.134,6.25 0.011,0.106 0.024,0.207 0.036,0.305 0.103,0.84 0.171,1.399 -0.805,1.622 l -0.255,0.058 c -1.102,0.252 -2.717,0.623 -3.3,0.623 -0.584,0 -2.2,-0.37 -3.302,-0.623 L 24.38,29.498 c -0.976,-0.223 -0.907,-0.782 -0.804,-1.622 0.012,-0.098 0.024,-0.2 0.035,-0.305 0.081,-0.753 0.725,-4.112 1.137,-6.259 0.14,-0.73 0.253,-1.32 0.308,-1.62 0.185,-1.012 0.076,-1.168 -0.092,-1.406 -0.03095,-0.04368 -0.06095,-0.08803 -0.09,-0.133 -0.174,-0.285 -2,-0.779 -3.491,-1.182 -0.307,-0.083 -0.6,-0.162 -0.86,-0.235 -1.327,-0.37 -4.055,-0.08 -5.535,0.077 -0.226,0.024 -0.422,0.045 -0.58,0.06 -1.196,0.113 -1.297,0.287 -0.903,0.468 0.285,0.131 2.013,0.778 3.568,1.36 0.597,0.223 1.17,0.437 1.624,0.609 0.183,0.069 0.373,0.138 0.568,0.21 1.558,0.57 3.398,1.241 3.608,2.414 0.187,1.039 -0.657,2.489 -1.41,3.784 -0.204,0.35 -0.4,0.688 -0.569,1.004 -0.791,1.486 -0.812,1.662 -0.757,2.264 0.044,0.488 1.816,1.587 2.798,2.197 0.229,0.142 0.415,0.257 0.526,0.332 0.098,0.066 0.266,0.168 0.48,0.298 1.076,0.654 3.29,2 3.29,2.81 0,0.97 -3.078,3.507 -4.05,4.137 -0.97,0.63 -3.612,2.008 -4.737,2.218 -1.124,0.21 -2.88,-1.061 -3.904,-2.966 -0.963,-1.791 -0.207,-3.559 0.406,-4.99 l 0.115,-0.27 C 15.62,31.43 14.828,30.634 14.289,30.094 14.22987,30.03478 14.171528,29.974778 14.114,29.914 L 8.724,24.197 C 8.566,24.03 8.408,23.877 8.256,23.729 7.528,23.022 6.91,22.421 6.91,20.887 c 0,-1.855 7.189,-10.536 7.189,-10.536 0,0 6.066,1.157 6.884,1.157 0.653,0 1.913,-0.433 3.227,-0.885 0.333,-0.114 0.669,-0.23 1,-0.34 1.635,-0.545 2.726,-0.549 2.726,-0.549 0,0 1.09,0.004 2.726,0.549 0.33,0.11 0.667,0.226 1,0.34 1.313,0.452 2.574,0.885 3.226,0.885 z m -1.041,30.706 c 1.282,0.66 2.192,1.128 2.536,1.343 0.445,0.278 0.174,0.803 -0.232,1.09 -0.405,0.285 -5.853,4.499 -6.381,4.965 l -0.215,0.191 c -0.509,0.459 -1.159,1.044 -1.62,1.044 -0.46,0 -1.11,-0.586 -1.62,-1.044 l -0.213,-0.191 c -0.53,-0.466 -5.977,-4.68 -6.382,-4.966 -0.405,-0.286 -0.677,-0.81 -0.232,-1.09 0.344,-0.214 1.255,-0.683 2.539,-1.344 l 1.22,-0.629 c 1.92,-0.992 4.315,-1.837 4.689,-1.837 0.373,0 2.767,0.844 4.689,1.837 0.436,0.226 0.845,0.437 1.222,0.63 z"
stroke-linecap="round"
id="path5" />
</g>
<g
transform="translate(-0.0645,-26.246)"
id="0KuDkb-_5PZSfGEzYnH-2">
<linearGradient
id="SVGID_1"
gradientUnits="userSpaceOnUse"
x1="6.2779999"
y1="11.466"
x2="50.564999"
y2="11.466">
<stop
offset="0%"
style="stop-color:rgb(255,69,42);stop-opacity: 1"
id="stop5" />
<stop
offset="100%"
style="stop-color:rgb(255,32,0);stop-opacity: 1"
id="stop6" />
</linearGradient>
<path
style="opacity:1;fill:url(#SVGID_1);fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0"
transform="translate(-27.9355,-5.754)"
d="M 43.34,6.334 37.751,0 H 18.12 l -5.589,6.334 c 0,0 -4.908,-1.362 -7.225,0.953 0,0 6.544,-0.59 8.793,3.064 0,0 6.066,1.157 6.884,1.157 0.818,0 2.59,-0.68 4.226,-1.225 1.636,-0.545 2.727,-0.549 2.727,-0.549 0,0 1.09,0.004 2.726,0.549 1.636,0.545 3.408,1.225 4.226,1.225 0.818,0 6.885,-1.157 6.885,-1.157 C 44.022,6.697 50.565,7.287 50.565,7.287 48.248,4.972 43.34,6.334 43.34,6.334 Z"
stroke-linecap="round"
id="path6" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Discourse</title><path d="M11.992 0C5.482 0 0 5.278 0 11.791V24l11.99-.012c6.51 0 11.79-5.481 11.79-11.991C23.78 5.486 18.495 0 11.992 0"/><path d="M12.108 4.564c-4.009.002-7.306 3.301-7.306 7.31 0 1.215.302 2.411.881 3.479L4.36 19.607l4.749-1.072a7.312 7.312 0 0 0 3.002.645c4.009 0 7.308-3.299 7.308-7.308 0-4.008-3.297-7.306-7.305-7.308z" fill="#fff9ae"/><path d="M17.822 16.395a7.307 7.307 0 0 1-8.713 2.128L4.36 19.61l4.834-.571a7.306 7.306 0 0 0 8.712-11.613 7.306 7.306 0 0 1-.084 8.969" fill="#00aeef"/><path d="M17.413 15.006a7.307 7.307 0 0 1-8.443 3.027L4.36 19.61l4.749-1.075A7.306 7.306 0 0 0 16.56 6.078a7.305 7.305 0 0 1 .853 8.928" fill="#00a94f"/><path d="M6.12 15.515a7.308 7.308 0 0 1 11.79-8.091 7.308 7.308 0 0 0-12.227 7.929L4.36 19.607z" fill="#f15d22"/><path d="M5.683 15.353A7.308 7.308 0 0 1 16.56 6.078 7.308 7.308 0 0 0 5.232 15.24l-.869 4.37z" fill="#d0232b"/></svg>

After

Width:  |  Height:  |  Size: 973 B

View File

@@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>dus.net</title><circle cx="12" cy="21.799" r="2.201"/><path d="M5.467 0a2.202 2.202 0 1 0 .001 4.403A2.202 2.202 0 0 0 5.467 0M12 0a2.201 2.201 0 1 0 0 4.402A2.201 2.201 0 0 0 12 0m6.533 4.402a2.201 2.201 0 1 0 0-4.402 2.201 2.201 0 0 0 0 4.402M5.467 6.533a2.201 2.201 0 1 0 0 4.402 2.201 2.201 0 0 0 0-4.402m6.533 0a2.2 2.2 0 1 0-.001 4.4 2.2 2.2 0 0 0 .001-4.4m6.533 0a2.201 2.201 0 1 0-.001 4.403 2.201 2.201 0 0 0 .001-4.403M5.467 13.065a2.202 2.202 0 1 0 .001 4.403 2.202 2.202 0 0 0-.001-4.403m6.533 0a2.201 2.201 0 1 0 0 4.402 2.201 2.201 0 0 0 0-4.402m6.533 0a2.202 2.202 0 1 0 0 4.404 2.202 2.202 0 0 0 0-4.404" fill="#eb311b"/></svg>

After

Width:  |  Height:  |  Size: 721 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
width="96"
height="96"
viewBox="0 0 96 96"
xml:space="preserve"
id="svg4"
sodipodi:docname="gitlab.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview4"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="8.09375"
inkscape:cx="48"
inkscape:cy="48"
inkscape:window-width="1920"
inkscape:window-height="991"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<desc
id="desc1">Created with Fabric.js 5.3.0</desc>
<defs
id="defs1">
</defs>
<g
transform="matrix(2,0,0,2,47.999807,47.999999)"
id="LlMVISacmLRypCAN8RaOa">
<g
id="g4">
<g
transform="translate(-0.00126224)"
id="jXZGVBwYEboir0aGofEdX">
<path
style="opacity:1;fill:#e24329;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0"
transform="translate(-15.998738,-15.999998)"
d="M 31.4618,12.7787 31.417,12.6641 27.0667,1.31308 C 26.9783,1.09046 26.8218,0.90145 26.6197,0.773028 26.416,0.644476 26.1775,0.582308 25.937,0.595107 25.6965,0.607906 25.4659,0.695039 25.277,0.844481 25.0899,0.994513 24.955,1.1998 24.8915,1.43106 L 21.9503,10.4324 H 10.0509 L 7.10976,1.43106 C 7.04625,1.1998 6.91133,0.994513 6.72425,0.844481 6.53618,0.694035 6.30572,0.606246 6.06523,0.593431 5.82473,0.580616 5.58625,0.64342 5.38326,0.773028 5.18023,0.900924 5.02312,1.09005 4.9346,1.31308 L 0.579314,12.679 0.534448,12.792 c -0.6252016,1.637 -0.702052,3.4327 -0.218996,5.1171 0.483056,1.6844 1.499908,3.1665 2.897638,4.2233 l 0.01662,0.0116 0.03822,0.0299 6.63513,4.9668 3.28014,2.4843 1.9941,1.5104 c 0.2342,0.177 0.5198,0.2728 0.8134,0.2728 0.2935,0 0.5791,-0.0958 0.8134,-0.2728 l 1.994,-1.5104 3.2818,-2.4843 6.6734,-4.9967 0.0182,-0.0133 c 1.4025,-1.0558 2.4234,-2.5391 2.9087,-4.2262 0.4854,-1.687 0.4087,-3.4861 -0.2184,-5.1258 z"
stroke-linecap="round"
id="path1" />
</g>
<g
transform="translate(8.0042462,3.9024)"
id="_HnOE30YI3q64iu3Kyf5i">
<path
style="opacity:1;fill:#fc6d26;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0"
transform="translate(-24.004246,-19.9024)"
d="m 31.462,12.7787 -0.0448,-0.1146 c -2.1217,0.4372 -4.121,1.3364 -5.8558,2.6337 l -9.5531,7.24 c 3.2569,2.4627 6.0918,4.6029 6.0918,4.6029 l 6.6733,-4.9967 0.0183,-0.0133 c 1.399,-1.0584 2.4159,-2.543 2.8976,-4.2298 0.4817,-1.6869 0.4019,-3.4846 -0.2273,-5.1222 z"
stroke-linecap="round"
id="path2" />
</g>
<g
transform="translate(-0.00585,10.973)"
id="AuQqbk918YYSmtG3qADkx">
<path
style="opacity:1;fill:#fca326;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0"
transform="translate(-15.99415,-26.973)"
d="m 9.9082,27.1407 3.2752,2.4843 1.994,1.5104 c 0.2343,0.177 0.5198,0.2728 0.8134,0.2728 0.2936,0 0.5792,-0.0958 0.8134,-0.2728 l 1.994,-1.5104 3.2819,-2.4843 c 0,0 -2.8349,-2.1402 -6.0918,-4.6029 z"
stroke-linecap="round"
id="path3" />
</g>
<g
transform="translate(-7.9959499,3.90655)"
id="WbT8aWwyHoQxpOUXtkBeV">
<path
style="opacity:1;fill:#fc6d26;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0"
transform="translate(-8.0040501,-19.90655)"
d="M 6.43513,15.3045 C 4.70076,14.0067 2.70123,13.108 0.579333,12.6724 l -0.044866,0.113 c -0.6268073,1.6378 -0.704503,3.4349 -0.221388,5.1207 0.483115,1.6858 1.500881,3.169 2.900031,4.2263 l 0.01662,0.0116 0.03822,0.0299 6.63512,4.9668 6.10503,-4.6029 z"
stroke-linecap="round"
id="path4" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 943.11 911.62"><script xmlns=""/>
<defs>
<style>
.cls-1 {
fill: #fff;
}
.cls-2 {
fill: #37474f;
}
.cls-3 {
fill: #46509e;
}
</style>
</defs>
<ellipse class="cls-3" cx="471.56" cy="454.28" rx="471.56" ry="454.28"/>
<g>
<g>
<ellipse class="cls-2" cx="471.56" cy="390.28" rx="233.66" ry="207"/>
<path class="cls-2" d="m705.22,848.95c-36.69,21.14-123.09,64.33-240.64,62.57-109.54-1.63-190.04-41.45-226.68-62.57v-454.19h467.33v454.19Z"/>
</g>
<path class="cls-1" d="m658.81,397.7v475.8c-36.98,15.7-98.93,36.54-177.98,38.04-88.67,1.69-157.75-21.73-196.2-38.04v-475.8c0-95.55,83.77-173.02,187.09-173.02s187.09,77.47,187.09,173.02Z"/>
<polygon class="cls-3" points="565.02 431.68 471.56 514.49 378.09 431.68 565.02 431.68"/>
<ellipse class="cls-2" cx="378.09" cy="369.58" rx="23.37" ry="20.7"/>
<ellipse class="cls-2" cx="565.02" cy="369.58" rx="23.37" ry="20.7"/>
<path class="cls-2" d="m658.49,400.63c0-40.04-36.59-72.45-81.78-72.45s-81.78,32.41-81.78,72.45c0,11.14,2.81,21.65,7.9,31.05h-62.54c5.1-9.4,7.9-19.91,7.9-31.05,0-40.04-36.59-72.45-81.78-72.45s-81.78,32.41-81.78,72.45l-46.73-10.35c0-114.32,104.63-207,233.66-207s233.66,92.69,233.66,207l-46.73,10.35Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,6 @@
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 388.755L126.571 249.665L0 110.571C13.9095 96.6616 33.3816 87.8523 54.7141 87.8523C77.4297 87.8523 97.3643 97.5937 111.274 112.89L181.281 189.854L235.992 249.665L181.281 309.942L111.274 386.902C97.3643 402.202 77.4297 411.94 54.7141 411.94C33.3816 411.94 13.9095 403.127 0 388.755ZM500 388.757L373.43 249.667L500 110.573C486.091 96.6633 466.619 87.8536 445.286 87.8536C422.571 87.8536 402.636 97.5956 388.726 112.892L318.719 189.856L264.008 249.667L318.719 309.943L388.726 386.904C402.636 402.204 422.571 411.942 445.286 411.942C466.619 411.942 486.091 403.128 500 388.757Z" fill="#E57000"/>
<g style="mix-blend-mode:difference">
<path fill-rule="evenodd" clip-rule="evenodd" d="M249.934 235.708L299.87 180.692L415.394 53.7358C402.7 41.0404 384.926 33 365.456 33C344.722 33 326.528 41.8913 313.83 55.8522L249.934 126.1L185.607 55.8522C172.491 41.4659 155.138 33 134.4 33C114.941 33 97.163 41.0404 84.4681 53.7358L199.996 180.692L249.934 235.708ZM249.937 263.615L299.873 318.631L415.398 445.588C402.703 458.283 384.929 466.323 365.459 466.323C344.725 466.323 326.531 457.432 313.834 443.471L249.937 373.224L185.61 443.471C172.494 457.857 155.141 466.323 134.403 466.323C114.944 466.323 97.1661 458.283 84.4713 445.588L200 318.631L249.937 263.615Z" fill="white"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
width="96"
height="96"
viewBox="0 0 96 96"
xml:space="preserve"
id="svg2"
sodipodi:docname="rust.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview2"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="8.09375"
inkscape:cx="48"
inkscape:cy="47.876448"
inkscape:window-width="1920"
inkscape:window-height="991"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" />
<desc
id="desc1">Created with Fabric.js 5.3.0</desc>
<defs
id="defs1">
</defs>
<g
transform="matrix(0.61244177,0,0,0.61244177,48.58182,48.643064)"
id="d0KfchOoJLd1am88Nf2bl">
<g
id="g2">
<g
transform="translate(-0.95,-1.05)"
id="CssRuECJ6utZ-6lKixFij">
<path
style="opacity:1;fill:#000000;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0"
transform="translate(-71.05,-70.95)"
d="M 71.05,23.68 C 44.99,23.68 23.78,44.9 23.78,70.95 23.78,97 45,118.22 71.05,118.22 97.1,118.22 118.32,97 118.32,70.95 118.32,44.9 97.1,23.68 71.05,23.68 Z m -0.07,4.2 c 1.680761,0.04353 3.02056,1.423255 3.02,3.11 0,1.717606 -1.392394,3.11 -3.11,3.11 -1.717606,0 -3.11,-1.392394 -3.11,-3.11 -3.52e-4,-0.840656 0.339633,-1.645648 0.942484,-2.231544 C 69.325335,28.172561 70.139696,27.855672 70.98,27.88 Z M 78.1,33 c 11.050222,2.069744 20.631432,8.893613 26.2,18.66 l -3.67,8.28 c -0.63,1.43 0.02,3.11 1.44,3.75 l 7.06,3.13 c 0.219,2.206835 0.24577,4.428529 0.08,6.64 h -3.93 c -0.39,0 -0.55,0.26 -0.55,0.64 v 1.8 c 0,4.24 -2.39,5.17 -4.49,5.4 -2,0.23 -4.21,-0.84 -4.49,-2.06 -1.18,-6.63 -3.14,-8.04 -6.24,-10.49 3.85,-2.44 7.85,-6.05 7.85,-10.87 0,-5.21 -3.57,-8.49 -6,-10.1 -3.42,-2.25 -7.2,-2.7 -8.22,-2.7 H 42.54 C 48.154011,38.808406 55.67907,34.5626 63.95,33 l 4.79,5.02 c 1.08,1.13 2.87,1.18 4,0.09 z M 33.9,56.02 c 1.682359,0.04871 3.020705,1.426936 3.02,3.11 0,1.717606 -1.392394,3.11 -3.11,3.11 -1.717606,0 -3.11,-1.392394 -3.11,-3.11 -3.52e-4,-0.840656 0.339633,-1.645648 0.942484,-2.231544 C 32.245335,56.312561 33.059696,55.995672 33.9,56.02 Z m 74.15,0.14 c 1.68236,0.04871 3.0207,1.426936 3.02,3.11 0,1.717606 -1.39239,3.11 -3.11,3.11 -1.71761,0 -3.11,-1.392394 -3.11,-3.11 -3.5e-4,-0.840656 0.33963,-1.645648 0.94248,-2.231544 0.60285,-0.585895 1.41722,-0.902784 2.25752,-0.878456 z m -68.29,0.5 h 5.42 V 81.1 H 34.24 C 32.88888,76.35544 32.46782,71.394402 33,66.49 l 6.7,-2.98 c 1.43,-0.64 2.08,-2.31 1.44,-3.74 z m 22.62,0.26 h 12.91 c 0.67,0 4.71,0.77 4.71,3.8 0,2.51 -3.1,3.41 -5.65,3.41 H 62.37 Z m 0,17.56 h 9.89 c 0.9,0 4.83,0.26 6.08,5.28 0.39,1.54 1.26,6.56 1.85,8.17 0.59,1.8 2.98,5.4 5.53,5.4 h 16.14 c -1.08168,1.448565 -2.26464,2.818663 -3.54,4.1 l -6.57,-1.41 c -1.53,-0.33 -3.04,0.65 -3.37,2.18 l -1.56,7.28 c -10.151161,4.59844 -21.802519,4.54367 -31.91,-0.15 L 53.36,98.05 C 53.03,96.52 51.53,95.54 50,95.87 l -6.43,1.38 c -1.193214,-1.230834 -2.302379,-2.54045 -3.32,-3.92 h 31.27 c 0.35,0 0.59,-0.06 0.59,-0.39 V 81.88 c 0,-0.32 -0.24,-0.39 -0.59,-0.39 H 62.37 Z M 47.95,99.81 c 1.682359,0.04871 3.020705,1.42694 3.02,3.11 0,1.71761 -1.392394,3.11 -3.11,3.11 -1.717606,0 -3.11,-1.39239 -3.11,-3.11 -3.52e-4,-0.84066 0.339633,-1.64565 0.942484,-2.23154 C 46.295335,100.10256 47.109696,99.785672 47.95,99.81 Z M 94,99.95 c 1.682359,0.04871 3.020705,1.42694 3.02,3.11 0,1.71761 -1.392394,3.11 -3.11,3.11 -1.717606,0 -3.11,-1.39239 -3.11,-3.11 -3.52e-4,-0.84066 0.339633,-1.64565 0.942484,-2.23154 C 92.345335,100.24256 93.159696,99.925672 94,99.95 Z"
stroke-linecap="round"
id="path1" />
</g>
<g
transform="translate(-0.95,-1.05)"
id="Sz8Hb_caEIP6YkrZ7l00L">
<path
style="opacity:1;fill:#000000;fill-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0"
transform="translate(-71.05,-70.95)"
d="m 115.68,70.95 c 0,11.836612 -4.70208,23.188427 -13.07182,31.55818 C 94.238427,110.87792 82.886612,115.58 71.05,115.58 c -24.648468,0 -44.63,-19.981532 -44.63,-44.63 0,-24.648468 19.981532,-44.63 44.63,-44.63 24.648468,0 44.63,19.981532 44.63,44.63 z m -0.84,-4.31 6.96,4.31 -6.96,4.31 5.98,5.59 -7.66,2.87 4.78,6.65 -8.09,1.32 3.4,7.46 -8.19,-0.29 1.88,7.98 -7.98,-1.88 0.29,8.19 -7.46,-3.4 -1.32,8.09 -6.65,-4.78 -2.87,7.66 -5.59,-5.98 -4.31,6.96 -4.31,-6.96 -5.59,5.98 -2.87,-7.66 -6.65,4.78 -1.32,-8.09 -7.46,3.4 0.29,-8.19 -7.98,1.88 1.88,-7.98 -8.19,0.29 3.4,-7.46 -8.09,-1.32 4.78,-6.65 -7.66,-2.87 5.98,-5.59 -6.96,-4.31 6.96,-4.31 -5.98,-5.59 7.66,-2.87 -4.78,-6.65 8.09,-1.32 -3.4,-7.46 8.19,0.29 -1.88,-7.98 7.98,1.88 -0.29,-8.19 7.46,3.4 1.32,-8.09 6.65,4.78 2.87,-7.66 5.59,5.98 4.31,-6.96 4.31,6.96 5.59,-5.98 2.87,7.66 6.65,-4.78 1.32,8.09 7.46,-3.4 -0.29,8.19 7.98,-1.88 -1.88,7.98 8.19,-0.29 -3.4,7.46 8.09,1.32 -4.78,6.65 7.66,2.87 z"
stroke-linecap="round"
id="path2" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@@ -0,0 +1 @@
<svg rols="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>service-bw</title><path fill="#f8dd15" d="M7.926 7.962c-.582.086-.083.499-.083.499l.618-.477s.035-.09-.535-.022 M9.571 10.864c.144-1.248-1.379-2.88-1.379-2.88l.592-.794-1.478.407s-1.581-.691-3.946.793a1.717 1.717 0 0 0 0 .989c.097.322.925-.48 1.107.154-.269.078-.548.12-.829.125a1.239 1.239 0 0 1-.758-.196 2.065 2.065 0 0 1-.48-.96c-.073-.356-.342 1.447 1.21 1.447.293.026.588-.004.87-.09-.051.858-.829.215-.829.215v.988s1.258-.163 1.488-.339c-.064.221-.173.819-.173.819l-2.025-.08S.691 9.478 0 11.856l1.581.099 3.651 2.378-1.677 1.28s-2.56-.858-2.464 1.123l3.251.058 5.095-1.184v-.586s1.449-.099 1.747-.099a15.308 15.308 0 0 0 1.862-.778v-3.232c-1.724.202-3.193.269-3.475-.051m-3.859-2.88-.618.493s-.499-.416.084-.499c.582-.084.534.022.534.022z M11.546 8.883c-.298-.601.492-1.683.492-1.683s-2.169.595-1.776 1.981c.304 1.069 2.01.96 2.778.854v-.96c-.618.103-1.334.135-1.494-.192"/><path d="M13.28 14.024v4.056h-.32V5.92h.32v3.146c.247-.045.493-.099.736-.164 0 0 5.203-1.52 5.622-1.6.42-.08 2.269-.396 2.762 1.485.176 1.415-.48 1.776-.995 2.279.883.08 1.184 2.393 1.184 3.068 1.062-.371 1.283.298 1.382.298.099 0-.089 2.179-.089 2.179l-2.762-.096s-.352-1.017 1.28-1.081c-2.275-.692-3.261-2.068-3.261-2.068s-.889 1.104-1.184 1.6c-.294.496-.393 1.642-.393 1.642H14.01c.06-.55.182-.858.588-1.046l1.476.041.387-2.771-.413-.099s-1.449.677-2.768 1.291m0-3.995v.845c2.485-.308 5.388-.857 5.958-.912a1.866 1.866 0 0 1 1.479.396s.598.496.79-.988c.192-1.485-1.974-1.204-2.368-1.088-.393.115-5.705 1.718-5.705 1.718s-.057.012-.154.029"/></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,27 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 686 802" width="686" height="802">
<title>TCPShield</title>
<style>
.s0 { fill: #ffffff;stroke: #414042;stroke-miterlimit:10 }
.s1 { fill: #ffffff }
</style>
<g id="Layer 1">
<g id="Layer 2 1 ">
</g>
<g id="Layer 1 1 ">
<g id="&lt;Group&gt;">
<path id="&lt;Path&gt;" class="s0" d="m205.2 141.5c6.8-6.1 13.6-12 20.5-17.8 7.5-6.1 15-12.2 22.6-17.9 3.8-2.9 7.7-5.7 11.5-8.5 1.9-1.4 3.9-2.7 5.8-4.1 1.9-1.3 3.9-2.7 5.8-4 2-1.3 3.9-2.6 5.9-3.9 2-1.3 3.9-2.5 5.9-3.8 3.9-2.6 7.9-4.9 11.9-7.4 2-1.2 4-2.3 6-3.5l3-1.7 3-1.7q3-1.7 6-3.3c2-1.1 4-2.1 6.1-3.2 1-0.5 2-1.1 3-1.6q1.5-0.8 3-1.5c2-1 4-2 6.1-3 4.1-1.9 8.1-3.8 12.2-5.6 4.1-1.8 8.2-3.4 12.3-5.1 2-0.8 4.1-1.5 6.1-2.3 1-0.4 2-0.8 3.1-1.1 1-0.3 2.1-0.7 3.1-1 8.2-2.9 16.4-5.2 24.5-7.3 2-0.5 4.1-1 6.1-1.4 2-0.4 4.1-0.8 6.1-1.2 2-0.4 4-0.7 6.1-1.1 2-0.3 4-0.7 6-0.9 16-2.2 31.7-2.4 46.5-0.6 1.8 0.3 3.7 0.5 5.5 0.8 1.8 0.3 3.6 0.7 5.4 1 1.8 0.3 3.5 0.8 5.3 1.2 1.7 0.4 3.5 0.8 5.2 1.3q2.5 0.7 5.1 1.5c1.7 0.6 3.3 1.2 4.9 1.8q1.2 0.4 2.4 0.9c0.8 0.3 1.6 0.7 2.4 1 0.8 0.3 1.6 0.7 2.4 1 0.8 0.3 1.6 0.6 2.3 1 12.3 5.8 23.2 13 32.4 21.3 9.3 8.2 17.2 17.2 23.8 26.3 6.6 9.1 12.1 18.3 16.7 27 1.1 2.2 2.2 4.4 3.2 6.5 0.5 1.1 1.1 2.1 1.5 3.2 0.5 1.1 0.9 2.1 1.4 3.2 0.9 2.1 1.8 4.1 2.7 6.1 0.8 2 1.6 4 2.4 5.9 1.6 3.8 2.9 7.6 4.2 11 1.3 3.5 2.3 6.8 3.3 9.9 0.5 1.5 1 3 1.4 4.5 0.5 1.4 0.8 2.8 1.2 4.1 0.8 2.6 1.4 5 2 7.1 0.3 1 0.6 2.1 0.8 3.1 0.3 1 0.5 1.9 0.7 2.8 0.4 1.7 0.8 3.2 1.1 4.3 0 0.2 0.1 0.3 0.1 0.4l5.3 1.9 26.4-7.1c0 0-0.3-1.2-1-3.5-0.3-1.2-0.7-2.6-1.2-4.3-0.2-0.9-0.5-1.8-0.8-2.8-0.3-1-0.6-2-0.9-3.2-0.7-2.4-1.6-5-2.5-7.9-0.5-1.4-0.9-3-1.5-4.5-0.5-1.5-1.1-3.1-1.7-4.8-1.2-3.3-2.4-6.9-3.9-10.7-1.5-3.8-3-7.8-4.9-12-0.9-2.1-1.8-4.3-2.8-6.5-1-2.2-2.1-4.4-3.2-6.7-0.5-1.1-1.1-2.3-1.7-3.5-0.6-1.2-1.2-2.3-1.8-3.5-1.2-2.4-2.5-4.8-3.8-7.2-5.5-9.7-11.9-20-19.8-30.3-7.9-10.3-17.4-20.7-28.8-30.2-11.3-9.6-24.7-18-39.7-24.6-0.9-0.4-1.9-0.8-2.8-1.2-1-0.4-1.9-0.8-2.9-1.1-1-0.4-1.9-0.8-2.9-1.1-1-0.4-2-0.7-3-1-2-0.6-4-1.3-6-1.9-2-0.6-4.1-1.1-6.1-1.7-2-0.6-4.1-1-6.2-1.4-2.1-0.4-4.2-0.9-6.3-1.2-2.1-0.3-4.2-0.7-6.4-1-2.1-0.2-4.3-0.5-6.5-0.7-17.3-1.7-35.2-1-53 1.9-2.2 0.3-4.5 0.8-6.7 1.2-2.2 0.4-4.5 0.8-6.7 1.4-2.2 0.5-4.4 1-6.7 1.5-2.2 0.6-4.4 1.2-6.6 1.7-8.8 2.4-17.7 5.2-26.4 8.5q-1.7 0.6-3.3 1.2c-1.1 0.4-2.2 0.9-3.3 1.3-2.2 0.9-4.4 1.7-6.5 2.6-4.3 1.9-8.6 3.7-12.9 5.7-4.3 2-8.5 4.1-12.8 6.2q-3.2 1.6-6.3 3.3c-1.1 0.6-2.1 1.1-3.2 1.7-1.1 0.6-2.1 1.2-3.1 1.7-2.1 1.2-4.2 2.3-6.3 3.5-2.1 1.2-4.1 2.4-6.2 3.7l-3.1 1.8-3.1 1.9c-2.1 1.3-4.1 2.5-6.2 3.8-4.1 2.6-8.2 5.2-12.2 8-2 1.4-4.1 2.7-6 4.1q-3 2.1-6 4.2c-2 1.4-4 2.9-6 4.3-2 1.5-4 2.9-5.9 4.4-3.9 3-7.8 6-11.7 9.1-7.8 6.1-15.4 12.5-22.9 19-15 13.1-29.5 26.9-43.4 41.4q-2.4 2.5-4.8 5.1z"/>
<path id="&lt;Path&gt;" class="s0" d="m157.7 674.4c-2.1-0.6-4.2-1.3-6.4-1.9-1.1-0.3-2.2-0.6-3.3-1-1.1-0.4-2.2-0.8-3.4-1.2-2.3-0.8-4.6-1.6-7-2.4-2.3-0.9-4.7-1.8-7.2-2.8-1.2-0.5-2.5-0.9-3.7-1.5-1.2-0.5-2.5-1.1-3.7-1.6-1.2-0.5-2.5-1.1-3.8-1.6-0.6-0.3-1.3-0.6-1.9-0.8-0.6-0.3-1.3-0.6-1.9-0.9-2.5-1.2-5.1-2.5-7.8-3.8q-3.9-2.1-7.8-4.2c-10.5-5.9-21.2-13-31.5-21.7-10.3-8.7-20-19.1-28.5-31.1l-0.8-1.1-0.8-1.2c-0.5-0.8-1-1.6-1.5-2.3-0.5-0.8-1-1.6-1.5-2.3-0.3-0.4-0.5-0.8-0.8-1.2l-0.7-1.2c-0.9-1.6-1.9-3.3-2.8-4.9-0.5-0.8-0.9-1.7-1.3-2.5-0.4-0.8-0.9-1.7-1.3-2.5-0.4-0.9-0.9-1.7-1.3-2.6-0.5-0.8-0.8-1.7-1.2-2.6-0.8-1.8-1.6-3.5-2.4-5.3-0.7-1.8-1.4-3.6-2.1-5.5-0.3-0.9-0.7-1.8-1-2.8-0.3-0.9-0.6-1.9-0.9-2.8-0.3-0.9-0.6-1.9-0.9-2.8l-0.5-1.4c-0.2-0.5-0.3-1-0.4-1.4-0.5-1.9-1.1-3.9-1.6-5.8-0.3-1-0.5-2-0.7-2.9-0.2-1-0.4-2-0.7-3-0.2-1-0.4-2-0.7-3-0.2-1-0.4-2-0.5-3-1.6-8-2.4-16.3-3-24.7-1-16.8 0.1-34.2 2.7-51.7 0.4-2.2 0.7-4.4 1.1-6.6q0.6-3.3 1.2-6.6 0.3-1.7 0.6-3.3c0.2-1.1 0.5-2.2 0.7-3.3q0.8-3.3 1.5-6.6c1.1-4.4 2.2-8.8 3.5-13.2q0.5-1.7 0.9-3.3c0.3-1.1 0.6-2.2 1-3.3 0.7-2.2 1.4-4.4 2-6.6 0.7-2.2 1.5-4.4 2.2-6.6 0.7-2.2 1.5-4.4 2.3-6.6q1.2-3.3 2.4-6.6c0.8-2.2 1.7-4.4 2.5-6.5l1.3-3.3 1.4-3.3c0.9-2.2 1.8-4.3 2.7-6.5 1-2.2 1.9-4.3 2.9-6.5 0.5-1.1 0.9-2.2 1.4-3.2l1.5-3.2c3.3-7.2 6.8-14.2 10.5-21.3-1.5-6.2-2.9-12.4-4.2-18.7-1.5 2.9-3 5.8-4.4 8.7-4.3 8.8-8.6 17.6-12.4 26.6l-1.5 3.3c-0.5 1.1-0.9 2.2-1.4 3.4-0.9 2.2-1.8 4.5-2.8 6.7-0.9 2.3-1.7 4.5-2.6 6.7l-1.3 3.4-1.2 3.4c-0.8 2.3-1.6 4.5-2.4 6.8-0.8 2.3-1.5 4.5-2.3 6.8-0.8 2.3-1.5 4.5-2.2 6.8-0.7 2.3-1.4 4.5-2.1 6.8-0.6 2.3-1.3 4.5-1.9 6.8-0.3 1.1-0.6 2.3-0.9 3.4-0.3 1.1-0.6 2.3-0.9 3.4-1.2 4.5-2.2 9.1-3.2 13.6-0.5 2.3-0.9 4.6-1.4 6.8-0.2 1.1-0.4 2.3-0.7 3.4-0.2 1.1-0.4 2.3-0.6 3.4-0.4 2.3-0.8 4.5-1.1 6.8-0.3 2.3-0.6 4.5-0.9 6.8-2.3 18.1-2.9 36.1-1.4 53.4 0.8 8.7 1.9 17.2 3.7 25.5 0.2 1 0.4 2.1 0.6 3.1 0.3 1 0.5 2 0.8 3.1 0.3 1 0.5 2 0.8 3 0.3 1 0.5 2 0.8 3q0.9 3 1.8 6c0.2 0.5 0.3 1 0.5 1.5l0.5 1.5c0.3 1 0.7 1.9 1 2.9 0.3 1 0.7 1.9 1 2.9 0.3 1 0.8 1.9 1.1 2.8 0.8 1.9 1.6 3.7 2.3 5.6 0.9 1.8 1.7 3.6 2.6 5.4 0.4 0.9 0.8 1.8 1.3 2.7 0.5 0.9 0.9 1.7 1.4 2.6 0.5 0.9 0.9 1.7 1.4 2.6 0.5 0.9 0.9 1.7 1.5 2.6 1 1.7 2 3.3 3 5l0.8 1.2c0.3 0.4 0.5 0.8 0.8 1.2 0.5 0.8 1.1 1.6 1.6 2.4 0.5 0.8 1.1 1.6 1.6 2.4l0.8 1.2 0.9 1.1c9.1 12.2 19.4 22.6 30.1 31.2 10.8 8.6 21.8 15.6 32.6 21.3 2.7 1.4 5.4 2.7 8 4.1 2.7 1.2 5.3 2.4 7.9 3.6 0.6 0.3 1.3 0.6 1.9 0.9 0.7 0.3 1.3 0.5 2 0.8 1.3 0.5 2.6 1 3.9 1.6 1.3 0.5 2.5 1 3.8 1.5 1.2 0.5 2.5 0.9 3.8 1.4 2.5 0.9 4.9 1.8 7.3 2.6 2.4 0.8 4.8 1.5 7.1 2.2 1.2 0.4 2.3 0.7 3.4 1.1 1.1 0.4 2.3 0.6 3.4 1 2.2 0.6 4.4 1.2 6.5 1.8 16.9 4.5 30.4 6.7 39.7 8.3 9.3 1.3 14.2 2 14.2 2 0 0-4.9-0.8-14.1-2.4-9.1-2-22.5-4.6-39.3-9.4z"/>
</g>
<g id="&lt;Group&gt;">
<g id="&lt;Group&gt;">
</g>
<g id="&lt;Group&gt;">
<path id="&lt;Path&gt;" class="s1" d="m294.7 337.1v326.6c19.1 18.4 35.9 32.8 48.1 42.8 12.2-10 29-24.4 48.1-42.8v-326.6h199.8c8-30.4 13.7-62.5 16.3-96.1h-528.4c2.6 33.6 8.4 65.7 16.3 96.1z"/>
</g>
</g>
<g id="&lt;Group&gt;">
<path id="&lt;Compound Path&gt;" fill-rule="evenodd" class="s1" d="m342.8 778.5l-11-7.6c-3.2-2.2-79.3-55.6-156.2-152.6-45.3-57.1-81.4-117.4-107.3-179.4-32.5-77.8-49-158.4-49-239.5v-13.7l323.5-112.5 323.5 112.5v13.7c0 81.1-16.5 161.6-49 239.5-25.9 62-62 122.4-107.3 179.4-76.9 97-153 150.4-156.2 152.6zm-284.9-565.6c4.1 165.5 81.7 297.8 147.1 380.5 56.8 71.9 114.4 119.6 137.8 137.7 23.5-18.1 81-65.8 137.8-137.7 65.4-82.7 143-215 147.1-380.5l-284.9-99z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
width="96"
height="96"
viewBox="0 0 96 96"
xml:space="preserve"
id="svg1"
sodipodi:docname="uphold.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs1" /><sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="8.09375"
inkscape:cx="48"
inkscape:cy="48"
inkscape:window-width="1920"
inkscape:window-height="991"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<desc
id="desc1">Created with Fabric.js 5.3.0</desc>
<g
transform="matrix(1.6496133,0,0,1.6496134,47.999993,48.000007)"
id="RoQhZNvyF1biObpr9w5El">
<path
style="opacity:1;fill:#6fe68a;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0"
d="m 3.59732,16.89848 c -1.1,0.4 -2.3,0.5 -3.5,0.5 h -0.2 c -1.2,0 -2.4,-0.2 -3.5,-0.6 -0.6,-0.2 -1.1,0.2 -1.3,0.7 -0.2,0.6 0.2,1.2 0.7,1.3 1.3,0.4 2.7,0.6 4.1,0.6 h 0.2 c 1.4,0 2.8,-0.2 4.1,-0.6 0.5,-0.2 0.8,-0.8 0.6,-1.4 -0.2,-0.3 -0.7,-0.6 -1.2,-0.5 z m 9.1,-26.7 c 0,0 0,-0.1 0,0 -2.3,-7.3 -9.9,-11.3 -16.9,-8.9 -4,1.4 -7.2,4.7 -8.5,8.9 -1.2,3.6 -0.8,8.4 1.2,12.9 2.6,6.1 7.3,10.4 11.5,10.4 h 0.1 c 4.2,0 8.9,-4.3 11.5,-10.4 1.9,-4.5 2.3,-9.3 1.1,-12.9 m -6.2,-5.2 c -2.1,-0.1 -4.4,0.9 -6.6,2.7 -2.2,-1.8 -4.5,-2.8 -6.6,-2.7 4,-2.9 9.3,-2.9 13.2,0 m -4.8,25.9 c -1.1,0.5 -2.4,0.5 -3.5,0 -3.6,-1.7 -4.3,-8.6 -1.6,-14.9 0.8,-2 1.9,-3.8 3.3,-5.3 1.4,1.6 2.5,3.4 3.3,5.3 2.8,6.4 2.1,13.2 -1.5,14.9 m -11.4,-8.7 c -1.7,-4 -2.1,-8.2 -1.1,-11.4 0.5,-1.6 1.5,-2.8 2.7,-3.4 1.8,-0.8 4.2,-0.1 6.6,1.8 -1.5,1.8 -2.8,3.8 -3.7,5.9 -1.8,4.3 -2.2,8.8 -1.4,12.2 -1.3,-1.5 -2.3,-3.2 -3.1,-5.1 m 19.3,0 c -0.8,1.8 -1.8,3.5 -3,5.1 0.8,-3.4 0.4,-7.9 -1.4,-12.2 -0.9,-2.2 -2.2,-4.2 -3.7,-5.9 2.3,-1.9 4.7,-2.6 6.6,-1.7 1.2,0.6 2.1,1.7 2.7,3.4 0.9,3.1 0.5,7.3 -1.2,11.3 z"
stroke-linecap="round"
id="path1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -1,5 +0,0 @@
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
<g style="mix-blend-mode:difference">
<path fill-rule="evenodd" clip-rule="evenodd" d="M250.207 5C111.849 5 0 117.52 0 256.723C0 367.996 71.6655 462.186 171.084 495.522C183.514 498.028 188.067 490.106 188.067 483.442C188.067 477.606 187.658 457.603 187.658 436.761C118.056 451.767 103.562 406.754 103.562 406.754C92.3769 377.581 75.8036 370.083 75.8036 370.083C53.0231 354.662 77.463 354.662 77.463 354.662C102.733 356.329 115.992 380.501 115.992 380.501C138.358 418.84 174.398 408.007 188.897 401.338C190.966 385.084 197.599 373.832 204.641 367.582C149.128 361.746 90.7226 340.075 90.7226 243.385C90.7226 215.879 100.658 193.374 116.402 175.872C113.918 169.622 105.217 143.779 118.891 109.189C118.891 109.189 140.017 102.519 187.653 135.028C208.047 129.517 229.079 126.714 250.207 126.691C271.333 126.691 292.869 129.611 312.756 135.028C360.396 102.519 381.523 109.189 381.523 109.189C395.197 143.779 386.491 169.622 384.007 175.872C400.165 193.374 409.691 215.879 409.691 243.385C409.691 340.075 351.285 361.326 295.358 367.582C304.474 375.499 312.341 390.5 312.341 414.257C312.341 448.013 311.931 475.105 311.931 483.437C311.931 490.106 316.49 498.028 328.914 495.527C428.333 462.18 499.999 367.996 499.999 256.723C500.409 117.52 388.15 5 250.207 5Z" fill="white"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,5 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 22C17.5 22 22 17.5 22 12C22 6.5 17.5 2 12 2C6.5 2 2 6.5 2 12C2 17.5 6.5 22 12 22Z" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.16998 14.8299L14.83 9.16992" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14.83 14.8299L9.16998 9.16992" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 536 B

View File

@@ -1,20 +0,0 @@
<svg width="62" height="62" viewBox="0 0 62 62" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_884_3193)">
<rect x="15" y="14" width="32" height="32" rx="15" fill="white"/>
</g>
<path d="M40.7558 20.2363C40.4408 19.9212 39.9262 19.9212 39.6112 20.2363L20.2363 39.6217C19.9212 39.9367 19.9212 40.4513 20.2363 40.7663C20.3938 40.9133 20.5933 40.9973 20.8033 40.9973C21.0134 40.9973 21.2129 40.9133 21.3704 40.7558L40.7558 21.3704C41.0814 21.0554 41.0814 20.5513 40.7558 20.2363Z" fill="#292D32"/>
<path d="M33.4575 21.5971V27.5408L27.5347 33.4636V31.8464H24.2898C22.8196 31.8464 22.4101 30.9433 23.3867 29.8407L30.4961 21.7546L31.3362 20.799C32.5018 19.4759 33.4575 19.8329 33.4575 21.5971Z" fill="#292D32"/>
<path d="M37.6039 31.1619L30.4946 39.2479L29.6545 40.2035C28.4888 41.5267 27.5332 41.1696 27.5332 39.4054V36.6121L34.9891 29.1562H36.7008C38.171 29.1562 38.5806 30.0593 37.6039 31.1619Z" fill="#292D32"/>
<defs>
<filter id="filter0_d_884_3193" x="0" y="0" width="62" height="62" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feMorphology radius="5" operator="dilate" in="SourceAlpha" result="effect1_dropShadow_884_3193"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.17 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_884_3193"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_884_3193" result="shape"/>
</filter>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,18 +0,0 @@
<svg width="62" height="62" viewBox="0 0 62 62" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_5498_6943)">
<rect x="15" y="14" width="32" height="32" rx="15" fill="white"/>
</g>
<path d="M36.844 29.1561H33.5997V21.5968C33.5997 19.8329 32.6443 19.476 31.4789 20.7988L30.639 21.7543L23.5311 29.8386C22.5547 30.941 22.9642 31.8439 24.4341 31.8439H27.6783V39.4032C27.6783 41.1671 28.6337 41.524 29.7991 40.2012L30.639 39.2457L37.7469 31.1615C38.7233 30.0591 38.3138 29.1561 36.844 29.1561Z" fill="#292D32"/>
<defs>
<filter id="filter0_d_5498_6943" x="0" y="0" width="62" height="62" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feMorphology radius="5" operator="dilate" in="SourceAlpha" result="effect1_dropShadow_5498_6943"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.17 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_5498_6943"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_5498_6943" result="shape"/>
</filter>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,18 +0,0 @@
<svg width="62" height="62" viewBox="0 0 62 62" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_884_3200)">
<rect x="15" y="14" width="32" height="32" rx="15" fill="white"/>
</g>
<path d="M35.19 20H26.81C23.17 20 21 22.17 21 25.81V34.19C21 35.28 21.19 36.23 21.56 37.03C22.42 38.93 24.26 40 26.81 40H35.19C38.83 40 41 37.83 41 34.19V31.9V25.81C41 22.17 38.83 20 35.19 20ZM39.37 30.5C38.59 29.83 37.33 29.83 36.55 30.5L32.39 34.07C31.61 34.74 30.35 34.74 29.57 34.07L29.23 33.79C28.52 33.17 27.39 33.11 26.59 33.65L22.85 36.16C22.63 35.6 22.5 34.95 22.5 34.19V25.81C22.5 22.99 23.99 21.5 26.81 21.5H35.19C38.01 21.5 39.5 22.99 39.5 25.81V30.61L39.37 30.5Z" fill="#292D32"/>
<defs>
<filter id="filter0_d_884_3200" x="0" y="0" width="62" height="62" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feMorphology radius="5" operator="dilate" in="SourceAlpha" result="effect1_dropShadow_884_3200"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.17 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_884_3200"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_884_3200" result="shape"/>
</filter>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -79,6 +79,8 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- privacy_screen (0.0.1):
- Flutter
- qr_code_scanner (0.2.0):
- Flutter
- MTBBarcodeScanner
@@ -127,6 +129,7 @@ DEPENDENCIES:
- open_filex (from `.symlinks/plugins/open_filex/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- privacy_screen (from `.symlinks/plugins/privacy_screen/ios`)
- qr_code_scanner (from `.symlinks/plugins/qr_code_scanner/ios`)
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
@@ -186,6 +189,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
privacy_screen:
:path: ".symlinks/plugins/privacy_screen/ios"
qr_code_scanner:
:path: ".symlinks/plugins/qr_code_scanner/ios"
sentry_flutter:
@@ -225,6 +230,7 @@ SPEC CHECKSUMS:
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
privacy_screen: 1a131c052ceb3c3659934b003b0d397c2381a24e
qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
SDWebImage: 750adf017a315a280c60fde706ab1e552a3ae4e9

View File

@@ -41,6 +41,8 @@ class InvalidStateError extends AssertionError {
class KeyDerivationError extends Error {}
class LoginKeyDerivationError extends Error {}
class SrpSetupNotCompleteError extends Error {}
class AuthenticatorKeyNotFound extends Error {}

1
lib/l10n/arb/app_ar.arb Normal file
View File

@@ -0,0 +1 @@
{}

View File

@@ -1,5 +1,6 @@
{
"account": "Konto",
"unlock": "Entsperren",
"recoveryKey": "Wiederherstellungsschlüssel",
"counterAppBarTitle": "Zähler",
"@counterAppBarTitle": {
@@ -60,6 +61,7 @@
"contactSupport": "Support kontaktieren",
"rateUsOnStore": "Bewerte uns auf {storeName}",
"blog": "Blog",
"merchandise": "Merchandise",
"verifyPassword": "Passwort überprüfen",
"pleaseWait": "Bitte warten...",
"generatingEncryptionKeysTitle": "Generierung von Verschlüsselungsschlüsseln...",
@@ -75,12 +77,14 @@
"changePassword": "Passwort ändern",
"data": "Datei",
"importCodes": "Codes importieren",
"importTypePlainText": "Klartext",
"importTypeEnteEncrypted": "ente verschlüsselt exportieren",
"passwordForDecryptingExport": "Passwort um den Export zu entschlüsseln",
"passwordEmptyError": "Passwort kann nicht leer sein",
"importFromApp": "Importiere Codes von {appName}",
"importGoogleAuthGuide": "Exportiere deine Accounts von Google Authenticator zu einem QR-Code, durch die \"Konten übertragen\" Option. Scanne den QR-Code danach mit einem anderen Gerät.\n\nTipp: Du kannst die Kamera eines Laptops verwenden, um ein Foto den dem QR-Code zu erstellen.",
"importSelectJsonFile": "Wähle eine JSON-Datei",
"importSelectAppExport": "{appName} Exportdatei auswählen",
"importEnteEncGuide": "Wähle die von ente exportierte, verschlüsselte JSON-Datei",
"exportCodes": "Codes exportieren",
"importLabel": "Importieren",
@@ -92,6 +96,7 @@
"authToViewYourRecoveryKey": "Bitte authentifizieren um ihren Wiederherstellungscode anzuzeigen",
"authToChangeYourEmail": "Bitte authentifizieren um ihre Emailadresse zu ändern",
"authToChangeYourPassword": "Bitte authentifizieren um ihr Passwort zu ändern",
"authToViewSecrets": "Bitte authentifizieren Sie sich, um ihren Wiederherstellungscode anzuzeigen",
"ok": "Ok",
"cancel": "Abbrechen",
"yes": "Ja",
@@ -177,6 +182,7 @@
"enterDetailsManually": "Details manuell hinzufügen",
"edit": "Editieren",
"copiedToClipboard": "In die Zwischenablage kopieren",
"copiedNextToClipboard": "Nächster Code wurde in die Zwischenablage kopiert",
"error": "Fehler",
"recoveryKeyCopiedToClipboard": "Wiederherstellungsschlüssel in die Zwischenablage kopiert",
"recoveryKeyOnForgotPassword": "Sollten sie ihr Passwort vergessen, dann ist dieser Schlüssel die einzige Möglichkeit ihre Daten wiederherzustellen.",
@@ -309,7 +315,68 @@
"incorrectRecoveryKey": "Falscher Wiederherstellungs-Schlüssel",
"theRecoveryKeyYouEnteredIsIncorrect": "Der eingegebene Wiederherstellungs-Schlüssel ist ungültig",
"enterPassword": "Passwort eingeben",
"selectExportFormat": "Exportformat auswählen",
"encrypted": "Verschlüsselt",
"plainText": "Klartext",
"passwordToEncryptExport": "Passwort zum Verschlüssen des Exports",
"export": "Export",
"useOffline": "Ohne Backup verwenden",
"signInToBackup": "Melde dich an, um deine Codes zu sichern",
"singIn": "Anmelden",
"showLargeIcons": "Große Symbole anzeigen",
"shouldHideCode": "Codes ausblenden",
"doubleTapToViewHiddenCode": "Sie können auf einen Eintrag doppelt tippen, um den Code anzuzeigen",
"minimizeAppOnCopy": "Beim Kopieren App minimieren",
"editCodeAuthMessage": "Authentifizieren, um Code zu bearbeiten",
"deleteCodeAuthMessage": "Authentifizieren, um Code zu löschen",
"showQRAuthMessage": "Authentifizieren, um QR-Code anzuzeigen"
"showQRAuthMessage": "Authentifizieren, um QR-Code anzuzeigen",
"confirmAccountDeleteTitle": "Kontolöschung bestätigen",
"androidBiometricHint": "Identität bestätigen",
"@androidBiometricHint": {
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
},
"androidBiometricNotRecognized": "Nicht erkannt. Versuchen Sie es erneut.",
"@androidBiometricNotRecognized": {
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
},
"androidBiometricSuccess": "Erfolgreich",
"@androidBiometricSuccess": {
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
},
"androidCancelButton": "Abbrechen",
"@androidCancelButton": {
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
},
"androidSignInTitle": "Authentifizierung erforderlich",
"@androidSignInTitle": {
"description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
},
"androidBiometricRequiredTitle": "Biometrie erforderlich",
"@androidBiometricRequiredTitle": {
"description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters."
},
"androidDeviceCredentialsRequiredTitle": "Geräteanmeldeinformationen erforderlich",
"@androidDeviceCredentialsRequiredTitle": {
"description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters."
},
"androidDeviceCredentialsSetupDescription": "Geräteanmeldeinformationen erforderlich",
"@androidDeviceCredentialsSetupDescription": {
"description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side."
},
"goToSettings": "Zu den Einstellungen",
"@goToSettings": {
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
},
"iOSOkButton": "OK",
"@iOSOkButton": {
"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": "Keine Internetverbindung",
"pleaseCheckYourInternetConnectionAndTryAgain": "Bitte überprüfe deine Internetverbindung und versuche es erneut.",
"signOutFromOtherDevices": "Von anderen Geräten abmelden",
"signOutOtherBody": "Falls du denkst, dass jemand dein Passwort kennen könnte, kannst du alle anderen Geräte von deinem Account abmelden.",
"signOutOtherDevices": "Andere Geräte abmelden",
"doNotSignOut": "Nicht abmelden",
"hearUsWhereTitle": "Wie hast du von Ente erfahren? (optional)",
"hearUsExplanation": "Wir tracken keine App-Installationen. Es würde uns jedoch helfen, wenn du uns mitteilst, wie du von uns erfahren hast!"
}

View File

@@ -84,22 +84,25 @@
"importFromApp": "Import codes from {appName}",
"importGoogleAuthGuide": "Export your accounts from Google Authenticator to a QR code using the \"Transfer Accounts\" option. Then using another device, scan the QR code.\n\nTip: You can use your laptop's webcam to take a picture of the QR code.",
"importSelectJsonFile": "Select JSON file",
"importSelectAppExport": "Select {appName} export file",
"importEnteEncGuide": "Select the encrypted JSON file exported from ente",
"importRaivoGuide": "Use the \"Export OTPs to Zip archive\" option in Raivo's Settings.\n\nExtract the zip file and import the JSON file.",
"importBitwardenGuide": "Use the \"Export vault\" option within Bitwarden Tools and import the unencrypted JSON file.",
"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",
"exportCodes": "Export codes",
"importLabel": "Import",
"importInstruction": "Please select a file that contains a list of your codes in the following format",
"importCodeDelimiterInfo": "The codes can be separated by a comma or a new line",
"selectFile": "Select file",
"emailVerificationToggle": "Email verification",
"emailVerificationEnableWarning": "If you are storing the 2FA to your email with us, turning on email verification could result in a deadlock. If you are locked out of one service, you might not be able to log in to the other.",
"emailVerificationEnableWarning": "To avoid getting locked out of your account, be sure to store a copy of your email 2FA outside of Ente Auth before enabling email verification.",
"authToChangeEmailVerificationSetting": "Please authenticate to change email verification",
"authToViewYourRecoveryKey": "Please authenticate to view your recovery key",
"authToChangeYourEmail": "Please authenticate to change your email",
"authToChangeYourPassword": "Please authenticate to change your password",
"authToViewSecrets": "Please authenticate to view your secrets",
"authToInitiateSignIn": "Please authenticate to initiate sign in for backup.",
"ok": "Ok",
"cancel": "Cancel",
"yes": "Yes",
@@ -129,6 +132,7 @@
"faq_q_5": "How can I enable FaceID lock in ente Auth",
"faq_a_5": "You can enable FaceID lock under Settings → Security → Lockscreen.",
"somethingWentWrongMessage": "Something went wrong, please try again",
"leaveFamily": "Leave family",
"leaveFamilyMessage": "Are you sure that you want to leave the family plan?",
"inFamilyPlanMessage": "You are on a family plan!",
@@ -332,6 +336,7 @@
"offlineModeWarning": "You have chosen to proceed without backups. Please take manual backups to make sure your codes are safe.",
"showLargeIcons": "Show large icons",
"shouldHideCode": "Hide codes",
"doubleTapToViewHiddenCode" : "You can double tap on an entry to view code",
"focusOnSearchBar": "Focus search on app start",
"confirmUpdatingkey": "Are you sure you want to update the secret key?",
"minimizeAppOnCopy": "Minimize app on copy",
@@ -340,13 +345,6 @@
"showQRAuthMessage": "Authenticate to show QR code",
"confirmAccountDeleteTitle": "Confirm account deletion",
"confirmAccountDeleteMessage": "This account is linked to other ente apps, if you use any.\n\nYour uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted.",
"reminderText": "Reminder",
"reminderPopupBody": "Please delete the screenshot before resuming any photo cloud sync",
"invalidQrCodeText": "Invalid QR code",
"googleAuthImagePopupBody": "Please turn off all photo cloud sync from all apps, including iCloud, Google Photo, OneDrive, etc. \nAlso if you have a second smartphone, it is safer to import by scanning QR code.",
"importGoogleAuthImageButtonText": "Import from image",
"unableToRecognizeQrCodeText": "Unable to recognize a valid code from the uploaded image",
"qrCodeImageNotSelectedText": "Qr code image not selected",
"androidBiometricHint": "Verify identity",
"@androidBiometricHint": {
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
@@ -399,5 +397,12 @@
"@iOSOkButton": {
"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."
},
"parsingErrorText": "Error while parsing Google Auth QR code"
"noInternetConnection": "No internet connection",
"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!"
}

View File

@@ -1 +1,116 @@
{}
{
"account": "ანგარიში",
"unlock": "განბლოკვა",
"recoveryKey": "აღდგენის კოდი",
"counterAppBarTitle": "მრიცხველზე დაფუძნებული",
"@counterAppBarTitle": {
"description": "Text shown in the AppBar of the Counter Page"
},
"onBoardingBody": "შექმენით თქვენი ორმხრივი აუთენთიფიკაციის კოდების სარეზერვო ასლი უსაფრთხოდ",
"onBoardingGetStarted": "დაწყება",
"setupFirstAccount": "დააყენეთ თქვენი პირველი ანგარიში",
"importScanQrCode": "QR კოდის დასკანერება",
"qrCode": "QR კოდი",
"importEnterSetupKey": "შეიყვანეთ დაყენების კოდი",
"importAccountPageTitle": "შეიყვანეთ ანგარიშის მონაცემები",
"secretCanNotBeEmpty": "გასაღების ველი არ შეიძლება ცარიელი იყოს",
"bothIssuerAndAccountCanNotBeEmpty": "მომწოდებლისა და ანგარიშის ველი არ შეიძლება ცარიელი იყოს",
"incorrectDetails": "არასწორი მონაცემები",
"pleaseVerifyDetails": "გთხოვთ, გადაამოწმოთ მონაცემები და სცადოთ ხელახლა",
"codeIssuerHint": "მომწოდებელი",
"codeSecretKeyHint": "გასაღები",
"codeAccountHint": "ანგარიში (you@domain.com)",
"accountKeyType": "გასაღების ტიპი",
"sessionExpired": "სესიის დრო ამოიწურა",
"@sessionExpired": {
"description": "Title of the dialog when the users current session is invalid/expired"
},
"pleaseLoginAgain": "გთხოვთ, გაიაროთ ავტორიზაცია ხელახლა",
"loggingOut": "მიმდინარეობს გამოსვლა...",
"timeBasedKeyType": "დროზე დაფუძნებული (TOTP)",
"counterBasedKeyType": "მრიცხველზე დაფუძნებული (HOTP)",
"saveAction": "შენახვა",
"nextTotpTitle": "შემდეგი",
"deleteCodeTitle": "გსურთ კოდის წაშლა?",
"deleteCodeMessage": "დარწმუნებული ხართ რომ გსურთ ამ კოდის წაშლა? ამ მოქმედების გაუქმება შეუძლებელია.",
"viewLogsAction": "აღრიცხვის ფაილების ნახვა",
"sendLogsDescription": "თქვენი პრობლემის აღმოსაფხვრელად, ეს ქმედება გააგზავნის აღრიცხვის ფაილებს. მიუხედავად იმისა, რომ ჩვენ ვიღებთ უსაფრთხოების ზომებს, რათა სენსიტიური ინფორმაცია არ მოხვდეს აღრიცხვის ფაილებში, გაგზავნამდე, გირჩევთ, გადახედოთ აღრიცხვის ფაილებს.",
"preparingLogsTitle": "მიმდინარეობს აღრიცხვის ფაილების მზადება...",
"emailLogsTitle": "აღრიცხვის ფაილების ელექტრონული ფოსტით გაგზავნა",
"emailLogsMessage": "გთხოვთ, გამოაგზავნოთ აღრიცხვის ფაილები {email}-ზე",
"@emailLogsMessage": {
"placeholders": {
"email": {
"type": "String"
}
}
},
"copyEmailAction": "ელექტრონული ფოსტის დაკოპირება",
"exportLogsAction": "აღრიცხვის ფაილების ექსპორტირება",
"reportABug": "პრობლემის შესახებ შეტყობინება",
"crashAndErrorReporting": "აპლიკაციის ხარვეზის & პრობლემის შეტყობინება",
"reportBug": "პრობლემის შეტყობინება",
"emailUsMessage": "გთხოვთ, მოგვწეროთ ელექტრონულ ფოსტაზე {email}",
"@emailUsMessage": {
"placeholders": {
"email": {
"type": "String"
}
}
},
"contactSupport": "მხარდაჭერის გუნდთან დაკავშირება",
"rateUsOnStore": "შეგვაფასეთ {storeName}-ზე",
"blog": "ბლოგი",
"merchandise": "მერჩანტი",
"verifyPassword": "პაროლის დასტური",
"pleaseWait": "გთხოვთ, დაელოდოთ...",
"generatingEncryptionKeysTitle": "მიმდინარეობს დაშიფრვის გასაღებების გენერირება...",
"recreatePassword": "პაროლის ხელახლა შექმნა",
"incorrectPasswordTitle": "არასწორი პაროლი",
"welcomeBack": "კეთილი იყოს თქვენი დაბრუნება!",
"madeWithLoveAtPrefix": "შეიქმნა ❤️ ",
"changeEmail": "ელექტრონული ფოსტის შეცვლა",
"changePassword": "პაროლის შეცვლა",
"data": "მონაცემები",
"importCodes": "კოდების იმპორტირება",
"importTypePlainText": "სტანდარტული ტექსტი",
"importTypeEnteEncrypted": "ente დაშიფრული ექსპორტი",
"passwordForDecryptingExport": "ექსპორტის გაშიფრვის პაროლი",
"passwordEmptyError": "პაროლის ველი არ შეიძლება იყოს ცარიელი",
"emailVerificationToggle": "ელექტრონული ფოსტის ვერიფიკაცია",
"cancel": "გაუქმება",
"yes": "დიახ",
"no": "არა",
"email": "ელექტრონული ფოსტა",
"support": "მხარდაჭერა",
"general": "ზოგადი",
"settings": "პარამეტრები",
"copied": "დაკოპირებულია",
"pleaseTryAgain": "გთხოვთ, სცადოთ ხელახლა",
"existingUser": "არსებული მომხმარებელი",
"delete": "წაშლა",
"androidBiometricNotRecognized": "ამოცნობა ვერ მოხერხდა. გთხოვთ, სცადოთ ხელახლა.",
"@androidBiometricNotRecognized": {
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
},
"androidBiometricSuccess": "წარმატებით",
"@androidBiometricSuccess": {
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
},
"androidCancelButton": "გაუქმება",
"@androidCancelButton": {
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
},
"androidSignInTitle": "საჭიროა აუთენთიფიკაცია",
"@androidSignInTitle": {
"description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
},
"noInternetConnection": "ინტერნეტთან კავშირი არ არის",
"pleaseCheckYourInternetConnectionAndTryAgain": "გთხოვთ, შეამოწმოთ თქვენი ინტერნეტ კავშირი და სცადოთ ხელახლა.",
"signOutFromOtherDevices": "ყველა მოწყობილობიდან გამოსვლა",
"signOutOtherBody": "თუ ფიქრობთ, რომ ვინმემ შესაძლოა იცოდეს თქვენი პაროლი, შეგიძლიათ ყველა მოწყობილობაზე იძულებითი გამოსვლა, რომელიც იყენებს თქვენს ანგარიშს.",
"signOutOtherDevices": "სხვა მოწყობილობებიდან გამოსვლა",
"doNotSignOut": "არ მოხდეს გამოსვლა",
"hearUsWhereTitle": "როგორ შეიტყვეთ Ente-ს შესახებ? (არასავალდებულო)",
"hearUsExplanation": "ჩვენ არ ვაკონტროლებთ აპლიკაციის ინსტალაციას. სასარგებლო იქნებოდა, თუ გვეტყოდით, სად გვიპოვეთ!"
}

View File

@@ -1,5 +1,6 @@
{
"account": "Account",
"unlock": "Ontgrendelen",
"recoveryKey": "Herstelsleutel",
"counterAppBarTitle": "Teller",
"@counterAppBarTitle": {
@@ -83,9 +84,12 @@
"importFromApp": "Importeer codes van {appName}",
"importGoogleAuthGuide": "Exporteer uw accounts van Google Authenticator naar een QR-code met behulp van de optie \"Transfer Accounts\". Met een ander apparaat scan je de QR-code.\n\nTip: Je kunt de webcam van je laptop gebruiken om een foto van de QR-code te maken.",
"importSelectJsonFile": "Selecteer JSON bestand",
"importSelectAppExport": "Selecteer {appName} exportbestand",
"importEnteEncGuide": "Selecteer het versleutelde JSON-bestand dat vanuit ente geëxporteerd is",
"importRaivoGuide": "Gebruik de optie \"Export OTPs to Zip archive\" in Raivo's instellingen.\n\nPak het zip-bestand uit en importeer het JSON-bestand.",
"importBitwardenGuide": "Gebruik de optie \"Exporteer kluis\" binnen Bitwarden Tools en importeer het niet-versleutelde JSON-bestand.",
"importAegisGuide": "Gebruik de optie \"Exporteer de kluis\" in de instellingen van Aegis.\n\nAls uw kluis is versleuteld, moet u het wachtwoord invoeren om de kluis te ontsleutelen.",
"import2FasGuide": "Gebruik de optie \"Instellingen->Backup -Export\" in 2FAS.\n\nAls uw back-up is versleuteld, moet u het wachtwoord invoeren om de back-up te ontsleutelen",
"exportCodes": "Codes exporteren",
"importLabel": "Importeren",
"importInstruction": "Selecteer een bestand dat een lijst van uw codes in de volgende indeling bevat",
@@ -97,6 +101,8 @@
"authToViewYourRecoveryKey": "Graag verifiëren om uw herstelsleutel te bekijken",
"authToChangeYourEmail": "Graag verifiëren om je e-mailadres te wijzigen",
"authToChangeYourPassword": "Graag verifiëren om je wachtwoord te wijzigen",
"authToViewSecrets": "Graag verifiëren om uw herstelsleutel te bekijken",
"authToInitiateSignIn": "Verifiëren om in te kunnen loggen voor back-up.",
"ok": "Oké",
"cancel": "Annuleer",
"yes": "Ja",
@@ -329,6 +335,7 @@
"offlineModeWarning": "Je hebt ervoor gekozen om verder te gaan zonder back-ups. Neem handmatige back-ups om ervoor te zorgen dat jouw codes veilig zijn.",
"showLargeIcons": "Grote iconen",
"shouldHideCode": "Verberg codes",
"doubleTapToViewHiddenCode": "Je kunt dubbel klikken op een item om code te bekijken",
"focusOnSearchBar": "Focus zoekveld na starten app",
"confirmUpdatingkey": "Weet u zeker dat u de geheime sleutel wilt bijwerken?",
"minimizeAppOnCopy": "Na kopiëren app minimaliseren",
@@ -336,5 +343,65 @@
"deleteCodeAuthMessage": "Authenticeren om code te verwijderen",
"showQRAuthMessage": "Authenticeren om QR-code te tonen",
"confirmAccountDeleteTitle": "Account verwijderen bevestigen",
"confirmAccountDeleteMessage": "Dit account is gekoppeld aan andere ente apps, als je er gebruik van maakt.\n\nJe geüploade gegevens worden in alle ente apps gepland voor verwijdering, en je account wordt permanent verwijderd voor alle ente diensten."
"confirmAccountDeleteMessage": "Dit account is gekoppeld aan andere ente apps, als je er gebruik van maakt.\n\nJe geüploade gegevens worden in alle ente apps gepland voor verwijdering, en je account wordt permanent verwijderd voor alle ente diensten.",
"androidBiometricHint": "Identiteit verifiëren",
"@androidBiometricHint": {
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
},
"androidBiometricNotRecognized": "Niet herkend. Probeer het opnieuw.",
"@androidBiometricNotRecognized": {
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
},
"androidBiometricSuccess": "Succes",
"@androidBiometricSuccess": {
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
},
"androidCancelButton": "Annuleren",
"@androidCancelButton": {
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
},
"androidSignInTitle": "Verificatie vereist",
"@androidSignInTitle": {
"description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
},
"androidBiometricRequiredTitle": "Biometrische verificatie vereist",
"@androidBiometricRequiredTitle": {
"description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters."
},
"androidDeviceCredentialsRequiredTitle": "Apparaatgegevens vereist",
"@androidDeviceCredentialsRequiredTitle": {
"description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters."
},
"androidDeviceCredentialsSetupDescription": "Apparaatgegevens vereist",
"@androidDeviceCredentialsSetupDescription": {
"description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side."
},
"goToSettings": "Ga naar instellingen",
"@goToSettings": {
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
},
"androidGoToSettingsDescription": "Biometrische verificatie is niet ingesteld op uw apparaat. Ga naar 'Instellingen > Beveiliging' om biometrische verificatie toe te voegen.",
"@androidGoToSettingsDescription": {
"description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side."
},
"iOSLockOut": "Biometrische verificatie is uitgeschakeld. Vergrendel en ontgrendel uw scherm om het in te schakelen.",
"@iOSLockOut": {
"description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side."
},
"iOSGoToSettingsDescription": "Biometrische authenticatie is niet ingesteld op uw apparaat. Schakel Touch ID of Face ID in op uw telefoon.",
"@iOSGoToSettingsDescription": {
"description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side."
},
"iOSOkButton": "Oké",
"@iOSOkButton": {
"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": "Geen internetverbinding",
"pleaseCheckYourInternetConnectionAndTryAgain": "Controleer je internetverbinding en probeer het opnieuw.",
"signOutFromOtherDevices": "Afmelden bij andere apparaten",
"signOutOtherBody": "Als je denkt dat iemand je wachtwoord zou kunnen kennen, kun je alle andere apparaten die je account gebruiken dwingen om uit te loggen.",
"signOutOtherDevices": "Afmelden bij andere apparaten",
"doNotSignOut": "Niet uitloggen",
"hearUsWhereTitle": "Hoe hoorde je over Ente? (optioneel)",
"hearUsExplanation": "Wij gebruiken geen tracking. Het zou helpen als je ons vertelt waar je ons gevonden hebt!"
}

View File

@@ -1,5 +1,6 @@
{
"account": "Konto",
"unlock": "Odblokuj",
"recoveryKey": "Klucz odzyskiwania",
"counterAppBarTitle": "Licznik",
"@counterAppBarTitle": {
@@ -97,6 +98,7 @@
"authToViewYourRecoveryKey": "Proszę uwierzytelnić, aby wyświetlić swój klucz odzyskiwania",
"authToChangeYourEmail": "Proszę uwierzytelnić, aby zmienić swój adres e-mail",
"authToChangeYourPassword": "Proszę uwierzytelnić, aby zmienić hasło",
"authToViewSecrets": "Proszę uwierzytelnić, aby wyświetlić swoje sekrety",
"ok": "Ok",
"cancel": "Anuluj",
"yes": "Tak",

View File

@@ -1,5 +1,6 @@
{
"account": "Аккаунт",
"unlock": "Разблокировать",
"recoveryKey": "Ключ восстановления",
"counterAppBarTitle": "Счетчик",
"@counterAppBarTitle": {
@@ -85,6 +86,7 @@
"importSelectJsonFile": "Выбрать JSON-файл",
"importEnteEncGuide": "Выберите зашифрованный JSON-файл, экспортированный из ente",
"importRaivoGuide": "Используйте опцию «Export OTPs to Zip archive» в настройках Raivo.\n\nРаспакуйте zip-архив и импортируйте JSON-файл.",
"importBitwardenGuide": "Используйте опцию \"Экспортировать хранилище\" в Bitwarden Tools и импортируйте незашифрованный JSON файл.",
"importAegisGuide": "Используйте опцию «Экспортировать хранилище» в настройках Aegis.\n\nЕсли ваше хранилище зашифровано, то для его расшифровки потребуется ввести пароль хранилища.",
"exportCodes": "Экспортировать коды",
"importLabel": "Импорт",
@@ -97,6 +99,7 @@
"authToViewYourRecoveryKey": "Пожалуйста, авторизуйтесь для просмотра вашего ключа восстановления",
"authToChangeYourEmail": "Пожалуйста, авторизуйтесь, чтобы изменить адрес электронной почты",
"authToChangeYourPassword": "Пожалуйста, авторизуйтесь, чтобы изменить пароль",
"authToViewSecrets": "Пожалуйста, авторизуйтесь для просмотра ваших секретов",
"ok": "Ок",
"cancel": "Отменить",
"yes": "Да",
@@ -336,5 +339,59 @@
"deleteCodeAuthMessage": "Аутентификация для удаления кода",
"showQRAuthMessage": "Аутентификация для отображения QR-кода",
"confirmAccountDeleteTitle": "Подтвердить удаление аккаунта",
"confirmAccountDeleteMessage": "Эта учетная запись связана с другими приложениями ente, если вы ими пользуетесь.\n\nЗагруженные вами данные во всех приложениях ente будут запланированы к удалению, а ваша учетная запись будет удалена без возможности восстановления."
"confirmAccountDeleteMessage": "Эта учетная запись связана с другими приложениями ente, если вы ими пользуетесь.\n\nЗагруженные вами данные во всех приложениях ente будут запланированы к удалению, а ваша учетная запись будет удалена без возможности восстановления.",
"androidBiometricHint": "Подтвердите личность",
"@androidBiometricHint": {
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
},
"androidBiometricNotRecognized": "Не распознано. Попробуйте еще раз.",
"@androidBiometricNotRecognized": {
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
},
"androidBiometricSuccess": "Успех",
"@androidBiometricSuccess": {
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
},
"androidCancelButton": "Отменить",
"@androidCancelButton": {
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
},
"androidSignInTitle": "Требуется аутентификация",
"@androidSignInTitle": {
"description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
},
"androidBiometricRequiredTitle": "Требуется биометрия",
"@androidBiometricRequiredTitle": {
"description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters."
},
"androidDeviceCredentialsRequiredTitle": "Требуются учетные данные устройства",
"@androidDeviceCredentialsRequiredTitle": {
"description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters."
},
"androidDeviceCredentialsSetupDescription": "Требуются учетные данные устройства",
"@androidDeviceCredentialsSetupDescription": {
"description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side."
},
"goToSettings": "Перейдите к настройкам",
"@goToSettings": {
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
},
"androidGoToSettingsDescription": "Биометрическая аутентификация не настроена на вашем устройстве. Перейдите в \"Настройки > Безопасность\", чтобы добавить биометрическую аутентификацию.",
"@androidGoToSettingsDescription": {
"description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side."
},
"iOSLockOut": "Биометрическая аутентификация отключена. Пожалуйста, заблокируйте и разблокируйте экран, чтобы включить ее.",
"@iOSLockOut": {
"description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side."
},
"iOSGoToSettingsDescription": "Биометрическая аутентификация не настроена на вашем устройстве. Пожалуйста, включите Touch ID или Face ID на вашем телефоне.",
"@iOSGoToSettingsDescription": {
"description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side."
},
"iOSOkButton": "ОК",
"@iOSOkButton": {
"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": "Проверьте подключение к Интернету и повторите попытку."
}

View File

@@ -1,5 +1,6 @@
{
"account": "Hesabım",
"unlock": "Kilidi aç",
"recoveryKey": "Kurtarma Anahtarı",
"counterAppBarTitle": "Sayaç",
"@counterAppBarTitle": {
@@ -83,6 +84,7 @@
"importSelectJsonFile": "JSON dosyasını seçin",
"importEnteEncGuide": "Ente'den dışa aktarılan şifrelenmiş JSON dosyasını seçin",
"importRaivoGuide": "Raivo'nun ayarlarında \"OTP'leri Zip arşivine aktar\" seçeneğini kullanın.\n\nZip dosyasını çıkarın ve JSON dosyasını içe aktarın.",
"importBitwardenGuide": "Bitwarden Tools içindeki \"Kasayı dışa aktar\" seçeneğini kullanın ve şifrelenmemiş JSON dosyasını içe aktarın.",
"importAegisGuide": "Aegis'in Ayarlarında \"Kasayı dışa aktar\" seçeneğini kullanın.\n\nKasanız şifrelenmişse, kasanın şifresini çözmek için kasa parolasını girmeniz gerekecektir.",
"exportCodes": "Kodu dışa aktar",
"importLabel": "İçe aktar",
@@ -90,16 +92,19 @@
"importCodeDelimiterInfo": "Kodlar, virgülle ya da yeni bir satırla ayrılabilir",
"selectFile": "Dosya seç",
"emailVerificationToggle": "E-posta doğrulama",
"emailVerificationEnableWarning": "E-postanız için 2FA'yı bizde saklıyorsanız, e-posta doğrulamasını açmak bir kilitlenmeye neden olabilir. Bir hizmetin dışında kalırsanız, diğerine giriş yapamayabilirsiniz.",
"authToChangeEmailVerificationSetting": "E-posta doğrulamasını değiştirmek için lütfen kimlik doğrulaması yapın",
"authToViewYourRecoveryKey": "Kurtarma anahtarınızı görmek için lütfen kimliğinizi doğrulayın",
"authToChangeYourEmail": "Epostanızı değiştirmek için lütfen kimliğinizi doğrulayın",
"authToChangeYourPassword": "Şifrenizi değiştirmek için lütfen kimliğinizi doğrulayın",
"authToInitiateSignIn": "Yedekleme için giriş yapmayı başlatmak üzere lütfen kimlik doğrulaması yapın.",
"ok": "Tamam",
"cancel": "İptal Et",
"yes": "Evet",
"no": "Hayır",
"email": "E-Posta",
"support": "Destek",
"general": "Genel",
"settings": "Ayarlar",
"copied": "Kopyalandı",
"pleaseTryAgain": "Lütfen tekrar deneyin",
@@ -179,6 +184,7 @@
"enterDetailsManually": "Bilgileri elle girin",
"edit": "Düzenle",
"copiedToClipboard": "Panoya kopyalandı",
"copiedNextToClipboard": "Sonraki kod panoya kopyalandı",
"error": "Hata",
"recoveryKeyCopiedToClipboard": "Kurtarma anahtarı panoya kopyalandı",
"recoveryKeyOnForgotPassword": "Eğer şifrenizi unutursanız, verilerinizi kurtarabileceğiniz tek yol bu anahtardır.",
@@ -249,6 +255,10 @@
"privacy": "Gizlilik",
"terms": "Şartlar",
"checkForUpdates": "Güncellemeleri denetleyin",
"downloadUpdate": "İndir",
"criticalUpdateAvailable": "Kritik güncelleme mevcut",
"updateAvailable": "Güncelleme mevcut",
"update": "Güncelle",
"checking": "Denetleniyor...",
"youAreOnTheLatestVersion": "En son sürümdesiniz",
"warning": "Uyarı",
@@ -313,7 +323,69 @@
"plainText": "Düz metin",
"passwordToEncryptExport": "Dışa aktarımı şifrelemek için parola",
"export": "Dışa aktar",
"useOffline": "Yedekleme olmadan kullan",
"signInToBackup": "Kodlarınızı yedeklemek için giriş yapın",
"singIn": "Giriş yap",
"sigInBackupReminder": "Geri yükleyebileceğiniz bir yedeğiniz olduğundan emin olmak için lütfen kodlarınızı dışa aktarın.",
"offlineModeWarning": "Yedekleme yapmadan devam etmeyi seçtiniz. Kodlarınızın güvende olduğundan emin olmak için lütfen manuel yedekleme yapın.",
"showLargeIcons": "Büyük simgeler göster",
"shouldHideCode": "Kodları gizle",
"doubleTapToViewHiddenCode": "Kodu görüntülemek için bir girdiye çift dokunabilirsiniz",
"focusOnSearchBar": "Uygulama başladığında arama bölümüne odaklan",
"minimizeAppOnCopy": "Kopyalarken uygulamayı küçült",
"editCodeAuthMessage": "Kodu düzenlemek için doğrulama yapın",
"deleteCodeAuthMessage": "Kodu silmek için doğrulama yapın",
"showQRAuthMessage": "QR kodunu göstermek için doğrulama yapın"
"showQRAuthMessage": "QR kodunu göstermek için doğrulama yapın",
"confirmAccountDeleteTitle": "Hesap silme işlemini onayla",
"confirmAccountDeleteMessage": "Bu hesap, eğer kullanıyorsanız, diğer ente uygulamalarıyla bağlantılıdır.\n\nTüm ente uygulamalarında yüklediğiniz veriler silinmek üzere programlanacak ve hesabınız kalıcı olarak silinecektir.",
"androidBiometricHint": "Kimliği doğrula",
"@androidBiometricHint": {
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
},
"androidBiometricNotRecognized": "Tanınmadı. Tekrar deneyin.",
"@androidBiometricNotRecognized": {
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
},
"androidBiometricSuccess": "Başarılı",
"@androidBiometricSuccess": {
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
},
"androidCancelButton": "İptal et",
"@androidCancelButton": {
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
},
"androidSignInTitle": "Kimlik doğrulaması gerekli",
"@androidSignInTitle": {
"description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
},
"androidBiometricRequiredTitle": "Biyometrik gerekli",
"@androidBiometricRequiredTitle": {
"description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters."
},
"androidDeviceCredentialsRequiredTitle": "Cihaz kimlik bilgileri gerekli",
"@androidDeviceCredentialsRequiredTitle": {
"description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters."
},
"goToSettings": "Ayarlara git",
"@goToSettings": {
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
},
"androidGoToSettingsDescription": "Biyometrik kimlik doğrulama cihazınızda ayarlanmamış. Biyometrik kimlik doğrulama eklemek için 'Ayarlar > Güvenlik' bölümüne gidin.",
"@androidGoToSettingsDescription": {
"description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side."
},
"iOSLockOut": "Biyometrik kimlik doğrulama devre dışı. Etkinleştirmek için lütfen ekranınızı kilitleyin ve kilidini açın.",
"@iOSLockOut": {
"description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side."
},
"iOSGoToSettingsDescription": "Cihazınızda biyometrik kimlik doğrulama ayarlanmamış. Lütfen telefonunuzda Touch ID veya Face ID'yi etkinleştirin.",
"@iOSGoToSettingsDescription": {
"description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side."
},
"iOSOkButton": "Tamam",
"@iOSOkButton": {
"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": "İnternet bağlantısı yok",
"pleaseCheckYourInternetConnectionAndTryAgain": "Lütfen internet bağlantınızı kontrol edin ve yeniden deneyin."
}

View File

@@ -1,14 +1,16 @@
{
"account": "Tài khoản",
"unlock": "Mở khóa",
"recoveryKey": "Khóa khôi phục",
"counterAppBarTitle": "Bộ Đếm",
"@counterAppBarTitle": {
"description": "Text shown in the AppBar of the Counter Page"
},
"onBoardingBody": "Bảo mật mã 2FA của bạn",
"onBoardingBody": "Sao lưu an toàn mã 2FA của bạn",
"onBoardingGetStarted": "Bắt đầu",
"setupFirstAccount": "Thiết lập tài khoản đầu tiên của bạn",
"importScanQrCode": "Quét mã QR",
"qrCode": "Mã QR",
"importEnterSetupKey": "Nhập khóa thiết lập",
"importAccountPageTitle": "Nhập chi tiết tài khoản",
"secretCanNotBeEmpty": "Khoá bí mật không được để trống",
@@ -46,7 +48,7 @@
"copyEmailAction": "Sao chép email",
"exportLogsAction": "Xuất nhật ký",
"reportABug": "Báo cáo lỗi",
"crashAndErrorReporting": "Báo cáo sự cố lỗi",
"crashAndErrorReporting": "Báo cáo sự cố & lỗi",
"reportBug": "Báo lỗi",
"emailUsMessage": "Vui lòng gửi email cho chúng tôi tại {email}",
"@emailUsMessage": {
@@ -57,11 +59,14 @@
}
},
"contactSupport": "Liên hệ hỗ trợ",
"rateUsOnStore": "Đánh giá chúng tôi trên {storeName}",
"blog": "Blog",
"merchandise": "Hàng hóa",
"verifyPassword": "Xác nhận mật khẩu",
"pleaseWait": "Vui lòng chờ...",
"generatingEncryptionKeysTitle": "Đang tạo khóa mã hóa...",
"recreatePassword": "Tạo lại mật khẩu",
"recreatePasswordMessage": "Thiết bị hiện tại không đủ mạnh để xác minh mật khẩu của bạn, vì vậy chúng tôi cần tạo lại mật khẩu một lần theo cách hoạt động với tất cả các thiết bị. \n\nVui lòng đăng nhập bằng khóa khôi phục và tạo lại mật khẩu của bạn (bạn có thể sử dụng lại cùng một mật khẩu nếu muốn).",
"recreatePasswordMessage": "Thiết bị hiện tại không đủ mạnh để xác minh mật khẩu của bạn, vì vậy chúng tôi cần tạo lại mật khẩu một lần theo cách hoạt động với tất cả các thiết bị.\n\nVui lòng đăng nhập bằng khóa khôi phục và tạo lại mật khẩu của bạn (bạn có thể sử dụng lại cùng một mật khẩu nếu muốn).",
"useRecoveryKey": "Dùng khóa khôi phục",
"incorrectPasswordTitle": "Mật khẩu không đúng",
"welcomeBack": "Chào mừng trở lại!",
@@ -72,19 +77,39 @@
"changePassword": "Thay đổi mật khẩu",
"data": "Dữ liệu",
"importCodes": "Nhập mã",
"importTypePlainText": "Văn bản thuần",
"importTypeEnteEncrypted": "xuất ente đã mã hóa",
"passwordForDecryptingExport": "Mật khẩu để giải mã xuất",
"passwordEmptyError": "Mật khẩu không thể để trống",
"importFromApp": "Nhập mã từ {appName}",
"importGoogleAuthGuide": "Xuất dữ liệu tài khoản của bạn từ Google Authenticator sang mã QR bằng tùy chọn \"Chuyển tài khoản\". Sau đó dùng thiết bị khác quét mã QR.",
"importSelectJsonFile": "Chọn tệp JSON",
"importSelectAppExport": "Chọn {appName} tệp dữ liệu xuất",
"importEnteEncGuide": "Chọn tệp JSON được mã hóa đã xuất từ ente",
"importRaivoGuide": "Sử dụng tùy chọn \"Xuất OTP sang lưu trữ Zip\" trong cài đặt của Raivo.",
"importBitwardenGuide": "Sử dụng tùy chọn \"Xuất vault\" trong công cụ Bitwarden và nhập tệp JSON không được mã hóa.",
"importAegisGuide": "Nếu vault của bạn được mã hóa, bạn sẽ cần nhập mật khẩu vault để giải mã vault.",
"import2FasGuide": "Sử dụng tùy chọn \"Cài đặt->Sao lưu -Xuất dữ liệu\" trong 2FAS.\n\nNếu bản sao lưu của bạn được mã hóa, bạn sẽ cần nhập mật khẩu để giải mã bản sao lưu",
"exportCodes": "Xuất mã",
"importLabel": "Nhập",
"importInstruction": "Vui lòng chọn tệp chứa danh sách mã của bạn ở định dạng sau",
"importCodeDelimiterInfo": "Các mã có thể được phân tách bằng một dấu phẩy hoặc một dòng mới",
"selectFile": "Chọn tập tin",
"emailVerificationToggle": "Email xác thực",
"emailVerificationEnableWarning": "Để tránh bị khóa tài khoản, hãy đảm bảo lưu trữ bản sao email 2FA của bạn bên ngoài Ente Auth trước khi bật xác minh email.",
"authToChangeEmailVerificationSetting": "Vui lòng xác thực để thay đổi email",
"authToViewYourRecoveryKey": "Vui lòng xác thực để xem khóa khôi phục của bạn",
"authToChangeYourEmail": "Vui lòng xác thực để thay đổi email của bạn",
"authToChangeYourPassword": "Vui lòng xác thực để thay đổi mật khẩu của bạn",
"ok": "Được rồi",
"authToViewSecrets": "Vui lòng xác thực để xem bí mật của bạn",
"authToInitiateSignIn": "Vui lòng xác thực để bắt đầu đăng nhập nhằm sao lưu.",
"ok": "Đồng ý",
"cancel": "Hủy",
"yes": "Đúng",
"no": "Không",
"email": "Thư điện tử",
"support": "Hỗ trợ",
"general": "Tổng quan",
"settings": "Cài đặt",
"copied": "\u001dĐã sao chép",
"pleaseTryAgain": "Vui lòng thử lại",
@@ -94,6 +119,18 @@
"enterYourPasswordHint": "Nhập mật khẩu của bạn",
"forgotPassword": "Quên mật khẩu",
"oops": "Rất tiếc",
"suggestFeatures": "Tính năng đề nghị",
"faq": "Câu hỏi thường gặp",
"faq_q_1": "Mức độ an toàn của ente Auth như thế nào?",
"faq_a_1": "Tất cả các mã bạn sao lưu qua ente đều được lưu trữ dưới dạng mã hóa đầu cuối. Điều này có nghĩa là chỉ bạn mới có thể truy cập mã của mình. Ứng dụng của chúng tôi là nguồn mở và mật mã của chúng tôi đã được kiểm toán độc lập.",
"faq_q_2": "Tôi có thể truy cập mã của mình trên máy tính không?",
"faq_a_2": "Bạn có thể truy cập mã của mình trên web @ auth.ente.io.",
"faq_q_3": "Làm cách nào để xóa mã?",
"faq_a_3": "Bạn có thể xóa mã bằng cách vuốt sang trái vào mục đó.",
"faq_q_4": "Tôi có thể hỗ trợ dự án này như thế nào?",
"faq_a_4": "Bạn có thể hỗ trợ sự phát triển của dự án này bằng cách đăng ký ứng dụng Ảnh @ ente.io của chúng tôi.",
"faq_q_5": "Tôi có thể bật khóa FaceID trong ente Auth như thế nào",
"faq_a_5": "Bạn có thể bật khóa FaceID trong Cài đặt → Bảo mật → Màn hình khóa.",
"somethingWentWrongMessage": "Phát hiện có lỗi, xin thử lại",
"leaveFamily": "Rời khỏi gia đình",
"leaveFamilyMessage": "Bạn có chắc chắn muốn thoát khỏi gói dành cho gia đình không?",
@@ -152,12 +189,14 @@
"enterDetailsManually": "Nhập chi tiết thủ công",
"edit": "Sửa",
"copiedToClipboard": "Đã sao chép vào khay nhớ tạm",
"copiedNextToClipboard": "Đã sao chép mã tiếp theo vào bảng nhớ tạm",
"error": "Lỗi",
"recoveryKeyCopiedToClipboard": "Đã sao chép khóa khôi phục vào bộ nhớ tạm",
"recoveryKeyOnForgotPassword": "Nếu bạn quên mật khẩu, cách duy nhất bạn có thể khôi phục dữ liệu của mình là sử dụng khóa này.",
"recoveryKeySaveDescription": "Chúng tôi không lưu trữ khóa này, vui lòng lưu khóa 24 từ này ở nơi an toàn.",
"doThisLater": "Để sau",
"saveKey": "Lưu khóa",
"back": "Quay lại",
"createAccount": "Tạo tài khoản",
"passwordStrength": "Độ mạnh mật khẩu: {passwordStrengthValue}",
"@passwordStrength": {
@@ -221,6 +260,10 @@
"privacy": "Riêng tư",
"terms": "Điều khoản",
"checkForUpdates": "Kiểm tra cập nhật",
"downloadUpdate": "Tải xuống",
"criticalUpdateAvailable": "Đã có bản cập nhật quan trọng",
"updateAvailable": "Đã có bản cập nhật",
"update": "Cập nhật",
"checking": "Đang kiểm tra...",
"youAreOnTheLatestVersion": "Bạn đang sử dụng phiên bản mới nhất",
"warning": "Cánh báo",
@@ -266,7 +309,99 @@
"terminateSession": "Chấm dứt phiên?",
"terminate": "Dừng lại",
"thisDevice": "Thiết bị này",
"toResetVerifyEmail": "Để đặt lại mật khẩu, vui lòng xác minh email của bạn trước.",
"thisEmailIsAlreadyInUse": "Email này đã được sử dụng",
"verificationFailedPleaseTryAgain": "Mã xác nhận thất bại. Vui lòng thử lại",
"yourVerificationCodeHasExpired": "Mã xác minh của bạn đã hết hạn",
"incorrectCode": "Mã không chính xác",
"sorryTheCodeYouveEnteredIsIncorrect": "Xin lỗi, mã bạn đã nhập không chính xác",
"emailChangedTo": "Thay đổi email thành {newEmail}",
"authenticationFailedPleaseTryAgain": "Xác thực lỗi, vui lòng thử lại",
"authenticationSuccessful": "Xác thực thành công!",
"twofactorAuthenticationSuccessfullyReset": "Xác thực hai bước được khôi phục thành công",
"incorrectRecoveryKey": "Khóa khôi phục không chính xác",
"theRecoveryKeyYouEnteredIsIncorrect": "Khóa khôi phục bạn đã nhập không chính xác",
"enterPassword": "Nhập mật khẩu",
"selectExportFormat": "Chọn định dạng dữ liệu xuất",
"exportDialogDesc": "Xuất dữ liệu được mã hóa sẽ được bảo vệ bằng mật khẩu bạn chọn.",
"encrypted": "Đã mã hóa",
"plainText": "Văn bản thuần",
"passwordToEncryptExport": "Mật khẩu để giải mã dữ liệu xuất",
"export": "Xuất dữ liệu",
"useOffline": "Sử dụng mà không sao lưu",
"signInToBackup": "Đăng nhập để sao lưu mã của bạn",
"singIn": "Đăng nhập",
"sigInBackupReminder": "Vui lòng xuất mã của bạn để đảm bảo rằng bạn có bản sao lưu có thể khôi phục.",
"offlineModeWarning": "Bạn đã chọn tiếp tục mà không cần sao lưu. Vui lòng sao lưu thủ công để đảm bảo mã của bạn được an toàn.",
"showLargeIcons": "Hiển thị biểu tượng lớn",
"shouldHideCode": "Ẩn mã",
"doubleTapToViewHiddenCode": "Bạn có thể nhấn đúp vào một mục để xem mã",
"focusOnSearchBar": "Mở tìm kiếm khi khởi động ứng dụng",
"confirmUpdatingkey": "Bạn có chắc chắn muốn cập nhật khóa bí mật không?",
"minimizeAppOnCopy": "Thu nhỏ khi sao chép",
"editCodeAuthMessage": "Xác minh để chỉnh sửa mã",
"deleteCodeAuthMessage": "Xác minh để xóa mã",
"showQRAuthMessage": "Xác minh để hiển thị mã QR"
"showQRAuthMessage": "Xác minh để hiển thị mã QR",
"confirmAccountDeleteTitle": "Xác nhận xóa tài khoản",
"confirmAccountDeleteMessage": "Tài khoản này được liên kết với các ứng dụng ente khác, nếu bạn sử dụng bất kỳ ứng dụng nào.\n\nDữ liệu đã tải lên của bạn, trên tất cả các ứng dụng, sẽ bị lên lịch xóa và tài khoản của bạn sẽ bị xóa vĩnh viễn.",
"androidBiometricHint": "Xác định danh tính",
"@androidBiometricHint": {
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
},
"androidBiometricNotRecognized": "Không nhận dạng được. Vui lòng thử lại.",
"@androidBiometricNotRecognized": {
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
},
"androidBiometricSuccess": "Thành công",
"@androidBiometricSuccess": {
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
},
"androidCancelButton": "Hủy",
"@androidCancelButton": {
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
},
"androidSignInTitle": "Yêu cầu xác thực",
"@androidSignInTitle": {
"description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
},
"androidBiometricRequiredTitle": "Yêu cầu sinh trắc học",
"@androidBiometricRequiredTitle": {
"description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters."
},
"androidDeviceCredentialsRequiredTitle": "Yêu cầu thông tin xác thực thiết bị",
"@androidDeviceCredentialsRequiredTitle": {
"description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters."
},
"androidDeviceCredentialsSetupDescription": "Yêu cầu thông tin xác thực thiết bị",
"@androidDeviceCredentialsSetupDescription": {
"description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side."
},
"goToSettings": "Chuyển đến cài đặt",
"@goToSettings": {
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
},
"androidGoToSettingsDescription": "Xác thực sinh trắc học chưa được thiết lập trên thiết bị của bạn. Đi tới 'Cài đặt > Bảo mật' để thêm xác thực sinh trắc học.",
"@androidGoToSettingsDescription": {
"description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side."
},
"iOSLockOut": "Xác thực sinh trắc học bị vô hiệu hóa. Vui lòng khóa và mở khóa màn hình của bạn để kích hoạt nó.",
"@iOSLockOut": {
"description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side."
},
"iOSGoToSettingsDescription": "Xác thực sinh trắc học chưa được thiết lập trên thiết bị của bạn. Vui lòng bật Touch ID hoặc Face ID trên điện thoại của bạn.",
"@iOSGoToSettingsDescription": {
"description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side."
},
"iOSOkButton": "Đồng ý",
"@iOSOkButton": {
"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": "Không có kết nối Internet",
"pleaseCheckYourInternetConnectionAndTryAgain": "Vui lòng kiểm tra kết nối internet của bạn và thử lại.",
"signOutFromOtherDevices": "Đăng xuất khỏi các thiết bị khác",
"signOutOtherBody": "Nếu bạn cho rằng ai đó có thể biết mật khẩu của mình, bạn có thể buộc đăng xuất tất cả các thiết bị khác đang sử dụng tài khoản của mình.",
"signOutOtherDevices": "Đăng xuất khỏi các thiết bị khác",
"doNotSignOut": "Không được đăng xuất",
"hearUsWhereTitle": "Bạn biết đến Ente bằng cách nào? (không bắt buộc)",
"hearUsExplanation": "Chúng tôi không theo dõi lượt cài đặt ứng dụng. Sẽ rất hữu ích nếu bạn cho chúng tôi biết nơi bạn tìm thấy chúng tôi!"
}

View File

@@ -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": "请以以下格式选择包含代码列表的文件",
@@ -100,6 +102,7 @@
"authToChangeYourEmail": "请验证以更改您的电子邮件",
"authToChangeYourPassword": "请验证以更改密码",
"authToViewSecrets": "请进行身份验证以查看您的秘密",
"authToInitiateSignIn": "请进行身份验证以启动登录进行备份。",
"ok": "好的",
"cancel": "取消",
"yes": "是",
@@ -332,6 +335,7 @@
"offlineModeWarning": "您已选择在不进行备份的情况下继续操作。请手动备份以确保您的代码安全。",
"showLargeIcons": "显示大图标",
"shouldHideCode": "隐藏代码",
"doubleTapToViewHiddenCode": "您可以双击条目来查看代码",
"focusOnSearchBar": "应用启动后聚焦搜索",
"confirmUpdatingkey": "您确定要更新此密钥吗?",
"minimizeAppOnCopy": "复制时最小化应用",
@@ -340,13 +344,6 @@
"showQRAuthMessage": "显示QR码需要身份验证",
"confirmAccountDeleteTitle": "确认删除账户",
"confirmAccountDeleteMessage": "该账户已链接到其他ente旗下的应用程序如果您使用任何相关的应用程序。\n\n您在所有ente旗下应用程序中上传的数据都将被安排删除并且您的账户将被永久删除。",
"reminderText": "提醒",
"reminderPopupBody": "请先删除屏幕截图,然后再恢复任何照片的云同步",
"invalidQrCodeText": "二维码无效",
"googleAuthImagePopupBody": "请关闭所有应用程序中的所有照片云同步,包括 iCloud、Google Photo、OneDrive 等。 \n此外如果您有第二部智能手机通过扫描二维码导入会更安全。",
"importGoogleAuthImageButtonText": "从图像导入",
"unableToRecognizeQrCodeText": "无法从上传的图像中识别有效代码",
"qrCodeImageNotSelectedText": "未选择二维码图像",
"androidBiometricHint": "验证身份",
"@androidBiometricHint": {
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
@@ -399,5 +396,12 @@
"@iOSOkButton": {
"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."
},
"parsingErrorText": "解析谷歌验证器的二维码时出错"
"noInternetConnection": "无互联网连接",
"pleaseCheckYourInternetConnectionAndTryAgain": "请检查您的互联网连接,然后重试。",
"signOutFromOtherDevices": "从其他设备退出登录",
"signOutOtherBody": "如果你认为有人可能知道你的密码,你可以强制所有使用你账户的其他设备退出登录。",
"signOutOtherDevices": "登出其他设备",
"doNotSignOut": "不要退登",
"hearUsWhereTitle": "您是如何知道Ente的 (可选的)",
"hearUsExplanation": "我们不跟踪应用程序安装情况。如果您告诉我们您是在哪里找到我们的,将会有所帮助!"
}

View File

@@ -24,6 +24,7 @@ import "package:flutter/material.dart";
import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:logging/logging.dart';
import 'package:path_provider/path_provider.dart';
import 'package:privacy_screen/privacy_screen.dart';
final _logger = Logger("main");
@@ -31,6 +32,22 @@ void main() async {
WidgetsFlutterBinding.ensureInitialized();
final savedThemeMode = await AdaptiveTheme.getThemeMode();
await _runInForeground(savedThemeMode);
await PrivacyScreen.instance.enable(
iosOptions: const PrivacyIosOptions(
enablePrivacy: true,
privacyImageName: "LaunchImage",
lockTrigger: IosLockTrigger.didEnterBackground,
),
androidOptions: const PrivacyAndroidOptions(
enableSecure: true,
),
backgroundColor: savedThemeMode == AdaptiveThemeMode.dark
? Colors.black
: Colors.white,
blurEffect: savedThemeMode == AdaptiveThemeMode.dark
? PrivacyBlurEffect.dark
: PrivacyBlurEffect.extraLight,
);
FlutterDisplayMode.setHighRefreshRate();
}

View File

@@ -24,13 +24,12 @@ import 'package:ente_auth/ui/account/ott_verification_page.dart';
import 'package:ente_auth/ui/account/password_entry_page.dart';
import 'package:ente_auth/ui/account/password_reentry_page.dart';
import 'package:ente_auth/ui/account/recovery_page.dart';
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
import 'package:ente_auth/ui/common/progress_dialog.dart';
import 'package:ente_auth/ui/home_page.dart';
import 'package:ente_auth/ui/two_factor_authentication_page.dart';
import 'package:ente_auth/ui/two_factor_recovery_page.dart';
import 'package:ente_auth/utils/crypto_util.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/email_util.dart';
import 'package:ente_auth/utils/toast_util.dart';
import "package:flutter/foundation.dart";
import 'package:flutter/material.dart';
@@ -46,9 +45,10 @@ 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;
final SRP6GroupParameters kDefaultSrpGroup = SRP6StandardGroups.rfc5054_4096;
final _dio = Network.instance.getDio();
final _enteDio = Network.instance.enteDio;
final _logger = Logger((UserService).toString());
@@ -68,12 +68,12 @@ class UserService {
}
Future<void> sendOtt(
BuildContext context,
String email, {
bool isChangeEmail = false,
bool isCreateAccountScreen = false,
bool isResetPasswordScreen = false,
}) async {
BuildContext context,
String email, {
bool isChangeEmail = false,
bool isCreateAccountScreen = false,
bool isResetPasswordScreen = false,
}) async {
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
await dialog.show();
try {
@@ -122,17 +122,16 @@ class UserService {
}
Future<void> sendFeedback(
BuildContext context,
String feedback, {
String type = "SubCancellation",
}) async {
BuildContext context,
String feedback, {
String type = "SubCancellation",
}) async {
await _dio.post(
_config.getHttpEndpoint() + "/anonymous/feedback",
data: {"feedback": feedback, "type": "type"},
);
}
Future<UserDetails> getUserDetailsV2({
bool memoryCount = false,
bool shouldCache = true,
@@ -146,9 +145,15 @@ class UserService {
);
final userDetails = UserDetails.fromMap(response.data);
if (shouldCache) {
if(userDetails.profileData != null) {
_preferences.setBool(kIsEmailMFAEnabled, userDetails.profileData!.isEmailMFAEnabled);
_preferences.setBool(kCanDisableEmailMFA, userDetails.profileData!.canDisableEmailMFA);
if (userDetails.profileData != null) {
_preferences.setBool(
kIsEmailMFAEnabled,
userDetails.profileData!.isEmailMFAEnabled,
);
_preferences.setBool(
kCanDisableEmailMFA,
userDetails.profileData!.canDisableEmailMFA,
);
}
// handle email change from different client
if (userDetails.email != _config.getEmail()) {
@@ -156,7 +161,7 @@ class UserService {
}
}
return userDetails;
} catch(e) {
} catch (e) {
_logger.warning("Failed to fetch", e);
rethrow;
}
@@ -210,15 +215,15 @@ class UserService {
//to close and only then to show the error dialog.
Future.delayed(
const Duration(milliseconds: 150),
() => showGenericErrorDialog(context: context),
() => showGenericErrorDialog(context: context),
);
rethrow;
}
}
Future<DeleteChallengeResponse?> getDeleteChallenge(
BuildContext context,
) async {
BuildContext context,
) async {
try {
final response = await _enteDio.get("/users/delete-challenge");
if (response.statusCode == 200) {
@@ -237,8 +242,9 @@ class UserService {
}
Future<void> deleteAccount(
BuildContext context,
String challengeResponse,) async {
BuildContext context,
String challengeResponse,
) async {
try {
final response = await _enteDio.delete(
"/users/delete",
@@ -258,18 +264,24 @@ class UserService {
}
}
Future<void> verifyEmail(BuildContext context, String ott, {bool
isResettingPasswordScreen = false,})
async {
Future<void> verifyEmail(
BuildContext context,
String ott, {
bool isResettingPasswordScreen = false,
}) 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) {
@@ -280,14 +292,15 @@ class UserService {
} else {
await _saveConfiguration(response);
if (Configuration.instance.getEncryptedToken() != null) {
if(isResettingPasswordScreen) {
if (isResettingPasswordScreen) {
page = const RecoveryPage();
} else {
page = const PasswordReentryPage();
}
} else {
page = const PasswordEntryPage(mode: PasswordEntryMode.set,);
page = const PasswordEntryPage(
mode: PasswordEntryMode.set,
);
}
}
Navigator.of(context).pushAndRemoveUntil(
@@ -296,7 +309,7 @@ class UserService {
return page;
},
),
(route) => route.isFirst,
(route) => route.isFirst,
);
} else {
// should never reach here
@@ -336,10 +349,10 @@ class UserService {
}
Future<void> changeEmail(
BuildContext context,
String email,
String ott,
) async {
BuildContext context,
String email,
String ott,
) async {
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
await dialog.show();
try {
@@ -431,9 +444,10 @@ class UserService {
}
Future<void> registerOrUpdateSrp(
Uint8List loginKey, {
SetKeysRequest? setKeysRequest,
}) async {
Uint8List loginKey, {
SetKeysRequest? setKeysRequest,
bool logOutOtherDevices = false,
}) async {
try {
final String username = const Uuid().v4().toString();
final SecureRandom random = _getSecureRandom();
@@ -466,14 +480,14 @@ class UserService {
);
if (response.statusCode == 200) {
final SetupSRPResponse setupSRPResponse =
SetupSRPResponse.fromJson(response.data);
SetupSRPResponse.fromJson(response.data);
final serverB =
SRP6Util.decodeBigInt(base64Decode(setupSRPResponse.srpB));
SRP6Util.decodeBigInt(base64Decode(setupSRPResponse.srpB));
// ignore: need to calculate secret to get M1, unused_local_variable
final clientS = client.calculateSecret(serverB);
final clientM = client.calculateClientEvidenceMessage();
late Response srpCompleteResponse;
if(setKeysRequest == null) {
if (setKeysRequest == null) {
srpCompleteResponse = await _enteDio.post(
"/users/srp/complete",
data: {
@@ -488,14 +502,15 @@ class UserService {
'setupID': setupSRPResponse.setupID,
'srpM1': base64Encode(SRP6Util.encodeBigInt(clientM!)),
'updatedKeyAttr': setKeysRequest.toMap(),
'logOutOtherDevices': logOutOtherDevices,
},
);
}
} else {
throw Exception("register-srp action failed");
}
} catch (e,s) {
_logger.severe("failed to register srp" ,e,s);
} catch (e, s) {
_logger.severe("failed to register srp", e, s);
rethrow;
}
}
@@ -512,133 +527,97 @@ class UserService {
}
Future<void> verifyEmailViaPassword(
BuildContext context,
SrpAttributes srpAttributes,
String userPassword,
) async {
final dialog = createProgressDialog(
context,
context.l10n.pleaseWait,
isDismissible: true,
);
await dialog.show();
BuildContext context,
SrpAttributes srpAttributes,
String userPassword,
ProgressDialog dialog,
) async {
late Uint8List keyEncryptionKey;
try {
keyEncryptionKey = await CryptoUtil.deriveKey(
utf8.encode(userPassword) as Uint8List,
CryptoUtil.base642bin(srpAttributes.kekSalt),
srpAttributes.memLimit,
srpAttributes.opsLimit,
);
final loginKey = await CryptoUtil.deriveLoginKey(keyEncryptionKey);
final Uint8List identity = Uint8List.fromList(
utf8.encode(srpAttributes.srpUserID),
);
final Uint8List salt = base64Decode(srpAttributes.srpSalt);
final Uint8List password = loginKey;
final SecureRandom random = _getSecureRandom();
_logger.finest('Start deriving key');
keyEncryptionKey = await CryptoUtil.deriveKey(
utf8.encode(userPassword) as Uint8List,
CryptoUtil.base642bin(srpAttributes.kekSalt),
srpAttributes.memLimit,
srpAttributes.opsLimit,
);
_logger.finest('keyDerivation done, derive LoginKey');
final loginKey = await CryptoUtil.deriveLoginKey(keyEncryptionKey);
final Uint8List identity = Uint8List.fromList(
utf8.encode(srpAttributes.srpUserID),
);
_logger.finest('longinKey derivation done');
final Uint8List salt = base64Decode(srpAttributes.srpSalt);
final Uint8List password = loginKey;
final SecureRandom random = _getSecureRandom();
final client = SRP6Client(
group: kDefaultSrpGroup,
digest: Digest('SHA-256'),
random: random,
);
final client = SRP6Client(
group: kDefaultSrpGroup,
digest: Digest('SHA-256'),
random: random,
);
final A = client.generateClientCredentials(salt, identity, password);
final createSessionResponse = await _dio.post(
_config.getHttpEndpoint() + "/users/srp/create-session",
data: {
"srpUserID": srpAttributes.srpUserID,
"srpA": base64Encode(SRP6Util.encodeBigInt(A!)),
},
);
final String sessionID = createSessionResponse.data["sessionID"];
final String srpB = createSessionResponse.data["srpB"];
final A = client.generateClientCredentials(salt, identity, password);
final createSessionResponse = await _dio.post(
_config.getHttpEndpoint() + "/users/srp/create-session",
data: {
"srpUserID": srpAttributes.srpUserID,
"srpA": base64Encode(SRP6Util.encodeBigInt(A!)),
},
);
final String sessionID = createSessionResponse.data["sessionID"];
final String srpB = createSessionResponse.data["srpB"];
final serverB = SRP6Util.decodeBigInt(base64Decode(srpB));
// ignore: need to calculate secret to get M1, unused_local_variable
final clientS = client.calculateSecret(serverB);
final clientM = client.calculateClientEvidenceMessage();
final response = await _dio.post(
_config.getHttpEndpoint() + "/users/srp/verify-session",
data: {
"sessionID": sessionID,
"srpUserID": srpAttributes.srpUserID,
"srpM1": base64Encode(SRP6Util.encodeBigInt(clientM!)),
},
);
if (response.statusCode == 200) {
Widget page;
final String twoFASessionID = response.data["twoFactorSessionID"];
Configuration.instance.setVolatilePassword(userPassword);
if (twoFASessionID.isNotEmpty) {
page = TwoFactorAuthenticationPage(twoFASessionID);
} else {
await _saveConfiguration(response);
if (Configuration.instance.getEncryptedToken() != null) {
await Configuration.instance.decryptSecretsAndGetKeyEncKey(
userPassword,
Configuration.instance.getKeyAttributes()!,
keyEncryptionKey: keyEncryptionKey,
);
page = const HomePage();
} else {
throw Exception("unexpected response during email verification");
}
}
await dialog.hide();
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (BuildContext context) {
return page;
},
),
(route) => route.isFirst,
);
final serverB = SRP6Util.decodeBigInt(base64Decode(srpB));
// ignore: need to calculate secret to get M1, unused_local_variable
final clientS = client.calculateSecret(serverB);
final clientM = client.calculateClientEvidenceMessage();
final response = await _dio.post(
_config.getHttpEndpoint() + "/users/srp/verify-session",
data: {
"sessionID": sessionID,
"srpUserID": srpAttributes.srpUserID,
"srpM1": base64Encode(SRP6Util.encodeBigInt(clientM!)),
},
);
if (response.statusCode == 200) {
Widget page;
final String twoFASessionID = response.data["twoFactorSessionID"];
Configuration.instance.setVolatilePassword(userPassword);
if (twoFASessionID.isNotEmpty) {
page = TwoFactorAuthenticationPage(twoFASessionID);
} else {
// should never reach here
throw Exception("unexpected response during email verification");
}
} on DioError catch (e, s) {
await dialog.hide();
if (e.response != null && e.response!.statusCode == 401) {
final dialogChoice = await showChoiceDialog(
context,
title: context.l10n.incorrectPasswordTitle,
body: context.l10n.pleaseTryAgain,
firstButtonLabel: context.l10n.contactSupport,
secondButtonLabel: context.l10n.ok,
);
if (dialogChoice!.action == ButtonAction.first) {
await sendLogs(
context,
context.l10n.contactSupport,
"support@ente.io",
postShare: () {},
await _saveConfiguration(response);
if (Configuration.instance.getEncryptedToken() != null) {
await Configuration.instance.decryptSecretsAndGetKeyEncKey(
userPassword,
Configuration.instance.getKeyAttributes()!,
keyEncryptionKey: keyEncryptionKey,
);
page = const HomePage();
} else {
throw Exception("unexpected response during email verification");
}
} else {
_logger.fine('failed to verify password', e, s);
await showErrorDialog(
context,
context.l10n.oops,
context.l10n.verificationFailedPleaseTryAgain,
);
}
} catch (e, s) {
_logger.fine('failed to verify password', e, s);
await dialog.hide();
await showErrorDialog(
context,
context.l10n.oops,
context.l10n.verificationFailedPleaseTryAgain,
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (BuildContext context) {
return page;
},
),
(route) => route.isFirst,
);
} else {
// should never reach here
throw Exception("unexpected response during email verification");
}
}
Future<void> updateKeyAttributes(KeyAttributes keyAttributes, Uint8List
loginKey,)
async {
Future<void> updateKeyAttributes(
KeyAttributes keyAttributes,
Uint8List loginKey, {
required bool logoutOtherDevices,
}) async {
try {
final setKeyRequest = SetKeysRequest(
kekSalt: keyAttributes.kekSalt,
@@ -647,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);
@@ -679,10 +658,10 @@ class UserService {
}
Future<void> verifyTwoFactor(
BuildContext context,
String sessionID,
String code,
) async {
BuildContext context,
String sessionID,
String code,
) async {
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
await dialog.show();
try {
@@ -703,7 +682,7 @@ class UserService {
return const PasswordReentryPage();
},
),
(route) => route.isFirst,
(route) => route.isFirst,
);
}
} on DioError catch (e) {
@@ -717,7 +696,7 @@ class UserService {
return const LoginPage();
},
),
(route) => route.isFirst,
(route) => route.isFirst,
);
} else {
showErrorDialog(
@@ -758,7 +737,7 @@ class UserService {
);
},
),
(route) => route.isFirst,
(route) => route.isFirst,
);
}
} on DioError catch (e) {
@@ -771,7 +750,7 @@ class UserService {
return const LoginPage();
},
),
(route) => route.isFirst,
(route) => route.isFirst,
);
} else {
showErrorDialog(
@@ -793,12 +772,12 @@ class UserService {
}
Future<void> removeTwoFactor(
BuildContext context,
String sessionID,
String recoveryKey,
String encryptedSecret,
String secretDecryptionNonce,
) async {
BuildContext context,
String sessionID,
String recoveryKey,
String encryptedSecret,
String secretDecryptionNonce,
) async {
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
await dialog.show();
String secret;
@@ -847,7 +826,7 @@ class UserService {
return const PasswordReentryPage();
},
),
(route) => route.isFirst,
(route) => route.isFirst,
);
}
} on DioError catch (e) {
@@ -860,7 +839,7 @@ class UserService {
return const LoginPage();
},
),
(route) => route.isFirst,
(route) => route.isFirst,
);
} else {
showErrorDialog(
@@ -881,13 +860,6 @@ class UserService {
}
}
Future<void> _saveConfiguration(Response response) async {
await Configuration.instance.setUserID(response.data["id"]);
if (response.data["encryptedToken"] != null) {
@@ -904,6 +876,7 @@ class UserService {
bool? canDisableEmailMFA() {
return _preferences.getBool(kCanDisableEmailMFA);
}
bool hasEmailMFAEnabled() {
return _preferences.getBool(kIsEmailMFAEnabled) ?? true;
}
@@ -918,9 +891,16 @@ class UserService {
);
_preferences.setBool(kIsEmailMFAEnabled, isEnabled);
} catch (e) {
_logger.severe("Failed to update email mfa",e);
_logger.severe("Failed to update email mfa", e);
rethrow;
}
}
}
Future<void> setRefSource(String refSource) async {
await _preferences.setString(kReferralSource, refSource);
}
String _getRefSource() {
return _preferences.getString(kReferralSource) ?? "";
}
}

View File

@@ -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(),

View File

@@ -1,11 +1,14 @@
import "package:dio/dio.dart";
import 'package:ente_auth/core/configuration.dart';
import "package:ente_auth/core/errors.dart";
import "package:ente_auth/l10n/l10n.dart";
import "package:ente_auth/models/api/user/srp.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/components/buttons/button_widget.dart";
import "package:ente_auth/utils/dialog_util.dart";
import "package:ente_auth/utils/email_util.dart";
import 'package:flutter/material.dart';
import "package:logging/logging.dart";
@@ -16,14 +19,16 @@ import "package:logging/logging.dart";
// volatile password.
class LoginPasswordVerificationPage extends StatefulWidget {
final SrpAttributes srpAttributes;
const LoginPasswordVerificationPage({Key? key, required this.srpAttributes}) : super(key: key);
const LoginPasswordVerificationPage({Key? key, required this.srpAttributes})
: super(key: key);
@override
State<LoginPasswordVerificationPage> createState() => _LoginPasswordVerificationPageState();
State<LoginPasswordVerificationPage> createState() =>
_LoginPasswordVerificationPageState();
}
class _LoginPasswordVerificationPageState extends
State<LoginPasswordVerificationPage> {
class _LoginPasswordVerificationPageState
extends State<LoginPasswordVerificationPage> {
final _logger = Logger((_LoginPasswordVerificationPageState).toString());
final _passwordController = TextEditingController();
final FocusNode _passwordFocusNode = FocusNode();
@@ -74,9 +79,7 @@ State<LoginPasswordVerificationPage> {
buttonText: context.l10n.logInLabel,
onPressedFunction: () async {
FocusScope.of(context).unfocus();
await UserService.instance.verifyEmailViaPassword(context, widget
.srpAttributes,
_passwordController.text,);
await verifyPassword(context, _passwordController.text);
},
),
floatingActionButtonLocation: fabLocation(),
@@ -84,6 +87,106 @@ State<LoginPasswordVerificationPage> {
);
}
Future<void> verifyPassword(BuildContext context, String password) async {
final dialog = createProgressDialog(
context,
context.l10n.pleaseWait,
isDismissible: true,
);
await dialog.show();
try {
await UserService.instance.verifyEmailViaPassword(
context,
widget.srpAttributes,
password,
dialog,
);
} on DioError catch (e, s) {
await dialog.hide();
if (e.response != null && e.response!.statusCode == 401) {
_logger.severe('server reject, failed verify SRP login', e, s);
await _showContactSupportDialog(
context,
context.l10n.incorrectPasswordTitle,
context.l10n.pleaseTryAgain,
);
} else {
_logger.severe('API failure during SRP login', e, s);
if (e.type == DioErrorType.other) {
await _showContactSupportDialog(
context,
context.l10n.noInternetConnection,
context.l10n.pleaseCheckYourInternetConnectionAndTryAgain,
);
} else {
await _showContactSupportDialog(
context,
context.l10n.oops,
context.l10n.verificationFailedPleaseTryAgain,
);
}
}
} catch (e, s) {
_logger.info('error during loginViaPassword', e);
await dialog.hide();
if (e is LoginKeyDerivationError) {
_logger.severe('loginKey derivation error', e, s);
// LoginKey err, perform regular login via ott verification
await UserService.instance.sendOtt(
context,
email!,
isCreateAccountScreen: true,
);
return;
} else if (e is KeyDerivationError) {
// device is not powerful enough to perform derive key
final dialogChoice = await showChoiceDialog(
context,
title: context.l10n.recreatePasswordTitle,
body: context.l10n.recreatePasswordBody,
firstButtonLabel: context.l10n.useRecoveryKey,
);
if (dialogChoice!.action == ButtonAction.first) {
await UserService.instance.sendOtt(
context,
email!,
isResetPasswordScreen: true,
);
}
return;
} else {
_logger.severe('unexpected error while verifying password', e, s);
await _showContactSupportDialog(
context,
context.l10n.oops,
context.l10n.verificationFailedPleaseTryAgain,
);
}
}
}
Future<void> _showContactSupportDialog(
BuildContext context,
String title,
String message,
) async {
final dialogChoice = await showChoiceDialog(
context,
title: title,
body: message,
firstButtonLabel: context.l10n.contactSupport,
secondButtonLabel: context.l10n.ok,
);
if (dialogChoice!.action == ButtonAction.first) {
await sendLogs(
context,
context.l10n.contactSupport,
"auth@ente.io",
postShare: () {},
);
}
}
Widget _getBody() {
return Column(
children: [
@@ -92,17 +195,22 @@ State<LoginPasswordVerificationPage> {
child: ListView(
children: [
Padding(
padding:
const EdgeInsets.only(top: 30, left: 20, right: 20),
padding: const EdgeInsets.only(top: 30, left: 20, right: 20),
child: Text(
context.l10n.enterPassword,
style: Theme.of(context).textTheme.headlineMedium,
),
),
Padding(
padding: const EdgeInsets.only(bottom: 30, left: 22, right:
20,),
child: Text(email ?? '', style: getEnteTextTheme(context).smallMuted,),
padding: const EdgeInsets.only(
bottom: 30,
left: 22,
right: 20,
),
child: Text(
email ?? '',
style: getEnteTextTheme(context).smallMuted,
),
),
Visibility(
// hidden textForm for suggesting auto-fill service for saving
@@ -133,19 +241,19 @@ State<LoginPasswordVerificationPage> {
),
suffixIcon: _passwordInFocus
? IconButton(
icon: Icon(
_passwordVisible
? Icons.visibility
: Icons.visibility_off,
color: Theme.of(context).iconTheme.color,
size: 20,
),
onPressed: () {
setState(() {
_passwordVisible = !_passwordVisible;
});
},
)
icon: Icon(
_passwordVisible
? Icons.visibility
: Icons.visibility_off,
color: Theme.of(context).iconTheme.color,
size: 20,
),
onPressed: () {
setState(() {
_passwordVisible = !_passwordVisible;
});
},
)
: null,
),
style: const TextStyle(
@@ -176,9 +284,11 @@ State<LoginPasswordVerificationPage> {
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () async {
await UserService.instance
.sendOtt(context, email!,
isResetPasswordScreen: true,);
await UserService.instance.sendOtt(
context,
email!,
isResetPasswordScreen: true,
);
},
child: Center(
child: Text(
@@ -187,9 +297,9 @@ State<LoginPasswordVerificationPage> {
.textTheme
.titleMedium!
.copyWith(
fontSize: 14,
decoration: TextDecoration.underline,
),
fontSize: 14,
decoration: TextDecoration.underline,
),
),
),
),
@@ -213,9 +323,9 @@ State<LoginPasswordVerificationPage> {
.textTheme
.titleMedium!
.copyWith(
fontSize: 14,
decoration: TextDecoration.underline,
),
fontSize: 14,
decoration: TextDecoration.underline,
),
),
),
),
@@ -229,4 +339,4 @@ State<LoginPasswordVerificationPage> {
],
);
}
}
}

View File

@@ -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 {

View File

@@ -18,72 +18,81 @@ class HomeEmptyStateWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints.tightFor(height: 800, width: 450),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
children: [
Image.asset(
"assets/wallet-front-gradient.png",
width: 200,
height: 200,
),
Text(
l10n.setupFirstAccount,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 64),
SizedBox(
width: 400,
child: OutlinedButton(
onPressed: onScanTap,
child: Text(l10n.importScanQrCode),
return SingleChildScrollView(
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints.tightFor(height: 800, width: 450),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
children: [
Image.asset(
"assets/wallet-front-gradient.png",
width: 200,
height: 200,
),
),
const SizedBox(height: 18),
SizedBox(
width: 400,
child: OutlinedButton(
onPressed: onManuallySetupTap,
child: Text(l10n.importEnterSetupKey),
Text(
l10n.setupFirstAccount,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headlineMedium,
),
),
const SizedBox(height: 54),
InkWell(
onTap: () {
routeToPage(context, ImportCodePage());
},
child: Text(
l10n.importCodes,
textAlign: TextAlign.center,
style: getEnteTextTheme(context).bodyFaint.copyWith(decoration: TextDecoration.underline),
),),
const SizedBox(height: 18),
InkWell(
onTap: () {
showModalBottomSheet<void>(
backgroundColor: Theme.of(context).colorScheme.background,
barrierColor: Colors.black87,
context: context,
builder: (context) {
return const FAQQuestionsWidget();
},
);
},
child: Text(
l10n.faq,
textAlign: TextAlign.center,
style: getEnteTextTheme(context).bodyFaint.copyWith(decoration: TextDecoration.underline),
),),
],
),
],
const SizedBox(height: 64),
SizedBox(
width: 400,
child: OutlinedButton(
onPressed: onScanTap,
child: Text(l10n.importScanQrCode),
),
),
const SizedBox(height: 18),
SizedBox(
width: 400,
child: OutlinedButton(
onPressed: onManuallySetupTap,
child: Text(l10n.importEnterSetupKey),
),
),
const SizedBox(height: 54),
InkWell(
onTap: () {
routeToPage(context, ImportCodePage());
},
child: Text(
l10n.importCodes,
textAlign: TextAlign.center,
style: getEnteTextTheme(context)
.bodyFaint
.copyWith(decoration: TextDecoration.underline),
),
),
const SizedBox(height: 18),
InkWell(
onTap: () {
showModalBottomSheet<void>(
backgroundColor:
Theme.of(context).colorScheme.background,
barrierColor: Colors.black87,
context: context,
builder: (context) {
return const FAQQuestionsWidget();
},
);
},
child: Text(
l10n.faq,
textAlign: TextAlign.center,
style: getEnteTextTheme(context)
.bodyFaint
.copyWith(decoration: TextDecoration.underline),
),
),
],
),
],
),
),
),
),

View File

@@ -0,0 +1,209 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
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';
import 'package:pointycastle/export.dart';
Future<void> show2FasImportInstruction(BuildContext context) async {
final l10n = context.l10n;
final result = await showDialogWidget(
context: context,
title: l10n.importFromApp("2FAS Authenticator"),
body: l10n.import2FasGuide,
buttons: [
ButtonWidget(
buttonType: ButtonType.primary,
labelText: l10n.importSelectAppExport("2FAS Authenticator"),
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 _pick2FasFile(context);
} else {}
}
}
Future<void> _pick2FasFile(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 _process2FasExportFile(context, path, progressDialog);
await progressDialog.hide();
if (count != null) {
await importSuccessDialog(context, count);
}
} catch (e, s) {
Logger('2FASImport').severe('exception while processing import', e, s);
await progressDialog.hide();
await showErrorDialog(
context,
context.l10n.sorry,
context.l10n.importFailureDesc,
);
}
}
Future<int?> _process2FasExportFile(
BuildContext context,
String path,
final ProgressDialog dialog,
) async {
File file = File(path);
final jsonString = await file.readAsString();
final decodedJson = jsonDecode(jsonString);
int version = (decodedJson['schemaVersion'] ?? 0) as int;
if (version != 3 && version != 4) {
await dialog.hide();
// todo: extract strings for l10n. Use same naming format as in aegis
// to avoid duplicate translation efforts.
await showErrorDialog(
context,
'Unsupported format: $version',
version == 0
? "The selected file is not a valid 2FAS Authenticator export."
: "Sorry, the app doesn't support this version of 2FAS Authenticator export",
);
return null;
}
var decodedServices = decodedJson['services'];
// https://github.com/twofas/2fas-android/blob/e97f1a1040eafaed6d5284d54d33403dff215886/data/services/src/main/java/com/twofasapp/data/services/domain/BackupContent.kt#L39
final isEncrypted = decodedJson['reference'] != null;
if (isEncrypted) {
String? password;
try {
await showTextInputDialog(
context,
title: "Enter password to decrypt 2FAS backup",
submitButtonLabel: "Submit",
isPasswordInput: true,
onSubmit: (value) async {
password = value;
},
);
if (password == null) {
await dialog.hide();
return null;
}
final content = decrypt2FasVault(decodedJson, password: password!);
decodedServices = jsonDecode(content);
} catch (e, s) {
Logger("2FASImport").warning("exception while decrypting backup", e, s);
await dialog.hide();
if (password != null) {
await showErrorDialog(
context,
"Failed to decrypt 2Fas export",
"Please check your password and try again.",
);
}
return null;
}
}
final parsedCodes = [];
for (var item in decodedServices) {
var kind = item['otp']['tokenType'];
var account = item['otp']['account'] ?? '';
var issuer = item['otp']['issuer'] ?? item['name'] ?? '';
var algorithm = item['otp']['algorithm'];
var secret = item['secret'];
var timer = item['otp']['period'];
var digits = item['otp']['digits'];
var counter = item['otp']['counter'];
// Build the OTP URL
String otpUrl;
if (kind.toLowerCase() == 'totp') {
otpUrl =
'otpauth://$kind/$issuer:$account?secret=$secret&issuer=$issuer&algorithm=$algorithm&digits=$digits&period=$timer';
} else if (kind.toLowerCase() == 'hotp') {
otpUrl =
'otpauth://$kind/$issuer:$account?secret=$secret&issuer=$issuer&algorithm=$algorithm&digits=$digits&counter=$counter';
} else {
throw Exception('Invalid OTP type');
}
parsedCodes.add(Code.fromRawData(otpUrl));
}
for (final code in parsedCodes) {
await CodeStore.instance.addCode(code, shouldSync: false);
}
unawaited(AuthenticatorService.instance.onlineSync());
int count = parsedCodes.length;
return count;
}
String decrypt2FasVault(dynamic data, {required String password}) {
int ITERATION_COUNT = 10000;
int KEY_SIZE = 256;
final String encryptedServices = data["servicesEncrypted"];
var split = encryptedServices.split(":");
final encryptedData = base64.decode(split[0]);
final salt = base64.decode(split[1]);
final iv = base64.decode(split[2]);
// derive 256 key using PBKDF2WithHmacSHA256 and 10000 iterations and above salt
final pbkdf2 = PBKDF2KeyDerivator(HMac(SHA256Digest(), 64));
final params = Pbkdf2Parameters(
salt,
ITERATION_COUNT,
KEY_SIZE ~/ 8,
);
pbkdf2.init(params);
Uint8List key = Uint8List(KEY_SIZE ~/ 8);
pbkdf2.deriveKey(Uint8List.fromList(utf8.encode(password)), 0, key, 0);
final decrypted = decrypt(key, iv, encryptedData);
final utf8Decode = utf8.decode(decrypted);
return utf8Decode;
}
Uint8List decrypt(Uint8List key, Uint8List iv, Uint8List data) {
final cipher = GCMBlockCipher(AESEngine())
..init(
false,
AEADParameters(
KeyParameter(key),
128,
iv,
Uint8List.fromList(<int>[]),
),
);
final dbBytes = cipher.process(data);
return dbBytes;
}

View File

@@ -1,268 +0,0 @@
import 'dart:io';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/models/code.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/google_auth_import.dart';
import 'package:ente_auth/ui/settings/data/import/qr_scanner_overlay.dart';
import 'package:ente_auth/utils/toast_util.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart';
import 'package:logging/logging.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
class QrScanner extends StatefulWidget {
const QrScanner({super.key});
@override
State<QrScanner> createState() => _QrScannerState();
}
class _QrScannerState extends State<QrScanner> {
bool isNavigationPerformed = false;
bool isScannedByImage = false;
MobileScannerController scannerController = MobileScannerController(
detectionSpeed: DetectionSpeed.normal,
facing: CameraFacing.back,
);
@override
void dispose() {
scannerController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return SafeArea(
child: Scaffold(
backgroundColor: Colors.black,
body: Stack(
alignment: Alignment.topLeft,
children: [
Stack(
children: [
MobileScanner(
controller: scannerController,
onDetect: (capture) async {
if (!isNavigationPerformed) {
isNavigationPerformed = true;
if (capture.barcodes[0].rawValue!
.startsWith(kGoogleAuthExportPrefix)) {
if (isScannedByImage) {
final result = await showDialogWidget(
context: context,
title: l10n.reminderText,
body: l10n.reminderPopupBody,
buttons: [
ButtonWidget(
buttonType: ButtonType.primary,
labelText: l10n.ok,
isInAlert: true,
buttonSize: ButtonSize.large,
buttonAction: ButtonAction.first,
),
],
);
if (result?.action != null &&
result!.action == ButtonAction.first) {
isScannedByImage = false;
}
}
HapticFeedback.vibrate();
try {
List<Code> codes =
parseGoogleAuth(capture.barcodes[0].rawValue!);
scannerController.dispose();
Navigator.of(context).pop(codes);
} catch (e) {
showToast(context, l10n.parsingErrorText);
Logger("Code parsing error").severe(
"Error while parsing Google Auth QR code",
e,
);
throw Exception(
'Failed to parse Google Auth QR code \n ${e.toString()}',
);
}
} else {
showToast(context, l10n.invalidQrCodeText);
isNavigationPerformed = false;
}
}
},
),
const QRScannerOverlay(),
Positioned(
top: 150,
left: 0,
right: 0,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Torch button
IconButton(
icon: ValueListenableBuilder(
valueListenable: scannerController.torchState,
builder: (context, state, child) {
switch (state) {
case TorchState.on:
return SvgPicture.asset(
'assets/scanner-icons/icons/flash_on.svg',
);
case TorchState.off:
return SvgPicture.asset(
'assets/scanner-icons/icons/flash_off.svg',
);
}
},
),
iconSize: 60,
onPressed: () => scannerController.toggleTorch(),
),
IconButton(
icon: SvgPicture.asset(
'assets/scanner-icons/icons/gallery.svg',
),
iconSize: 60,
onPressed: () async {
final result = await showDialogWidget(
context: context,
title: l10n.importFromApp(
"Google Authenticator (saved image)",
),
body: l10n.googleAuthImagePopupBody,
buttons: [
ButtonWidget(
buttonType: ButtonType.primary,
labelText: l10n.importGoogleAuthImageButtonText,
isInAlert: true,
buttonSize: ButtonSize.large,
buttonAction: ButtonAction.first,
),
ButtonWidget(
buttonType: ButtonType.secondary,
labelText: l10n.cancel,
buttonSize: ButtonSize.large,
isInAlert: true,
buttonAction: ButtonAction.second,
),
],
);
if (result?.action != null &&
result!.action != ButtonAction.cancel) {
if (result.action == ButtonAction.first) {
List<AssetEntity>? assets =
await AssetPicker.pickAssets(
context,
pickerConfig: const AssetPickerConfig(
maxAssets: 1,
requestType: RequestType.image,
),
);
if (assets != null && assets.isNotEmpty) {
AssetEntity asset = assets.first;
File? file = await asset.file;
String path = file!.path;
if (await scannerController
.analyzeImage(path)) {
isScannedByImage = true;
if (!mounted) return;
} else {
if (!mounted) return;
isScannedByImage = false;
showToast(
context,
l10n.unableToRecognizeQrCodeText,
);
}
} else {
if (!mounted) return;
showToast(
context,
l10n.qrCodeImageNotSelectedText,
);
}
}
}
},
),
],
),
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
decoration: const BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
color: Colors.white,
),
child: Padding(
padding: const EdgeInsets.fromLTRB(
40,
15,
40,
18,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
height: 5,
width: 40,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(50),
),
),
const SizedBox(
height: 25,
),
Text(
l10n.scanACode,
textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.black,
),
),
],
),
),
),
),
],
),
Positioned(
left: 25,
top: 25,
child: GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: SvgPicture.asset(
'assets/scanner-icons/icons/cross.svg',
colorFilter:
const ColorFilter.mode(Colors.white, BlendMode.srcATop),
height: 30,
),
),
),
],
),
),
);
}
}

View File

@@ -80,18 +80,25 @@ Future<int?> _processBitwardenExportFile(
List<dynamic> jsonArray = data['items'];
final parsedCodes = [];
for (var item in jsonArray) {
if (item['login']['totp'] != null) {
var issuer = item['name'];
var account = item['login']['username'];
var secret = item['login']['totp'];
if (item['login'] != null && item['login']['totp'] != null) {
var totp = item['login']['totp'];
parsedCodes.add(
Code.fromAccountAndSecret(
Code code;
if (totp.contains("otpauth://")) {
code = Code.fromRawData(totp);
} else {
var issuer = item['name'];
var account = item['login']['username'];
code = Code.fromAccountAndSecret(
account,
issuer,
secret,
),
);
totp,
);
}
parsedCodes.add(code);
}
}

View File

@@ -1,7 +1,6 @@
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:base32/base32.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/models/code.dart';
@@ -11,7 +10,7 @@ import 'package:ente_auth/store/code_store.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/analyze_qr_code.dart';
import 'package:ente_auth/ui/scanner_gauth_page.dart';
import 'package:ente_auth/ui/settings/data/import/import_success.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
@@ -46,7 +45,7 @@ Future<void> showGoogleAuthInstruction(BuildContext context) async {
final List<Code>? codes = await Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
return const QrScanner();
return const ScannerGoogleAuthPage();
},
),
);

View File

@@ -1,3 +1,4 @@
import 'package:ente_auth/ui/settings/data/import/2fas_import.dart';
import 'package:ente_auth/ui/settings/data/import/aegis_import.dart';
import 'package:ente_auth/ui/settings/data/import/bitwarden_import.dart';
import 'package:ente_auth/ui/settings/data/import/encrypted_ente_import.dart';
@@ -5,7 +6,7 @@ import 'package:ente_auth/ui/settings/data/import/google_auth_import.dart';
import 'package:ente_auth/ui/settings/data/import/plain_text_import.dart';
import 'package:ente_auth/ui/settings/data/import/raivo_plain_text_import.dart';
import 'package:ente_auth/ui/settings/data/import_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
class ImportService {
static final ImportService _instance = ImportService._internal();
@@ -32,6 +33,9 @@ class ImportService {
case ImportType.aegis:
showAegisImportInstruction(context);
break;
case ImportType.twoFas:
show2FasImportInstruction(context);
break;
case ImportType.bitwarden:
showBitwardenImportInstruction(context);
break;

View File

@@ -1,152 +0,0 @@
import 'package:flutter/material.dart';
class QRScannerOverlay extends StatelessWidget {
const QRScannerOverlay({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
double scanArea = (MediaQuery.of(context).size.width < 400 ||
MediaQuery.of(context).size.height < 400)
? 200.0
: 330.0;
return Stack(
children: [
ColorFiltered(
colorFilter:
ColorFilter.mode(Colors.black.withOpacity(0.9), BlendMode.srcOut),
child: Stack(
children: [
Container(
decoration: const BoxDecoration(
color: Colors.red,
backgroundBlendMode: BlendMode.dstOut,
),
),
Align(
alignment: Alignment.center,
child: Container(
height: scanArea,
width: scanArea,
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(20),
),
),
),
],
),
),
Align(
alignment: Alignment.center,
child: CustomPaint(
foregroundPainter: BorderPainter(),
child: SizedBox(
width: scanArea + 25,
height: scanArea + 25,
),
),
),
],
);
}
}
// Creates the white borders
class BorderPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
const width = 4.0;
const radius = 20.0;
const tRadius = 3 * radius;
final rect = Rect.fromLTWH(
width,
width,
size.width - 2 * width,
size.height - 2 * width,
);
final rrect = RRect.fromRectAndRadius(rect, const Radius.circular(radius));
const clippingRect0 = Rect.fromLTWH(
0,
0,
tRadius,
tRadius,
);
final clippingRect1 = Rect.fromLTWH(
size.width - tRadius,
0,
tRadius,
tRadius,
);
final clippingRect2 = Rect.fromLTWH(
0,
size.height - tRadius,
tRadius,
tRadius,
);
final clippingRect3 = Rect.fromLTWH(
size.width - tRadius,
size.height - tRadius,
tRadius,
tRadius,
);
final path = Path()
..addRect(clippingRect0)
..addRect(clippingRect1)
..addRect(clippingRect2)
..addRect(clippingRect3);
canvas.clipPath(path);
canvas.drawRRect(
rrect,
Paint()
..color = Colors.blueAccent
..style = PaintingStyle.stroke
..strokeWidth = width,
);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
class BarReaderSize {
static double width = 200;
static double height = 200;
}
class OverlayWithHolePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = Colors.black54;
canvas.drawPath(
Path.combine(
PathOperation.difference,
Path()..addRect(Rect.fromLTWH(0, 0, size.width, size.height)),
Path()
..addOval(
Rect.fromCircle(
center: Offset(size.width - 44, size.height - 44),
radius: 40,
),
)
..close(),
),
paint,
);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}

View File

@@ -15,6 +15,7 @@ enum ImportType {
ravio,
googleAuthenticator,
aegis,
twoFas,
bitwarden,
}
@@ -22,10 +23,11 @@ class ImportCodePage extends StatelessWidget {
late List<ImportType> importOptions = [
ImportType.plainText,
ImportType.encrypted,
ImportType.ravio,
ImportType.twoFas,
ImportType.aegis,
ImportType.googleAuthenticator,
ImportType.bitwarden,
ImportType.googleAuthenticator,
ImportType.ravio,
];
ImportCodePage({super.key});
@@ -34,7 +36,6 @@ class ImportCodePage extends StatelessWidget {
switch (type) {
case ImportType.plainText:
return context.l10n.importTypePlainText;
case ImportType.encrypted:
return context.l10n.importTypeEnteEncrypted;
case ImportType.ravio:
@@ -43,6 +44,8 @@ class ImportCodePage extends StatelessWidget {
return 'Google Authenticator';
case ImportType.aegis:
return 'Aegis Authenticator';
case ImportType.twoFas:
return '2FAS Authenticator';
case ImportType.bitwarden:
return 'Bitwarden';
}

View File

@@ -13,6 +13,7 @@ import 'package:ente_auth/ui/components/toggle_switch_widget.dart';
import 'package:ente_auth/ui/settings/common_settings.dart';
import 'package:ente_auth/ui/settings/language_picker.dart';
import 'package:ente_auth/utils/navigation_util.dart';
import 'package:ente_auth/utils/toast_util.dart';
import 'package:flutter/material.dart';
class AdvancedSectionWidget extends StatefulWidget {
@@ -86,6 +87,9 @@ class _AdvancedSectionWidgetState extends State<AdvancedSectionWidget> {
await PreferenceService.instance.setHideCodes(
!PreferenceService.instance.shouldHideCodes(),
);
if(PreferenceService.instance.shouldHideCodes()) {
showToast(context, context.l10n.doubleTapToViewHiddenCode);
}
setState(() {});
},
),

View File

@@ -3,7 +3,9 @@ import 'dart:io';
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/onboarding/view/onboarding_page.dart';
import 'package:ente_auth/services/local_authentication_service.dart';
import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/store/code_store.dart';
import 'package:ente_auth/theme/colors.dart';
import 'package:ente_auth/theme/ente_theme.dart';
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
@@ -99,6 +101,19 @@ class SettingsPage extends StatelessWidget {
await handleExportClick(context);
} else {
if (result.action == ButtonAction.second) {
bool hasCodes =
(await CodeStore.instance.getAllCodes()).isNotEmpty;
if (hasCodes) {
final hasAuthenticated = await LocalAuthenticationService
.instance
.requestLocalAuthentication(
context,
context.l10n.authToInitiateSignIn,
);
if (!hasAuthenticated) {
return;
}
}
await routeToPage(
context,
const OnboardingPage(),

View File

@@ -1,3 +1,5 @@
import 'dart:io';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/ui/common/gradient_button.dart';
import 'package:ente_auth/ui/tools/app_lock.dart';
@@ -21,10 +23,16 @@ class _LockScreenState extends State<LockScreen> with WidgetsBindingObserver {
@override
void initState() {
_logger.info("initState");
_logger.info("initiatingState");
super.initState();
_showLockScreen(source: "initState");
WidgetsBinding.instance.addObserver(this);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (isNonMobileIOSDevice()) {
_logger.info('ignore init for non mobile iOS device');
return;
}
_showLockScreen(source: "postFrameInit");
});
}
@override
@@ -60,6 +68,14 @@ class _LockScreenState extends State<LockScreen> with WidgetsBindingObserver {
);
}
bool isNonMobileIOSDevice() {
if (Platform.isAndroid) {
return false;
}
var shortestSide = MediaQuery.of(context).size.shortestSide;
return shortestSide > 600 ? true : false;
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
_logger.info(state.toString());
@@ -93,6 +109,7 @@ class _LockScreenState extends State<LockScreen> with WidgetsBindingObserver {
@override
void dispose() {
_logger.info('disposing');
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}

View File

@@ -1,5 +1,6 @@
import 'dart:convert';
import 'package:ente_auth/ente_theme_data.dart';
import 'package:ente_auth/theme/ente_theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -14,6 +15,8 @@ class IconUtils {
// Map of icon-title to the color code in HEX
final Map<String, String> _simpleIcons = {};
final Map<String, CustomIconData> _customIcons = {};
// Map of icon-color to its luminance
final Map<Color, double> _colorLuminance = {};
Future<void> init() async {
await _loadJson();
@@ -31,6 +34,7 @@ class IconUtils {
title,
_customIcons[title]!.color,
width,
context,
);
} else if (_simpleIcons.containsKey(title)) {
return _getSVGIcon(
@@ -38,6 +42,7 @@ class IconUtils {
title,
_simpleIcons[title],
width,
context,
);
} else if (title.isNotEmpty) {
bool showLargeIcon = width > 24;
@@ -63,33 +68,64 @@ class IconUtils {
String title,
String? color,
double width,
BuildContext context,
) {
final iconColor = _getAdaptiveColor(color, context);
return SvgPicture.asset(
path,
width: width,
semanticsLabel: title,
colorFilter: color != null
colorFilter: iconColor != null
? ColorFilter.mode(
Color(int.parse("0xFF" + color)),
iconColor,
BlendMode.srcIn,
)
: null,
);
}
Color? _getAdaptiveColor(String? hexColor, BuildContext context) {
if (hexColor == null) return null;
final theme = Theme.of(context).brightness;
final color = Color(int.parse("0xFF" + hexColor));
// Color is close to neutral-grey and it's too light or dark for theme
if (_isCloseToNeutralGrey(color) &&
((theme == Brightness.light && _getColorLuminance(color) > 0.70) ||
(theme == Brightness.dark && _getColorLuminance(color) < 0.05))) {
return Theme.of(context).colorScheme.iconColor;
}
return color;
}
double _getColorLuminance(Color color) {
return _colorLuminance.putIfAbsent(color, () => color.computeLuminance());
}
bool _isCloseToNeutralGrey(Color color, {double tolerance = 3}) {
return (color.red - color.green).abs() <= tolerance &&
(color.green - color.blue).abs() <= tolerance &&
(color.blue - color.red).abs() <= tolerance;
}
Future<void> _loadJson() async {
try {
final simpleIconData = await rootBundle
.loadString('assets/simple-icons/_data/simple-icons.json');
final simpleIcons = json.decode(simpleIconData);
for (final icon in simpleIcons["icons"]) {
_simpleIcons[icon["title"].toString().toLowerCase()] = icon["hex"];
_simpleIcons[icon["title"]
.toString()
.replaceAll(' ', '')
.toLowerCase()] = icon["hex"];
}
final customIconData = await rootBundle
.loadString('assets/custom-icons/_data/custom-icons.json');
final customIcons = json.decode(customIconData);
for (final icon in customIcons["icons"]) {
_customIcons[icon["title"].toString().toLowerCase()] = CustomIconData(
_customIcons[icon["title"]
.toString()
.replaceAll(' ', '')
.toLowerCase()] = CustomIconData(
icon["slug"],
icon["hex"],
);
@@ -108,7 +144,7 @@ class IconUtils {
}
String _getProviderTitle(String provider) {
return provider.split(RegExp(r'[.(]'))[0].trim().toLowerCase();
return provider.split(RegExp(r'[.(]'))[0].replaceAll(' ', '').toLowerCase();
}
}

View File

@@ -42,8 +42,8 @@ Uint8List cryptoPwHash(Map<String, dynamic> args) {
}
Uint8List cryptoKdfDeriveFromKey(
Map<String, dynamic> args,
) {
Map<String, dynamic> args,
) {
return Sodium.cryptoKdfDeriveFromKey(
args["subkeyLen"],
args["subkeyId"],
@@ -58,7 +58,7 @@ Future<Uint8List> cryptoGenericHash(Map<String, dynamic> args) async {
final sourceFileLength = await sourceFile.length();
final inputFile = sourceFile.openSync(mode: io.FileMode.read);
final state =
Sodium.cryptoGenerichashInit(null, Sodium.cryptoGenerichashBytesMax);
Sodium.cryptoGenerichashInit(null, Sodium.cryptoGenerichashBytesMax);
var bytesRead = 0;
bool isDone = false;
while (!isDone) {
@@ -77,7 +77,7 @@ Future<Uint8List> cryptoGenericHash(Map<String, dynamic> args) async {
EncryptionResult chachaEncryptData(Map<String, dynamic> args) {
final initPushResult =
Sodium.cryptoSecretstreamXchacha20poly1305InitPush(args["key"]);
Sodium.cryptoSecretstreamXchacha20poly1305InitPush(args["key"]);
final encryptedData = Sodium.cryptoSecretstreamXchacha20poly1305Push(
initPushResult.state,
args["source"],
@@ -102,7 +102,7 @@ Future<EncryptionResult> chachaEncryptFile(Map<String, dynamic> args) async {
final inputFile = sourceFile.openSync(mode: io.FileMode.read);
final key = args["key"] ?? Sodium.cryptoSecretstreamXchacha20poly1305Keygen();
final initPushResult =
Sodium.cryptoSecretstreamXchacha20poly1305InitPush(key);
Sodium.cryptoSecretstreamXchacha20poly1305InitPush(key);
var bytesRead = 0;
var tag = Sodium.cryptoSecretstreamXchacha20poly1305TagMessage;
while (tag != Sodium.cryptoSecretstreamXchacha20poly1305TagFinal) {
@@ -156,7 +156,7 @@ Future<void> chachaDecryptFile(Map<String, dynamic> args) async {
final buffer = await inputFile.read(chunkSize);
bytesRead += chunkSize;
final pullResult =
Sodium.cryptoSecretstreamXchacha20poly1305Pull(pullState, buffer, null);
Sodium.cryptoSecretstreamXchacha20poly1305Pull(pullState, buffer, null);
await destinationFile.writeAsBytes(pullResult.m, mode: io.FileMode.append);
tag = pullResult.tag;
}
@@ -190,20 +190,22 @@ class CryptoUtil {
Sodium.init();
}
static Uint8List base642bin(String b64, {
static Uint8List base642bin(
String b64, {
String? ignore,
int variant = Sodium.base64VariantOriginal,
}) {
return Sodium.base642bin(b64, ignore: ignore, variant: variant);
}
static String bin2base64(Uint8List bin, {
static String bin2base64(
Uint8List bin, {
bool urlSafe = false,
}) {
return Sodium.bin2base64(
bin,
variant:
urlSafe ? Sodium.base64VariantUrlsafe : Sodium.base64VariantOriginal,
urlSafe ? Sodium.base64VariantUrlsafe : Sodium.base64VariantOriginal,
);
}
@@ -237,9 +239,11 @@ class CryptoUtil {
// Decrypts the given cipher, with the given key and nonce using XSalsa20
// (w Poly1305 MAC).
static Future<Uint8List> decrypt(Uint8List cipher,
Uint8List key,
Uint8List nonce,) async {
static Future<Uint8List> decrypt(
Uint8List cipher,
Uint8List key,
Uint8List nonce,
) async {
final args = <String, dynamic>{};
args["cipher"] = cipher;
args["nonce"] = nonce;
@@ -256,9 +260,11 @@ class CryptoUtil {
// This function runs on the same thread as the caller, so should be used only
// for small amounts of data where thread switching can result in a degraded
// user experience
static Uint8List decryptSync(Uint8List cipher,
Uint8List key,
Uint8List nonce,) {
static Uint8List decryptSync(
Uint8List cipher,
Uint8List key,
Uint8List nonce,
) {
final args = <String, dynamic>{};
args["cipher"] = cipher;
args["nonce"] = nonce;
@@ -270,8 +276,10 @@ class CryptoUtil {
// nonce, using XChaCha20 (w Poly1305 MAC).
// This function runs on the isolate pool held by `_computer`.
// TODO: Remove "ChaCha", an implementation detail from the function name
static Future<EncryptionResult> encryptChaCha(Uint8List source,
Uint8List key,) async {
static Future<EncryptionResult> encryptChaCha(
Uint8List source,
Uint8List key,
) async {
final args = <String, dynamic>{};
args["source"] = source;
args["key"] = key;
@@ -285,9 +293,11 @@ class CryptoUtil {
// Decrypts the given source, with the given key and header using XChaCha20
// (w Poly1305 MAC).
// TODO: Remove "ChaCha", an implementation detail from the function name
static Future<Uint8List> decryptChaCha(Uint8List source,
Uint8List key,
Uint8List header,) async {
static Future<Uint8List> decryptChaCha(
Uint8List source,
Uint8List key,
Uint8List header,
) async {
final args = <String, dynamic>{};
args["source"] = source;
args["key"] = key;
@@ -304,10 +314,10 @@ class CryptoUtil {
// to the destinationFilePath.
// If a key is not provided, one is generated and returned.
static Future<EncryptionResult> encryptFile(
String sourceFilePath,
String destinationFilePath, {
Uint8List? key,
}) {
String sourceFilePath,
String destinationFilePath, {
Uint8List? key,
}) {
final args = <String, dynamic>{};
args["sourceFilePath"] = sourceFilePath;
args["destinationFilePath"] = destinationFilePath;
@@ -322,10 +332,11 @@ class CryptoUtil {
// Decrypts the file at sourceFilePath, with the given key and header using
// XChaCha20 (w Poly1305 MAC), and writes it to the destinationFilePath.
static Future<void> decryptFile(
String sourceFilePath,
String destinationFilePath,
Uint8List header,
Uint8List key,) {
String sourceFilePath,
String destinationFilePath,
Uint8List header,
Uint8List key,
) {
final args = <String, dynamic>{};
args["sourceFilePath"] = sourceFilePath;
args["destinationFilePath"] = destinationFilePath;
@@ -356,10 +367,10 @@ class CryptoUtil {
// Decrypts the input using the given publicKey-secretKey pair
static Uint8List openSealSync(
Uint8List input,
Uint8List publicKey,
Uint8List secretKey,
) {
Uint8List input,
Uint8List publicKey,
Uint8List secretKey,
) {
return Sodium.cryptoBoxSealOpen(input, publicKey, secretKey);
}
@@ -377,9 +388,9 @@ class CryptoUtil {
// At all points, we ensure that the product of these two variables (the area
// under the graph that determines the amount of work required) is a constant.
static Future<DerivedKeyResult> deriveSensitiveKey(
Uint8List password,
Uint8List salt,
) async {
Uint8List password,
Uint8List salt,
) async {
final logger = Logger("pwhash");
int memLimit = Sodium.cryptoPwhashMemlimitSensitive;
int opsLimit = Sodium.cryptoPwhashOpslimitSensitive;
@@ -407,7 +418,10 @@ class CryptoUtil {
return DerivedKeyResult(key, memLimit, opsLimit);
} catch (e, s) {
logger.warning(
"failed to deriveKey mem: $memLimit, ops: $opsLimit", e, s,);
"failed to deriveKey mem: $memLimit, ops: $opsLimit",
e,
s,
);
}
memLimit = (memLimit / 2).round();
opsLimit = opsLimit * 2;
@@ -421,9 +435,9 @@ class CryptoUtil {
// extra layer of authentication (atop the access token and collection key).
// More details @ https://ente.io/blog/building-shareable-links/
static Future<DerivedKeyResult> deriveInteractiveKey(
Uint8List password,
Uint8List salt,
) async {
Uint8List password,
Uint8List salt,
) async {
final int memLimit = Sodium.cryptoPwhashMemlimitInteractive;
final int opsLimit = Sodium.cryptoPwhashOpslimitInteractive;
final key = await deriveKey(password, salt, memLimit, opsLimit);
@@ -433,13 +447,13 @@ class CryptoUtil {
// Derives a key for a given password, salt, memLimit and opsLimit using
// Argon2id, v1.3.
static Future<Uint8List> deriveKey(
Uint8List password,
Uint8List salt,
int memLimit,
int opsLimit,
) {
Uint8List password,
Uint8List salt,
int memLimit,
int opsLimit,
) async {
try {
return _computer.compute(
return await _computer.compute(
cryptoPwHash,
param: {
"password": password,
@@ -449,7 +463,7 @@ class CryptoUtil {
},
taskName: "deriveKey",
);
} catch(e,s) {
} catch (e, s) {
final String errMessage = 'failed to deriveKey memLimit: $memLimit and '
'opsLimit: $opsLimit';
Logger("CryptoUtilDeriveKey").warning(errMessage, e, s);
@@ -461,20 +475,25 @@ class CryptoUtil {
// (Key Derivation Function) with the `loginSubKeyId` and
// `loginSubKeyLen` and `loginSubKeyContext` as context
static Future<Uint8List> deriveLoginKey(
Uint8List key,
) async {
final Uint8List derivedKey = await _computer.compute(
cryptoKdfDeriveFromKey,
param: {
"key": key,
"subkeyId": loginSubKeyId,
"subkeyLen": loginSubKeyLen,
"context": utf8.encode(loginSubKeyContext),
},
taskName: "deriveLoginKey",
);
// return the first 16 bytes of the derived key
return derivedKey.sublist(0, 16);
Uint8List key,
) async {
try {
final Uint8List derivedKey = await _computer.compute(
cryptoKdfDeriveFromKey,
param: {
"key": key,
"subkeyId": loginSubKeyId,
"subkeyLen": loginSubKeyLen,
"context": utf8.encode(loginSubKeyContext),
},
taskName: "deriveLoginKey",
);
// return the first 16 bytes of the derived key
return derivedKey.sublist(0, 16);
} catch (e, s) {
Logger("deriveLoginKey").severe("loginKeyDerivation failed", e, s);
throw LoginKeyDerivationError();
}
}
// Computes and returns the hash of the source file

View File

@@ -10,16 +10,13 @@ import device_info_plus
import file_saver
import flutter_local_notifications
import flutter_secure_storage_macos
import mobile_scanner
import package_info_plus
import path_provider_foundation
import photo_manager
import sentry_flutter
import share_plus
import shared_preferences_foundation
import sqflite
import url_launcher_macos
import video_player_avfoundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
@@ -27,14 +24,11 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin"))
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
PhotoManagerPlugin.register(with: registry.registrar(forPlugin: "PhotoManagerPlugin"))
SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
}

View File

@@ -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:

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,13 +1,10 @@
package main
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"io"
"log"
"github.com/jamesruan/sodium"
"golang.org/x/crypto/argon2"
)
@@ -48,23 +45,38 @@ func deriveArgonKey(password, salt string, memLimit, opsLimit int) ([]byte, erro
// Returns:
// - A byte slice representing the decrypted data.
// - An error object, which is nil if no error occurs.
func decryptChaCha20poly1305(data []byte, key []byte, nonce []byte) ([]byte, error) {
reader := bytes.NewReader(data)
header := sodium.SecretStreamXCPHeader{Bytes: nonce}
decoder, err := sodium.MakeSecretStreamXCPDecoder(
sodium.SecretStreamXCPKey{Bytes: key},
reader,
header)
// func decryptChaCha20poly13052(data []byte, key []byte, nonce []byte) ([]byte, error) {
// reader := bytes.NewReader(data)
// header := sodium.SecretStreamXCPHeader{Bytes: nonce}
// decoder, err := sodium.MakeSecretStreamXCPDecoder(
// sodium.SecretStreamXCPKey{Bytes: key},
// reader,
// header)
// if err != nil {
// log.Println("Failed to make secret stream decoder", err)
// return nil, err
// }
// // Buffer to store the decrypted data
// decryptedData := make([]byte, len(data))
// n, err := decoder.Read(decryptedData)
// if err != nil && err != io.EOF {
// log.Println("Failed to read from decoder", err)
// return nil, err
// }
// return decryptedData[:n], nil
// }
func decryptChaCha20poly13052(data []byte, key []byte, nonce []byte) ([]byte, error) {
decryptor, err := NewDecryptor(key, nonce)
if err != nil {
log.Println("Failed to make secret stream decoder", err)
return nil, err
}
// Buffer to store the decrypted data
decryptedData := make([]byte, len(data))
n, err := decoder.Read(decryptedData)
if err != nil && err != io.EOF {
log.Println("Failed to read from decoder", err)
decoded, tag, err := decryptor.Pull(data)
if tag != TagFinal {
return nil, errors.New("invalid tag")
}
if err != nil {
return nil, err
}
return decryptedData[:n], nil
return decoded, nil
}

View File

@@ -43,7 +43,7 @@ func TestDecryptChaCha20poly1305(t *testing.T) {
t.Fatalf("Failed to decode cipher nonce: %v", err)
}
decryptedText, err := decryptChaCha20poly1305(decodedCipherText, derivedKey, decodedCipherNonce)
decryptedText, err := decryptChaCha20poly13052(decodedCipherText, derivedKey, decodedCipherNonce)
if err != nil {
t.Fatalf("Failed to decrypt: %v", err)
}

Binary file not shown.

View File

@@ -90,7 +90,7 @@ func main() {
return
}
decryptedData, err := decryptChaCha20poly1305(encryptedData, key, nonce)
decryptedData, err := decryptChaCha20poly13052(encryptedData, key, nonce)
if err != nil {
fmt.Println("Error decrypting data:", err)
return

View File

@@ -2,9 +2,6 @@ module decrypt
go 1.20
require (
github.com/jamesruan/sodium v1.0.14
golang.org/x/crypto v0.11.0
)
require golang.org/x/crypto v0.11.0
require golang.org/x/sys v0.10.0 // indirect

View File

@@ -1,5 +1,3 @@
github.com/jamesruan/sodium v1.0.14 h1:JfOHobip/lUWouxHV3PwYwu3gsLewPrDrZXO3HuBzUU=
github.com/jamesruan/sodium v1.0.14/go.mod h1:GK2+LACf7kuVQ9k7Irk0MB2B65j5rVqkz+9ylGIggZk=
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=

View File

@@ -0,0 +1,43 @@
#!/bin/bash
# Create a "bin" directory if it doesn't exist
mkdir -p bin
# List of target operating systems
OS_TARGETS=("windows" "linux" "darwin")
# Corresponding architectures for each OS
ARCH_TARGETS=("386 amd64" "386 amd64 arm arm64" "amd64 arm64")
# Loop through each OS target
for index in "${!OS_TARGETS[@]}"
do
OS=${OS_TARGETS[$index]}
for ARCH in ${ARCH_TARGETS[$index]}
do
# Set the GOOS environment variable for the current target OS
export GOOS="$OS"
export GOARCH="$ARCH"
# Set the output binary name to "ente-decrypt" for the current OS and architecture
BINARY_NAME="ente-decrypt-$OS-$ARCH"
# Add .exe extension for Windows
if [ "$OS" == "windows" ]; then
BINARY_NAME="ente-decrypt-$OS-$ARCH.exe"
fi
# Build the binary and place it in the "bin" directory
go build -o "bin/$BINARY_NAME" decrypt.go crypt.go stream.go
# Print a message indicating the build is complete for the current OS and architecture
echo "Built for $OS ($ARCH) as bin/$BINARY_NAME"
done
done
# Clean up any environment variables
unset GOOS
unset GOARCH
# Print a message indicating the build process is complete
echo "Build process completed for all platforms and architectures. Binaries are in the 'bin' directory."

View File

@@ -0,0 +1,409 @@
package main
import (
"bytes"
"crypto/rand"
"encoding/binary"
"errors"
"fmt"
"golang.org/x/crypto/chacha20"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/poly1305"
)
// public constants
const (
//TagMessage the most common tag, that doesn't add any information about the nature of the message.
TagMessage = 0
// TagPush indicates that the message marks the end of a set of messages,
// but not the end of the stream. For example, a huge JSON string sent as multiple chunks can use this tag to indicate to the application that the string is complete and that it can be decoded. But the stream itself is not closed, and more data may follow.
TagPush = 0x01
// TagRekey "forget" the key used to encrypt this message and the previous ones, and derive a new secret key.
TagRekey = 0x02
// TagFinal indicates that the message marks the end of the stream, and erases the secret key used to encrypt the previous sequence.
TagFinal = TagPush | TagRekey
StreamKeyBytes = chacha20poly1305.KeySize
StreamHeaderBytes = chacha20poly1305.NonceSizeX
// XChaCha20Poly1305IetfABYTES links to crypto_secretstream_xchacha20poly1305_ABYTES
XChaCha20Poly1305IetfABYTES = 16 + 1
)
const cryptoCoreHchacha20InputBytes = 16
/* const crypto_secretstream_xchacha20poly1305_INONCEBYTES = 8 */
const cryptoSecretStreamXchacha20poly1305Counterbytes = 4
var pad0 [16]byte
var invalidKey = errors.New("invalid key")
var invalidInput = errors.New("invalid input")
var cryptoFailure = errors.New("crypto failed")
func memZero(b []byte) {
for i := range b {
b[i] = 0
}
}
func xorBuf(out, in []byte) {
for i := range out {
out[i] ^= in[i]
}
}
func bufInc(n []byte) {
c := 1
for i := range n {
c += int(n[i])
n[i] = byte(c)
c >>= 8
}
}
// crypto_secretstream_xchacha20poly1305_state
type streamState struct {
k [StreamKeyBytes]byte
nonce [chacha20poly1305.NonceSize]byte
pad [8]byte
}
func (s *streamState) reset() {
for i := range s.nonce {
s.nonce[i] = 0
}
s.nonce[0] = 1
}
type Encryptor interface {
Push(m []byte, tag byte) ([]byte, error)
}
type Decryptor interface {
Pull(m []byte) ([]byte, byte, error)
}
type encryptor struct {
streamState
}
type decryptor struct {
streamState
}
func NewStreamKey() []byte {
k := make([]byte, chacha20poly1305.KeySize)
_, _ = rand.Read(k)
return k
}
func NewEncryptor(key []byte) (Encryptor, []byte, error) {
if len(key) != StreamKeyBytes {
return nil, nil, invalidKey
}
header := make([]byte, StreamHeaderBytes)
_, _ = rand.Read(header)
stream := &encryptor{}
k, err := chacha20.HChaCha20(key[:], header[:16])
if err != nil {
//fmt.Printf("error: %v", err)
return nil, nil, err
}
copy(stream.k[:], k)
stream.reset()
for i := range stream.pad {
stream.pad[i] = 0
}
for i, b := range header[cryptoCoreHchacha20InputBytes:] {
stream.nonce[i+cryptoSecretStreamXchacha20poly1305Counterbytes] = b
}
// fmt.Printf("stream: %+v\n", stream.streamState)
return stream, header, nil
}
func (s *encryptor) Push(plain []byte, tag byte) ([]byte, error) {
var err error
//crypto_onetimeauth_poly1305_state poly1305_state;
var poly *poly1305.MAC
//unsigned char block[64U];
var block [64]byte
//unsigned char slen[8U];
var slen [8]byte
//unsigned char *c;
//unsigned char *mac;
//
//if (outlen_p != NULL) {
//*outlen_p = 0U;
//}
mlen := len(plain)
//if (mlen > crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX) {
//sodium_misuse();
//}
out := make([]byte, mlen+XChaCha20Poly1305IetfABYTES)
chacha, err := chacha20.NewUnauthenticatedCipher(s.k[:], s.nonce[:])
if err != nil {
return nil, err
}
//crypto_stream_chacha20_ietf(block, sizeof block, state->nonce, state->k);
chacha.XORKeyStream(block[:], block[:])
//crypto_onetimeauth_poly1305_init(&poly1305_state, block);
var poly_init [32]byte
copy(poly_init[:], block[:])
poly = poly1305.New(&poly_init)
// TODO add support for add data
//sodium_memzero(block, sizeof block);
//crypto_onetimeauth_poly1305_update(&poly1305_state, ad, adlen);
//crypto_onetimeauth_poly1305_update(&poly1305_state, _pad0,
//(0x10 - adlen) & 0xf);
//memset(block, 0, sizeof block);
//block[0] = tag;
memZero(block[:])
block[0] = tag
//
//crypto_stream_chacha20_ietf_xor_ic(block, block, sizeof block, state->nonce, 1U, state->k);
//crypto_onetimeauth_poly1305_update(&poly1305_state, block, sizeof block);
//out[0] = block[0];
chacha.XORKeyStream(block[:], block[:])
_, _ = poly.Write(block[:])
out[0] = block[0]
//
//c = out + (sizeof tag);
c := out[1:]
//crypto_stream_chacha20_ietf_xor_ic(c, m, mlen, state->nonce, 2U, state->k);
//crypto_onetimeauth_poly1305_update(&poly1305_state, c, mlen);
//crypto_onetimeauth_poly1305_update (&poly1305_state, _pad0, (0x10 - (sizeof block) + mlen) & 0xf);
chacha.XORKeyStream(c, plain)
_, _ = poly.Write(c[:mlen])
padlen := (0x10 - len(block) + mlen) & 0xf
_, _ = poly.Write(pad0[:padlen])
//
//STORE64_LE(slen, (uint64_t) adlen);
//crypto_onetimeauth_poly1305_update(&poly1305_state, slen, sizeof slen);
binary.LittleEndian.PutUint64(slen[:], uint64(0))
_, _ = poly.Write(slen[:])
//STORE64_LE(slen, (sizeof block) + mlen);
//crypto_onetimeauth_poly1305_update(&poly1305_state, slen, sizeof slen);
binary.LittleEndian.PutUint64(slen[:], uint64(len(block)+mlen))
_, _ = poly.Write(slen[:])
//
//mac = c + mlen;
//crypto_onetimeauth_poly1305_final(&poly1305_state, mac);
mac := c[mlen:]
copy(mac, poly.Sum(nil))
//sodium_memzero(&poly1305_state, sizeof poly1305_state);
//
//XOR_BUF(STATE_INONCE(state), mac, crypto_secretstream_xchacha20poly1305_INONCEBYTES);
//sodium_increment(STATE_COUNTER(state), crypto_secretstream_xchacha20poly1305_COUNTERBYTES);
xorBuf(s.nonce[cryptoSecretStreamXchacha20poly1305Counterbytes:], mac)
bufInc(s.nonce[:cryptoSecretStreamXchacha20poly1305Counterbytes])
// TODO
//if ((tag & crypto_secretstream_xchacha20poly1305_TAG_REKEY) != 0 ||
//sodium_is_zero(STATE_COUNTER(state),
//crypto_secretstream_xchacha20poly1305_COUNTERBYTES)) {
//crypto_secretstream_xchacha20poly1305_rekey(state);
//}
//if (outlen_p != NULL) {
//*outlen_p = crypto_secretstream_xchacha20poly1305_ABYTES + mlen;
//}
//return 0;
return out, nil
}
func NewDecryptor(key, header []byte) (Decryptor, error) {
stream := &decryptor{}
//crypto_core_hchacha20(state->k, in, k, NULL);
k, err := chacha20.HChaCha20(key, header[:16])
if err != nil {
fmt.Printf("error: %v", err)
return nil, err
}
copy(stream.k[:], k)
//_crypto_secretstream_xchacha20poly1305_counter_reset(state);
stream.reset()
//memcpy(STATE_INONCE(state), in + crypto_core_hchacha20_INPUTBYTES,
// crypto_secretstream_xchacha20poly1305_INONCEBYTES);
copy(stream.nonce[cryptoSecretStreamXchacha20poly1305Counterbytes:],
header[cryptoCoreHchacha20InputBytes:])
//memset(state->_pad, 0, sizeof state->_pad);
copy(stream.pad[:], pad0[:])
//fmt.Printf("decryptor: %+v\n", stream.streamState)
return stream, nil
}
func (s *decryptor) Pull(cipher []byte) ([]byte, byte, error) {
cipherLen := len(cipher)
//crypto_onetimeauth_poly1305_state poly1305_state;
var poly1305State [32]byte
//unsigned char block[64U];
var block [64]byte
//unsigned char slen[8U];
var slen [8]byte
//unsigned char mac[crypto_onetimeauth_poly1305_BYTES];
//const unsigned char *c;
//const unsigned char *stored_mac;
//unsigned long long mlen; // length of the returned message
//unsigned char tag; // for the return value
//
//if (mlen_p != NULL) {
//*mlen_p = 0U;
//}
//if (tag_p != NULL) {
//*tag_p = 0xff;
//}
/*
if (inlen < crypto_secretstream_xchacha20poly1305_ABYTES) {
return -1;
}
mlen = inlen - crypto_secretstream_xchacha20poly1305_ABYTES;
*/
if cipherLen < XChaCha20Poly1305IetfABYTES {
return nil, 0, invalidInput
}
mlen := cipherLen - XChaCha20Poly1305IetfABYTES
//if (mlen > crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX) {
//sodium_misuse();
//}
//crypto_stream_chacha20_ietf(block, sizeof block, state->nonce, state->k);
chacha, err := chacha20.NewUnauthenticatedCipher(s.k[:], s.nonce[:])
if err != nil {
return nil, 0, err
}
chacha.XORKeyStream(block[:], block[:])
//crypto_onetimeauth_poly1305_init(&poly1305_state, block);
copy(poly1305State[:], block[:])
poly := poly1305.New(&poly1305State)
// TODO
//sodium_memzero(block, sizeof block);
//crypto_onetimeauth_poly1305_update(&poly1305_state, ad, adlen);
//crypto_onetimeauth_poly1305_update(&poly1305_state, _pad0,
//(0x10 - adlen) & 0xf);
//
//memset(block, 0, sizeof block);
//block[0] = in[0];
//crypto_stream_chacha20_ietf_xor_ic(block, block, sizeof block, state->nonce, 1U, state->k);
memZero(block[:])
block[0] = cipher[0]
chacha.XORKeyStream(block[:], block[:])
//tag = block[0];
//block[0] = in[0];
//crypto_onetimeauth_poly1305_update(&poly1305_state, block, sizeof block);
tag := block[0]
block[0] = cipher[0]
if _, err = poly.Write(block[:]); err != nil {
return nil, 0, err
}
//c = in + (sizeof tag);
//crypto_onetimeauth_poly1305_update(&poly1305_state, c, mlen);
//crypto_onetimeauth_poly1305_update (&poly1305_state, _pad0, (0x10 - (sizeof block) + mlen) & 0xf);
c := cipher[1:]
if _, err = poly.Write(c[:mlen]); err != nil {
return nil, 0, err
}
padLen := (0x10 - len(block) + mlen) & 0xf
if _, err = poly.Write(pad0[:padLen]); err != nil {
return nil, 0, err
}
//
//STORE64_LE(slen, (uint64_t) adlen);
//crypto_onetimeauth_poly1305_update(&poly1305_state, slen, sizeof slen);
binary.LittleEndian.PutUint64(slen[:], uint64(0))
if _, err = poly.Write(slen[:]); err != nil {
return nil, 0, err
}
//STORE64_LE(slen, (sizeof block) + mlen);
//crypto_onetimeauth_poly1305_update(&poly1305_state, slen, sizeof slen);
binary.LittleEndian.PutUint64(slen[:], uint64(len(block)+mlen))
if _, err = poly.Write(slen[:]); err != nil {
return nil, 0, err
}
//
//crypto_onetimeauth_poly1305_final(&poly1305_state, mac);
//sodium_memzero(&poly1305_state, sizeof poly1305_state);
mac := poly.Sum(nil)
memZero(poly1305State[:])
//stored_mac = c + mlen;
//if (sodium_memcmp(mac, stored_mac, sizeof mac) != 0) {
//sodium_memzero(mac, sizeof mac);
//return -1;
//}
storedMac := c[mlen:]
if !bytes.Equal(mac, storedMac) {
memZero(mac)
return nil, 0, cryptoFailure
}
//crypto_stream_chacha20_ietf_xor_ic(m, c, mlen, state->nonce, 2U, state->k);
//XOR_BUF(STATE_INONCE(state), mac, crypto_secretstream_xchacha20poly1305_INONCEBYTES);
//sodium_increment(STATE_COUNTER(state), crypto_secretstream_xchacha20poly1305_COUNTERBYTES);
m := make([]byte, mlen)
chacha.XORKeyStream(m, c[:mlen])
xorBuf(s.nonce[cryptoSecretStreamXchacha20poly1305Counterbytes:], mac)
bufInc(s.nonce[:cryptoSecretStreamXchacha20poly1305Counterbytes])
// TODO
//if ((tag & crypto_secretstream_xchacha20poly1305_TAG_REKEY) != 0 ||
//sodium_is_zero(STATE_COUNTER(state),
//crypto_secretstream_xchacha20poly1305_COUNTERBYTES)) {
//crypto_secretstream_xchacha20poly1305_rekey(state);
//}
//if (mlen_p != NULL) {
//*mlen_p = mlen;
//}
//if (tag_p != NULL) {
//*tag_p = tag;
//}
//return 0;
return m, tag, nil
}

View File

@@ -56,7 +56,7 @@ For encryption, we are using `XChaCha20-Poly1305` algorithm.
* **ente Authenticator app**: You can directly import the codes in the ente Authenticator app.
> Settings -> Data -> Import Codes -> ente Encrypted export.
* **Decryption Tool** : You can download the prebuilt [decryption tool](decrypt/decrypt) (or build it from [source](decrypt)) and run the following command.
* **Decryption Tool** : You can download the prebuilt [decryption tool](decrypt/bin/) (or build it from [source](decrypt)) and run the following command.
```
./decrypt <export_file> <password> <output_file>

View File

@@ -386,22 +386,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.0"
extended_image:
dependency: transitive
description:
name: extended_image
sha256: b4d72a27851751cfadaf048936d42939db7cd66c08fdcfe651eeaa1179714ee6
url: "https://pub.dev"
source: hosted
version: "8.1.1"
extended_image_library:
dependency: transitive
description:
name: extended_image_library
sha256: "8bf87c0b14dcb59200c923a9a3952304e4732a0901e40811428834ef39018ee1"
url: "https://pub.dev"
source: hosted
version: "3.6.0"
fake_async:
dependency: transitive
description:
@@ -703,14 +687,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.0"
http_client_helper:
dependency: transitive
description:
name: http_client_helper
sha256: "8a9127650734da86b5c73760de2b404494c968a3fd55602045ffec789dac3cb1"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
http_multi_server:
dependency: transitive
description:
@@ -863,14 +839,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.4"
mobile_scanner:
dependency: "direct main"
description:
name: mobile_scanner
sha256: cf978740676ba5b0c17399baf117984b31190bb7a6eaa43e51229ed46abc42ee
url: "https://pub.dev"
source: hosted
version: "3.5.2"
mocktail:
dependency: "direct dev"
description:
@@ -1039,14 +1007,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.1.0"
photo_manager:
dependency: transitive
description:
name: photo_manager
sha256: c1f21882f22c97cc85a8a67b08d7b979a03c9b7f18f940c10c6860b3a49581d3
url: "https://pub.dev"
source: hosted
version: "2.8.0"
pinput:
dependency: "direct main"
description:
@@ -1087,6 +1047,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.5.1"
privacy_screen:
dependency: "direct main"
description:
name: privacy_screen
sha256: b80297d2726d96e8a8341149e81a415302755f02d3af7c05c820d9e191bbfbee
url: "https://pub.dev"
source: hosted
version: "0.0.6"
process:
dependency: transitive
description:
@@ -1588,46 +1556,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
video_player:
dependency: transitive
description:
name: video_player
sha256: e16f0a83601a78d165dabc17e4dac50997604eb9e4cc76e10fa219046b70cef3
url: "https://pub.dev"
source: hosted
version: "2.8.1"
video_player_android:
dependency: transitive
description:
name: video_player_android
sha256: "3fe89ab07fdbce786e7eb25b58532d6eaf189ceddc091cb66cba712f8d9e8e55"
url: "https://pub.dev"
source: hosted
version: "2.4.10"
video_player_avfoundation:
dependency: transitive
description:
name: video_player_avfoundation
sha256: fe73d636f82286a3739f5e644f95f09442cacdc436ebbe5436521dc915f3ecac
url: "https://pub.dev"
source: hosted
version: "2.5.1"
video_player_platform_interface:
dependency: transitive
description:
name: video_player_platform_interface
sha256: be72301bf2c0150ab35a8c34d66e5a99de525f6de1e8d27c0672b836fe48f73a
url: "https://pub.dev"
source: hosted
version: "6.2.1"
video_player_web:
dependency: transitive
description:
name: video_player_web
sha256: ab7a462b07d9ca80bed579e30fb3bce372468f1b78642e0911b10600f2c5cb5b
url: "https://pub.dev"
source: hosted
version: "2.1.2"
vm_service:
dependency: transitive
description:
@@ -1668,14 +1596,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.0"
wechat_assets_picker:
dependency: "direct main"
description:
name: wechat_assets_picker
sha256: "00c93a04421013040b555cdcccdb8e90f142a171d6c0d968c2b5042a76013601"
url: "https://pub.dev"
source: hosted
version: "8.7.1"
win32:
dependency: transitive
description:
@@ -1717,5 +1637,5 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.1.0 <4.0.0"
flutter: ">=3.13.0"
dart: ">=3.1.0-185.0.dev <4.0.0"
flutter: ">=3.10.0"

View File

@@ -1,6 +1,6 @@
name: ente_auth
description: ente two-factor authenticator
version: 2.0.18+218
version: 2.0.30+230
publish_to: none
environment:
@@ -58,7 +58,6 @@ dependencies:
local_auth_android: ^1.0.31
local_auth_ios: ^1.1.3
logging: ^1.0.1
mobile_scanner: ^3.5.2
modal_bottom_sheet: ^3.0.0-pre
move_to_background: ^1.0.2
open_filex: ^4.3.2
@@ -68,6 +67,7 @@ dependencies:
path_provider: ^2.0.11
pinput: ^1.2.2
pointycastle: ^3.7.3
privacy_screen: ^0.0.6
protobuf: ^3.0.0
qr_code_scanner: ^1.0.1
qr_flutter: 4.0.0
@@ -82,7 +82,6 @@ dependencies:
uni_links: ^0.5.1
url_launcher: ^6.1.5
uuid: ^3.0.4
wechat_assets_picker: ^8.6.3
dev_dependencies:
bloc_test: ^9.0.3
@@ -105,7 +104,6 @@ flutter:
- assets/simple-icons/_data/
- assets/custom-icons/icons/
- assets/custom-icons/_data/
- assets/scanner-icons/icons/
fonts:
- family: Inter