├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── ci.yml │ └── l18n-crowdin.yml ├── .gitignore ├── .gitmodules ├── .metadata ├── .vscode ├── extensions.json └── launch.json ├── LICENSE ├── README.md ├── SECURITY.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ └── io │ │ │ └── ente │ │ │ └── authenticator │ │ │ └── MainActivity.kt │ │ └── res │ │ ├── drawable-hdpi │ │ ├── ic_launcher_foreground.png │ │ └── splash.png │ │ ├── drawable-mdpi │ │ ├── ic_launcher_foreground.png │ │ └── splash.png │ │ ├── drawable-night-hdpi │ │ └── splash.png │ │ ├── drawable-night-mdpi │ │ └── splash.png │ │ ├── drawable-night-v21 │ │ ├── background.png │ │ └── launch_background.xml │ │ ├── drawable-night-xhdpi │ │ └── splash.png │ │ ├── drawable-night-xxhdpi │ │ └── splash.png │ │ ├── drawable-night-xxxhdpi │ │ └── splash.png │ │ ├── drawable-night │ │ ├── background.png │ │ └── launch_background.xml │ │ ├── drawable-v21 │ │ ├── background.png │ │ └── launch_background.xml │ │ ├── drawable-xhdpi │ │ ├── ic_launcher_foreground.png │ │ └── splash.png │ │ ├── drawable-xxhdpi │ │ ├── ic_launcher_foreground.png │ │ └── splash.png │ │ ├── drawable-xxxhdpi │ │ ├── ic_launcher_foreground.png │ │ └── splash.png │ │ ├── drawable │ │ ├── background.png │ │ ├── launch_background.xml │ │ └── notification_icon.png │ │ ├── mipmap-anydpi-v26 │ │ └── launcher_icon.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-ldpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── playstore-icon.png │ │ ├── values-night-v31 │ │ └── styles.xml │ │ ├── values-night │ │ └── styles.xml │ │ ├── values-v31 │ │ └── styles.xml │ │ └── values │ │ ├── colors.xml │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── architecture ├── README.md └── assets │ ├── authentication.svg │ ├── e2ee.svg │ ├── key-derivation.svg │ ├── recovery.svg │ └── token-encryption.svg ├── assets ├── app_icon.png ├── build │ └── .last_build_id ├── custom-icons │ ├── _data │ │ └── custom-icons.json │ └── icons │ │ ├── 3commas.svg │ │ ├── addy_io.svg │ │ ├── anycoindirect.svg │ │ ├── ascendex.svg │ │ ├── bitfinex.svg │ │ ├── bitmex.svg │ │ ├── bitvavo.svg │ │ ├── bitwarden.svg │ │ ├── bybit.svg │ │ ├── cih.svg │ │ ├── cloudflare.svg │ │ ├── controld.svg │ │ ├── ente.svg │ │ ├── filen.svg │ │ ├── github.svg │ │ ├── google.svg │ │ ├── ing.svg │ │ ├── instagram.svg │ │ ├── jagex.svg │ │ ├── kick.svg │ │ ├── kpn.svg │ │ ├── kraken.svg │ │ ├── kronos.svg │ │ ├── kucoin.svg │ │ ├── laposte.svg │ │ ├── mastodon.svg │ │ ├── microsoft.svg │ │ ├── mozilla.svg │ │ ├── nextdns.svg │ │ ├── ngrok.svg │ │ ├── njalla.svg │ │ ├── notion.svg │ │ ├── nvidia.svg │ │ ├── odido.svg │ │ ├── parsec.svg │ │ ├── paypal.svg │ │ ├── peerberry.svg │ │ ├── pingvinshare.svg │ │ ├── plutus.svg │ │ ├── poloniex.svg │ │ ├── porkbun.svg │ │ ├── postnl.svg │ │ ├── privacy.svg │ │ ├── privacyguides.svg │ │ ├── proton.svg │ │ ├── proxmox.svg │ │ ├── revolt.svg │ │ ├── simplelogin.svg │ │ ├── skiff.svg │ │ ├── snapchat.svg │ │ ├── standardnotes.svg │ │ ├── techlore.svg │ │ ├── termius.svg │ │ ├── trading212.svg │ │ ├── tradingview.svg │ │ ├── transip.svg │ │ ├── tresorit.svg │ │ ├── tweakers.svg │ │ ├── twingate.svg │ │ ├── ubisoft.svg │ │ ├── unity.svg │ │ ├── whmcs.svg │ │ ├── windscribe.svg │ │ ├── wise.svg │ │ └── x.svg └── fonts │ ├── Inter-Bold.ttf │ ├── Inter-Light.ttf │ ├── Inter-Medium.ttf │ ├── Inter-Regular.ttf │ ├── Inter-SemiBold.ttf │ └── Montserrat-Bold.ttf ├── coverage └── lcov.info ├── fastlane └── metadata │ └── android │ └── en-US │ ├── changelogs │ ├── 23.txt │ └── 39.txt │ ├── full_description.txt │ ├── images │ ├── icon.png │ └── phoneScreenshots │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ └── 5.png │ ├── short_description.txt │ └── title.txt ├── fonts ├── Inter-Bold.ttf ├── Inter-Light.ttf ├── Inter-Medium.ttf ├── Inter-Regular.ttf ├── Inter-SemiBold.ttf └── Montserrat-Bold.ttf ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon-1024.png │ │ ├── icon-20@2x.png │ │ ├── icon-20@3x.png │ │ ├── icon-29@2x.png │ │ ├── icon-29@3x.png │ │ ├── icon-38@2x.png │ │ ├── icon-38@3x.png │ │ ├── icon-40@2x.png │ │ ├── icon-40@3x.png │ │ ├── icon-60@2x.png │ │ ├── icon-60@3x.png │ │ ├── icon-64@2x.png │ │ ├── icon-64@3x.png │ │ ├── icon-68@2x.png │ │ ├── icon-76@2x.png │ │ └── icon-83.5@2x.png │ ├── Contents.json │ ├── LaunchBackground.imageset │ │ ├── Contents.json │ │ ├── background.png │ │ └── darkbackground.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ ├── LaunchImageDark.png │ │ ├── LaunchImageDark@2x.png │ │ ├── LaunchImageDark@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ ├── Runner-Bridging-Header.h │ └── Runner.entitlements ├── l10n.yaml ├── lib ├── app.dart ├── bootstrap.dart ├── core │ ├── configuration.dart │ ├── event_bus.dart │ ├── events │ │ ├── codes_updated_event.dart │ │ ├── event.dart │ │ ├── icons_changed_event.dart │ │ └── notification_event.dart │ ├── ext │ │ └── list.dart │ └── utils │ │ ├── auth_util.dart │ │ ├── crypto_util.dart │ │ ├── data_util.dart │ │ ├── date_time_util.dart │ │ ├── debouncer.dart │ │ ├── dialog_util.dart │ │ ├── navigation_util.dart │ │ ├── toast_util.dart │ │ └── totp_util.dart ├── data │ ├── models │ │ ├── authenticator │ │ │ ├── auth_entity.dart │ │ │ ├── entity_result.dart │ │ │ └── local_auth_entity.dart │ │ ├── code.dart │ │ ├── derived_key_result.dart │ │ ├── encryption_result.dart │ │ ├── execution_states.dart │ │ ├── export │ │ │ └── ente.dart │ │ ├── protos │ │ │ ├── googleauth.pb.dart │ │ │ └── googleauth.pbenum.dart │ │ └── typedefs.dart │ ├── res │ │ ├── build_data.dart │ │ ├── components_constants.dart │ │ └── theme │ │ │ ├── colors.dart │ │ │ ├── effects.dart │ │ │ ├── ente_theme.dart │ │ │ └── text_style.dart │ ├── services │ │ ├── auth.dart │ │ ├── local_auth.dart │ │ └── preference.dart │ └── store │ │ ├── auth.dart │ │ └── code.dart ├── l10n │ ├── arb │ │ ├── app_ar.arb │ │ ├── app_de.arb │ │ ├── app_en.arb │ │ ├── app_es.arb │ │ ├── app_fa.arb │ │ ├── app_fi.arb │ │ ├── app_fr.arb │ │ ├── app_he.arb │ │ ├── app_it.arb │ │ ├── app_ja.arb │ │ ├── app_ka.arb │ │ ├── app_nl.arb │ │ ├── app_pl.arb │ │ ├── app_pt.arb │ │ ├── app_ru.arb │ │ ├── app_tr.arb │ │ ├── app_vi.arb │ │ └── app_zh.arb │ └── l10n.dart ├── locale.dart ├── main.dart ├── theme.dart └── ui │ ├── page │ ├── home.dart │ ├── qr.dart │ ├── scanner │ │ ├── gauth.dart │ │ └── general.dart │ ├── secret_key.dart │ └── settings │ │ ├── import │ │ ├── 2fas_import.dart │ │ ├── aegis_import.dart │ │ ├── bitwarden_import.dart │ │ ├── encrypted_ente_import.dart │ │ ├── google_auth_import.dart │ │ ├── import.dart │ │ ├── import_service.dart │ │ ├── import_success.dart │ │ ├── plain_text_import.dart │ │ └── raivo_plain_text_import.dart │ │ ├── language.dart │ │ └── settings.dart │ └── view │ ├── app_lock.dart │ ├── buttons │ ├── button_result.dart │ ├── button_type.dart │ ├── button_widget.dart │ └── icon_button_widget.dart │ ├── cardx.dart │ ├── code.dart │ ├── code_progress.dart │ ├── dialog.dart │ ├── expand_tile.dart │ ├── gradient_button.dart │ ├── issuer_icon.dart │ ├── lifecycle_handler.dart │ ├── loading.dart │ ├── scanner_overlay.dart │ ├── text_input.dart │ └── title_bar.dart ├── make.dart ├── migration-guides ├── authy.md ├── decrypt │ ├── crypt.go │ ├── crypt_test.go │ ├── decrypt │ ├── decrypt.go │ ├── go.mod │ └── go.sum └── encrypted_export.md ├── protos └── googleauth.proto ├── pubspec.lock ├── pubspec.yaml ├── screenshots └── screenshots.png ├── test ├── helpers │ ├── helpers.dart │ └── pump_app.dart └── models │ └── code_test.dart ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ ├── Icon-maskable-512.png │ └── favicon.png ├── index.html ├── manifest.json └── splash │ ├── img │ ├── dark-1x.png │ ├── dark-2x.png │ ├── dark-3x.png │ ├── dark-4x.png │ ├── light-1x.png │ ├── light-2x.png │ ├── light-3x.png │ └── light-4x.png │ ├── splash.js │ └── style.css └── windows ├── .gitignore ├── CMakeLists.txt ├── flutter ├── CMakeLists.txt ├── generated_plugin_registrant.cc ├── generated_plugin_registrant.h └── generated_plugins.cmake └── runner ├── CMakeLists.txt ├── Runner.rc ├── flutter_window.cpp ├── flutter_window.h ├── main.cpp ├── resource.h ├── resources └── app_icon.ico ├── runner.exe.manifest ├── utils.cpp ├── utils.h ├── win32_window.cpp └── win32_window.h /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | ## Description 10 | 11 | 12 | 13 | ## Type of Change 14 | 15 | 16 | 17 | - [ ] 🖼️ New icon 18 | - [ ] ✨ New feature (non-breaking change which adds functionality) 19 | - [ ] 🛠️ Bug fix (non-breaking change which fixes an issue) 20 | - [ ] ❌ Breaking change (fix or feature that would cause existing functionality to change) 21 | - [ ] 🧹 Code refactor 22 | - [ ] ✅ Build configuration change 23 | - [ ] 📝 Documentation 24 | - [ ] 🗑️ Chore 25 | -------------------------------------------------------------------------------- /.github/workflows/l18n-crowdin.yml: -------------------------------------------------------------------------------- 1 | name: Sync crowdin translation 2 | 3 | on: 4 | push: 5 | paths: # run action automatically when app_en.arb file is changed 6 | - 'lib/l10n/arb/app_en.arb' 7 | branches: [ main ] 8 | schedule: 9 | - cron: '0 */12 * * *' # Every 12 hours - https://crontab.guru/#0_*/12_*_*_* 10 | workflow_dispatch: # for manually running the action 11 | 12 | jobs: 13 | synchronize-with-crowdin: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v3 19 | 20 | - name: crowdin action 21 | uses: crowdin/github-action@v1 22 | with: 23 | upload_sources: true 24 | upload_translations: true 25 | download_translations: true 26 | localization_branch_name: l10n_translations 27 | create_pull_request: true 28 | skip_untranslated_strings: true 29 | pull_request_title: 'New Translations' 30 | pull_request_body: 'New translations via [Crowdin GH Action](https://github.com/crowdin/github-action)' 31 | pull_request_base_branch_name: 'main' 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} 35 | CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | 39 | android/key.properties 40 | android/app/*.key 41 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | # [submodule "thirdparty/sentry-dart"] 2 | # path = thirdparty/sentry-dart 3 | # url = https://github.com/ente-io/sentry-dart.git 4 | # branch = sentry_flutter_ente 5 | # [submodule "flutter"] 6 | # path = flutter 7 | # url = https://github.com/flutter/flutter.git 8 | # branch = stable 9 | [submodule "assets/simple-icons"] 10 | path = assets/simple-icons 11 | url = https://github.com/simple-icons/simple-icons.git 12 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851 8 | channel: unknown 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851 17 | base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851 18 | - platform: linux 19 | create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851 20 | base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851 21 | - platform: macos 22 | create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851 23 | base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851 24 | - platform: windows 25 | create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851 26 | base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851 27 | 28 | # User provided section 29 | 30 | # List of Local paths (relative to this file) that should be 31 | # ignored by the migrate tool. 32 | # 33 | # Files that are not part of the templates will be ignored by default. 34 | unmanaged_files: 35 | - 'lib/main.dart' 36 | - 'ios/Runner.xcodeproj/project.pbxproj' 37 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dart-code.dart-code", 6 | "dart-code.flutter", 7 | "felixangelov.bloc" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "ente-2fa", 9 | "request": "launch", 10 | "type": "dart", 11 | }, 12 | { 13 | "name": "ente-2fa (profile mode)", 14 | "request": "launch", 15 | "type": "dart", 16 | "flutterMode": "profile", 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flutter_2fa 2 | 3 | Which helps you generate and store 2 step verification (2FA) tokens on your mobile devices. 4 | 5 | ![App Screenshots](./screenshots/screenshots.png) 6 | 7 | ## ✨ Features 8 | 9 | According to the [origin ente Auth](https://github.com/ente-io/auth) this forked version has the following features: 10 | - Support pure offline mode. No ads, no analytics, no data collection, no tracking. 11 | - Remove useless features (web browser & etc.) 12 | - More optimizations 13 | 14 | ## 🙋‍♂️ Help 15 | Please create a GitHub issue / discussion if you have any questions or problems. 16 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ente believes that working with security researchers across the globe is crucial 2 | to keeping our users safe. If you believe you've found a security issue in our 3 | product or service, we encourage you to notify us (security@ente.io). We welcome 4 | working with you to resolve the issue promptly. Thanks in advance! 5 | 6 | # Disclosure Policy 7 | 8 | - Let us know as soon as possible upon discovery of a potential security issue, 9 | and we'll make every effort to quickly resolve the issue. 10 | - Provide us a reasonable amount of time to resolve the issue before any 11 | disclosure to the public or a third-party. We may publicly disclose the issue 12 | before resolving it, if appropriate. 13 | - Make a good faith effort to avoid privacy violations, destruction of data, and 14 | interruption or degradation of our service. Only interact with accounts you 15 | own or with explicit permission of the account holder. 16 | - If you would like to encrypt your report, please use the PGP key with long ID 17 | `E273695C0403F34F74171932DF6DDDE98EBD2394` (available in the public keyserver 18 | pool). 19 | 20 | # In-scope 21 | 22 | - Security issues in any current release of ente. This includes the web app, 23 | desktop app, and mobile apps (iOS and Android). Product downloads are 24 | available at https://ente.io. Source code is available at 25 | https://github.com/ente-io. 26 | 27 | # Exclusions 28 | 29 | The following bug classes are out-of scope: 30 | 31 | - Bugs that are already reported on any of ente's issue trackers 32 | (https://github.com/ente-io), or that we already know of. Note that some of 33 | our issue tracking is private. 34 | - Issues in an upstream software dependency (ex: Flutter, Next.js etc) which are 35 | already reported to the upstream maintainer. 36 | - Attacks requiring physical access to a user's device. 37 | - Self-XSS 38 | - Issues related to software or protocols not under ente's control 39 | - Vulnerabilities in outdated versions of ente 40 | - Missing security best practices that do not directly lead to a vulnerability 41 | - Issues that do not have any impact on the general public 42 | 43 | While researching, we'd like to ask you to refrain from: 44 | 45 | - Denial of service 46 | - Spamming 47 | - Social engineering (including phishing) of ente staff or contractors 48 | - Any physical attempts against ente property or data centers 49 | 50 | Thank you for helping keep ente and our users safe! 51 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # For more linters, we can check https://dart-lang.github.io/linter/lints/index.html 2 | # or https://pub.dev/packages/lint (Effective dart) 3 | # use "flutter analyze ." or "dart analyze ." for running lint checks 4 | 5 | include: package:lints/recommended.yaml 6 | linter: 7 | rules: 8 | # Ref https://github.com/flutter/packages/blob/master/packages/flutter_lints/lib/flutter.yaml 9 | - avoid_print 10 | - avoid_unnecessary_containers 11 | - avoid_web_libraries_in_flutter 12 | - no_logic_in_create_state 13 | - prefer_const_constructors 14 | - prefer_const_constructors_in_immutables 15 | - prefer_const_declarations 16 | - prefer_const_literals_to_create_immutables 17 | - require_trailing_commas 18 | - sized_box_for_whitespace 19 | - use_full_hex_values_for_flutter_colors 20 | - use_key_in_widget_constructors 21 | 22 | - avoid_empty_else 23 | - exhaustive_cases 24 | 25 | # just style suggestions 26 | - sort_pub_dependencies 27 | - use_rethrow_when_possible 28 | - directives_ordering 29 | - always_use_package_imports 30 | 31 | analyzer: 32 | exclude: 33 | - thirdparty/** 34 | - flutter/** -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 12 | 13 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 55 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/io/ente/authenticator/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package tech.lolli.twofa 2 | 3 | import io.flutter.embedding.android.FlutterFragmentActivity 4 | 5 | class MainActivity: FlutterFragmentActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/android/app/src/main/res/drawable-hdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/android/app/src/main/res/drawable-mdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/android/app/src/main/res/drawable-night-hdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/android/app/src/main/res/drawable-night-mdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/android/app/src/main/res/drawable-night-v21/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/android/app/src/main/res/drawable-night-xhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/android/app/src/main/res/drawable-night-xxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/android/app/src/main/res/drawable-night-xxxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/android/app/src/main/res/drawable-night/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/android/app/src/main/res/drawable-v21/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/android/app/src/main/res/drawable-xhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/android/app/src/main/res/drawable-xxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/android/app/src/main/res/drawable-xxxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/android/app/src/main/res/drawable/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/notification_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/android/app/src/main/res/drawable/notification_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/android/app/src/main/res/mipmap-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/playstore-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/android/app/src/main/res/playstore-icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night-v31/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 16 | 19 | 20 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-v31/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 16 | 19 | 20 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #000000 4 | #ffffff 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.8.22' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.1.2' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | tasks.register("clean", Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /assets/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/assets/app_icon.png -------------------------------------------------------------------------------- /assets/build/.last_build_id: -------------------------------------------------------------------------------- 1 | f23611a675a8c9bc71eb16e8a1108cf8 -------------------------------------------------------------------------------- /assets/custom-icons/icons/anycoindirect.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/ascendex.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/bitfinex.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/bitmex.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/bitwarden.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/bybit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/cih.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/cloudflare.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/ente.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/github.svg: -------------------------------------------------------------------------------- 1 | GitHub -------------------------------------------------------------------------------- /assets/custom-icons/icons/google.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/jagex.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/laposte.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/mastodon.svg: -------------------------------------------------------------------------------- 1 | Mastodon -------------------------------------------------------------------------------- /assets/custom-icons/icons/microsoft.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/nextdns.svg: -------------------------------------------------------------------------------- 1 | > -------------------------------------------------------------------------------- /assets/custom-icons/icons/ngrok.svg: -------------------------------------------------------------------------------- 1 | ngrok -------------------------------------------------------------------------------- /assets/custom-icons/icons/njalla.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/notion.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/nvidia.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/parsec.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/paypal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/pingvinshare.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/privacy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/proton.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/proxmox.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/revolt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/simplelogin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/termius.svg: -------------------------------------------------------------------------------- 1 | Termius -------------------------------------------------------------------------------- /assets/custom-icons/icons/trading212.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/tradingview.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/transip.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/tresorit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/twingate.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/ubisoft.svg: -------------------------------------------------------------------------------- 1 | Ubisoft -------------------------------------------------------------------------------- /assets/custom-icons/icons/unity.svg: -------------------------------------------------------------------------------- 1 | Unity -------------------------------------------------------------------------------- /assets/custom-icons/icons/whmcs.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/windscribe.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /assets/custom-icons/icons/x.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/fonts/Inter-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/assets/fonts/Inter-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/Inter-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/assets/fonts/Inter-Light.ttf -------------------------------------------------------------------------------- /assets/fonts/Inter-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/assets/fonts/Inter-Medium.ttf -------------------------------------------------------------------------------- /assets/fonts/Inter-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/assets/fonts/Inter-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/Inter-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/assets/fonts/Inter-SemiBold.ttf -------------------------------------------------------------------------------- /assets/fonts/Montserrat-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/assets/fonts/Montserrat-Bold.ttf -------------------------------------------------------------------------------- /coverage/lcov.info: -------------------------------------------------------------------------------- 1 | SF:lib/app/view/app.dart 2 | DA:7,1 3 | DA:9,1 4 | DA:11,1 5 | DA:12,1 6 | DA:14,1 7 | LF:5 8 | LH:5 9 | end_of_record 10 | SF:lib/l10n/l10n.dart 11 | DA:7,2 12 | LF:1 13 | LH:1 14 | end_of_record 15 | SF:lib/counter/cubit/counter_cubit.dart 16 | DA:4,2 17 | DA:6,0 18 | DA:8,0 19 | LF:3 20 | LH:1 21 | end_of_record 22 | SF:lib/counter/view/counter_page.dart 23 | DA:7,1 24 | DA:9,1 25 | DA:11,1 26 | DA:12,2 27 | DA:19,1 28 | DA:21,1 29 | DA:23,1 30 | DA:24,1 31 | DA:25,3 32 | DA:27,1 33 | DA:30,1 34 | DA:31,1 35 | DA:32,0 36 | DA:36,1 37 | DA:37,0 38 | DA:47,1 39 | DA:49,1 40 | DA:51,1 41 | DA:52,3 42 | DA:53,4 43 | LF:20 44 | LH:18 45 | end_of_record 46 | SF:lib/main.dart 47 | DA:3,0 48 | DA:4,0 49 | DA:8,2 50 | DA:11,1 51 | DA:13,1 52 | DA:15,1 53 | DA:33,1 54 | DA:46,1 55 | DA:47,1 56 | DA:53,1 57 | DA:54,2 58 | DA:60,2 59 | DA:64,1 60 | DA:72,1 61 | DA:73,1 62 | DA:76,3 63 | DA:78,1 64 | DA:81,1 65 | DA:97,1 66 | DA:101,1 67 | DA:102,2 68 | DA:103,3 69 | DA:108,1 70 | DA:109,1 71 | LF:24 72 | LH:22 73 | end_of_record 74 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/23.txt: -------------------------------------------------------------------------------- 1 | - Hello, FDroid! -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/39.txt: -------------------------------------------------------------------------------- 1 | - Added OTPAuth URL Scheme Support: Our Two-Factor Authenticator app now supports the otpauth:// URL scheme for easy setup and configuration of your one-time password (OTP) accounts. 2 | 3 | - Expanded Language Support: Thanks to the help from our community, in addition to English, French, and Spanish, our app now supports German, Spanish, Dutch, Brazilian Portuguese, Russian, and Simplified Chinese. To change your language preferences, go to Settings > Account > Language. -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 | ente's Authenticator app helps you generate and store 2 step verification (2FA) 2 | tokens on your mobile devices. 3 | 4 | 5 | FEATURES 6 | 7 | - Secure Backups 8 | ente provides end-to-end encrypted cloud backups so that you don't have to worry 9 | about losing your tokens. We use the same protocols ente Photos uses to encrypt 10 | and preserve your data. 11 | 12 | - Multi Device Synchronization 13 | ente will automatically sync the 2FA tokens you add to your account, across all 14 | your devices. Every new device you sign into will have access to these tokens. 15 | 16 | - Web access 17 | You can access your 2FA code from any web browser by visiting https://auth.ente.io . 18 | 19 | - Offline Mode 20 | ente generates 2FA tokens offline, so your network connectivity will not get in 21 | the way of your workflow. 22 | 23 | - Import and Export Tokens 24 | You can add tokens to ente by one of the following methods: 25 | 1. Scanning a QR code 26 | 2. Manually entering (copy-pasting) a 2FA secret 27 | 3. Bulk importing from a file that contains a list of codes in the following format: 28 | 29 | otpauth://totp/provider.com:you@email.com?secret=YOUR_SECRET 30 | 31 | The codes maybe separated by new lines or commas. 32 | 33 | You can also export the codes you have added to ente, to an **unencrypted** text 34 | file, that adheres to the above format. 35 | 36 | 37 | SUPPORT 38 | 39 | If you need help, please reach out to support@ente.io, and a human will get in touch with you. 40 | If you have feature requests, please create an issue @ https://github.com/ente-io/auth 41 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/fastlane/metadata/android/en-US/images/icon.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | ente is an end-to-end encrypted authenticator app -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/title.txt: -------------------------------------------------------------------------------- 1 | ente Authenticator -------------------------------------------------------------------------------- /fonts/Inter-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/fonts/Inter-Bold.ttf -------------------------------------------------------------------------------- /fonts/Inter-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/fonts/Inter-Light.ttf -------------------------------------------------------------------------------- /fonts/Inter-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/fonts/Inter-Medium.ttf -------------------------------------------------------------------------------- /fonts/Inter-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/fonts/Inter-Regular.ttf -------------------------------------------------------------------------------- /fonts/Inter-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/fonts/Inter-SemiBold.ttf -------------------------------------------------------------------------------- /fonts/Montserrat-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/fonts/Montserrat-Bold.ttf -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | build/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/app.flx 23 | Flutter/app.zip 24 | Flutter/flutter_assets/ 25 | Flutter/flutter_export_environment.sh 26 | ServiceDefinitions.json 27 | Runner/GeneratedPluginRegistrant.* 28 | 29 | # Exceptions to above rules. 30 | !default.mode1v3 31 | !default.mode2v3 32 | !default.pbxuser 33 | !default.perspectivev3 34 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '12.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | # Used for flutter lib "file_picker" 31 | Pod::PICKER_MEDIA = false 32 | Pod::PICKER_AUDIO = false 33 | 34 | target 'Runner' do 35 | use_frameworks! 36 | use_modular_headers! 37 | 38 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 39 | end 40 | 41 | post_install do |installer| 42 | installer.pods_project.targets.each do |target| 43 | flutter_additional_ios_build_settings(target) 44 | target.build_configurations.each do |config| 45 | config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET' 46 | config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64' 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-38@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-38@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-38@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-38@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-64@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-64@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-64@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-64@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-68@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-68@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "background.png", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "filename" : "darkbackground.png", 15 | "idiom" : "universal" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "LaunchImage.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "appearances" : [ 10 | { 11 | "appearance" : "luminosity", 12 | "value" : "dark" 13 | } 14 | ], 15 | "filename" : "LaunchImageDark.png", 16 | "idiom" : "universal", 17 | "scale" : "1x" 18 | }, 19 | { 20 | "filename" : "LaunchImage@2x.png", 21 | "idiom" : "universal", 22 | "scale" : "2x" 23 | }, 24 | { 25 | "appearances" : [ 26 | { 27 | "appearance" : "luminosity", 28 | "value" : "dark" 29 | } 30 | ], 31 | "filename" : "LaunchImageDark@2x.png", 32 | "idiom" : "universal", 33 | "scale" : "2x" 34 | }, 35 | { 36 | "filename" : "LaunchImage@3x.png", 37 | "idiom" : "universal", 38 | "scale" : "3x" 39 | }, 40 | { 41 | "appearances" : [ 42 | { 43 | "appearance" : "luminosity", 44 | "value" : "dark" 45 | } 46 | ], 47 | "filename" : "LaunchImageDark@3x.png", 48 | "idiom" : "universal", 49 | "scale" : "3x" 50 | } 51 | ], 52 | "info" : { 53 | "author" : "xcode", 54 | "version" : 1 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CADisableMinimumFrameDurationOnPhone 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleDisplayName 10 | auth 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleLocalizations 18 | 19 | en 20 | es 21 | 22 | CFBundleName 23 | auth 24 | CFBundlePackageType 25 | APPL 26 | CFBundleShortVersionString 27 | $(FLUTTER_BUILD_NAME) 28 | CFBundleSignature 29 | ???? 30 | CFBundleURLTypes 31 | 32 | 33 | CFBundleTypeRole 34 | Editor 35 | CFBundleURLName 36 | $(PRODUCT_BUNDLE_IDENTIFIER) 37 | CFBundleURLSchemes 38 | 39 | otpauth 40 | 41 | 42 | 43 | CFBundleVersion 44 | $(FLUTTER_BUILD_NUMBER) 45 | ITSAppUsesNonExemptEncryption 46 | 47 | LSRequiresIPhoneOS 48 | 49 | MinimumOSVersion 50 | 12.0 51 | NSCameraUsageDescription 52 | This app needs camera access to scan QR codes 53 | NSFaceIDUsageDescription 54 | Please allow auth to lock itself with FaceID or TouchID 55 | NSPhotoLibraryUsageDescription 56 | Please allow auth to pick a file to import data from 57 | UIApplicationSupportsIndirectInputEvents 58 | 59 | UILaunchStoryboardName 60 | LaunchScreen 61 | UIMainStoryboardFile 62 | Main 63 | UIStatusBarHidden 64 | 65 | UISupportedInterfaceOrientations 66 | 67 | UIInterfaceOrientationPortrait 68 | UIInterfaceOrientationLandscapeLeft 69 | UIInterfaceOrientationLandscapeRight 70 | 71 | UISupportedInterfaceOrientations~ipad 72 | 73 | UIInterfaceOrientationPortrait 74 | UIInterfaceOrientationPortraitUpsideDown 75 | UIInterfaceOrientationLandscapeLeft 76 | UIInterfaceOrientationLandscapeRight 77 | 78 | UIViewControllerBasedStatusBarAppearance 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/Runner/Runner.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | production 7 | 8 | 9 | -------------------------------------------------------------------------------- /l10n.yaml: -------------------------------------------------------------------------------- 1 | arb-dir: lib/l10n/arb 2 | template-arb-file: app_en.arb 3 | output-localization-file: app_localizations.dart 4 | nullable-getter: false 5 | -------------------------------------------------------------------------------- /lib/app.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:adaptive_theme/adaptive_theme.dart'; 4 | import "package:ente_auth/l10n/l10n.dart"; 5 | import 'package:ente_auth/locale.dart'; 6 | import 'package:ente_auth/theme.dart'; 7 | import 'package:ente_auth/ui/page/home.dart'; 8 | import 'package:flutter/foundation.dart'; 9 | import "package:flutter/material.dart"; 10 | import 'package:flutter_localizations/flutter_localizations.dart'; 11 | 12 | class App extends StatefulWidget { 13 | final Locale? locale; 14 | const App({super.key, this.locale}); 15 | 16 | static void setLocale(BuildContext context, Locale newLocale) { 17 | _AppState state = context.findAncestorStateOfType<_AppState>()!; 18 | state.setLocale(newLocale); 19 | } 20 | 21 | @override 22 | State createState() => _AppState(); 23 | } 24 | 25 | class _AppState extends State { 26 | Locale? locale; 27 | 28 | void setLocale(Locale newLocale) { 29 | setState(() { 30 | locale = newLocale; 31 | }); 32 | } 33 | 34 | @override 35 | void initState() { 36 | locale = widget.locale; 37 | super.initState(); 38 | } 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | if (Platform.isAndroid || kDebugMode) { 43 | return AdaptiveTheme( 44 | light: lightThemeData, 45 | dark: darkThemeData, 46 | initial: AdaptiveThemeMode.system, 47 | builder: (lightTheme, dartTheme) => MaterialApp( 48 | title: "2fa", 49 | themeMode: ThemeMode.system, 50 | theme: lightTheme, 51 | darkTheme: dartTheme, 52 | debugShowCheckedModeBanner: false, 53 | locale: locale, 54 | supportedLocales: appSupportedLocales, 55 | localeListResolutionCallback: localResolutionCallBack, 56 | localizationsDelegates: const [ 57 | AppLocalizations.delegate, 58 | GlobalMaterialLocalizations.delegate, 59 | GlobalCupertinoLocalizations.delegate, 60 | GlobalWidgetsLocalizations.delegate, 61 | ], 62 | home: const HomePage(), 63 | ), 64 | ); 65 | } else { 66 | return MaterialApp( 67 | title: "2fa", 68 | themeMode: ThemeMode.system, 69 | theme: lightThemeData, 70 | darkTheme: darkThemeData, 71 | debugShowCheckedModeBanner: false, 72 | locale: locale, 73 | supportedLocales: appSupportedLocales, 74 | localeListResolutionCallback: localResolutionCallBack, 75 | localizationsDelegates: const [ 76 | AppLocalizations.delegate, 77 | GlobalMaterialLocalizations.delegate, 78 | GlobalCupertinoLocalizations.delegate, 79 | GlobalWidgetsLocalizations.delegate, 80 | ], 81 | home: const HomePage(), 82 | ); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/bootstrap.dart: -------------------------------------------------------------------------------- 1 | import "dart:async"; 2 | import "dart:developer"; 3 | 4 | import "package:bloc/bloc.dart"; 5 | import "package:flutter/widgets.dart"; 6 | 7 | class AppBlocObserver extends BlocObserver { 8 | @override 9 | void onChange(BlocBase bloc, Change change) { 10 | super.onChange(bloc, change); 11 | log("onChange(${bloc.runtimeType}, $change)"); 12 | } 13 | 14 | @override 15 | void onError(BlocBase bloc, Object error, StackTrace stackTrace) { 16 | log("onError(${bloc.runtimeType}, $error, $stackTrace)"); 17 | super.onError(bloc, error, stackTrace); 18 | } 19 | } 20 | 21 | Future bootstrap(FutureOr Function() builder) async { 22 | FlutterError.onError = (details) { 23 | log(details.exceptionAsString(), stackTrace: details.stack); 24 | }; 25 | 26 | await runZonedGuarded( 27 | () async { 28 | Bloc.observer = AppBlocObserver(); 29 | runApp(await builder()); 30 | }, 31 | (error, stackTrace) => log(error.toString(), stackTrace: stackTrace), 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /lib/core/event_bus.dart: -------------------------------------------------------------------------------- 1 | import 'package:event_bus/event_bus.dart'; 2 | 3 | class Bus { 4 | static final EventBus instance = EventBus(); 5 | } 6 | -------------------------------------------------------------------------------- /lib/core/events/codes_updated_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:ente_auth/core/events/event.dart'; 2 | 3 | class CodesUpdatedEvent extends Event {} 4 | -------------------------------------------------------------------------------- /lib/core/events/event.dart: -------------------------------------------------------------------------------- 1 | class Event {} 2 | -------------------------------------------------------------------------------- /lib/core/events/icons_changed_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:ente_auth/core/events/event.dart'; 2 | 3 | class IconsChangedEvent extends Event {} 4 | -------------------------------------------------------------------------------- /lib/core/events/notification_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:ente_auth/core/events/event.dart'; 2 | 3 | // NotificationEvent event is used to re-fresh the UI to show latest notification 4 | // (if any) 5 | class NotificationEvent extends Event {} 6 | -------------------------------------------------------------------------------- /lib/core/ext/list.dart: -------------------------------------------------------------------------------- 1 | extension ListX on List { 2 | List joinWith(T item, [bool self = true]) { 3 | final list = self ? this : List.from(this); 4 | for (var i = length - 1; i > 0; i--) { 5 | list.insert(i, item); 6 | } 7 | return list; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/core/utils/auth_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:ente_auth/l10n/l10n.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:local_auth/local_auth.dart'; 4 | import 'package:local_auth_android/local_auth_android.dart'; 5 | import 'package:local_auth_ios/types/auth_messages_ios.dart'; 6 | import 'package:logging/logging.dart'; 7 | 8 | Future requestAuthentication(BuildContext context, String reason) async { 9 | Logger("AuthUtil").info("Requesting authentication"); 10 | await LocalAuthentication().stopAuthentication(); 11 | final l10n = context.l10n; 12 | return await LocalAuthentication().authenticate( 13 | localizedReason: reason, 14 | authMessages: [ 15 | AndroidAuthMessages( 16 | biometricHint: l10n.androidBiometricHint, 17 | biometricNotRecognized: l10n.androidBiometricNotRecognized, 18 | biometricRequiredTitle: l10n.androidBiometricRequiredTitle, 19 | biometricSuccess: l10n.androidBiometricSuccess, 20 | cancelButton: l10n.androidCancelButton, 21 | deviceCredentialsRequiredTitle: 22 | l10n.androidDeviceCredentialsRequiredTitle, 23 | deviceCredentialsSetupDescription: 24 | l10n.androidDeviceCredentialsSetupDescription, 25 | goToSettingsButton: l10n.goToSettings, 26 | goToSettingsDescription: l10n.androidGoToSettingsDescription, 27 | signInTitle: l10n.androidSignInTitle, 28 | ), 29 | IOSAuthMessages( 30 | goToSettingsButton: l10n.goToSettings, 31 | goToSettingsDescription: l10n.goToSettings, 32 | lockOut: l10n.iOSLockOut, 33 | // cancelButton default value is "Ok" 34 | cancelButton: l10n.iOSOkButton, 35 | ), 36 | ], 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /lib/core/utils/data_util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | double convertBytesToGBs(final int bytes, {int precision = 2}) { 4 | return double.parse( 5 | (bytes / (1024 * 1024 * 1024)).toStringAsFixed(precision), 6 | ); 7 | } 8 | 9 | final storageUnits = ["bytes", "KB", "MB", "GB"]; 10 | 11 | String convertBytesToReadableFormat(int bytes) { 12 | int storageUnitIndex = 0; 13 | while (bytes >= 1024 && storageUnitIndex < storageUnits.length - 1) { 14 | storageUnitIndex++; 15 | bytes = (bytes / 1024).round(); 16 | } 17 | return "$bytes ${storageUnits[storageUnitIndex]}"; 18 | } 19 | 20 | String formatBytes(int bytes, [int decimals = 2]) { 21 | if (bytes == 0) return '0 bytes'; 22 | const k = 1024; 23 | final int dm = decimals < 0 ? 0 : decimals; 24 | final int i = (log(bytes) / log(k)).floor(); 25 | return '${(bytes / pow(k, i)).toStringAsFixed(dm)} ${storageUnits[i]}'; 26 | } 27 | -------------------------------------------------------------------------------- /lib/core/utils/debouncer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | class Debouncer { 6 | final Duration _duration; 7 | final ValueNotifier _debounceActiveNotifier = ValueNotifier(false); 8 | Timer? _debounceTimer; 9 | 10 | Debouncer(this._duration); 11 | 12 | void run(Future Function() fn) { 13 | if (isActive()) { 14 | _debounceTimer!.cancel(); 15 | } 16 | _debounceTimer = Timer(_duration, () async { 17 | await fn(); 18 | _debounceActiveNotifier.value = false; 19 | }); 20 | _debounceActiveNotifier.value = true; 21 | } 22 | 23 | void cancelDebounce() { 24 | if (_debounceTimer != null) { 25 | _debounceTimer!.cancel(); 26 | } 27 | } 28 | 29 | bool isActive() => _debounceTimer != null && _debounceTimer!.isActive; 30 | 31 | ValueNotifier get debounceActiveNotifier { 32 | return _debounceActiveNotifier; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/core/utils/navigation_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | Future routeToPage( 4 | BuildContext context, 5 | Widget page, { 6 | bool forceCustomPageRoute = false, 7 | }) { 8 | return Navigator.of(context).push( 9 | MaterialPageRoute( 10 | builder: (context) { 11 | return page; 12 | }, 13 | ), 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /lib/core/utils/toast_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:ente_auth/theme.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:fluttertoast/fluttertoast.dart'; 4 | 5 | Future showToast( 6 | BuildContext context, 7 | String message, { 8 | toastLength = Toast.LENGTH_LONG, 9 | iOSDismissOnTap = true, 10 | }) async { 11 | await Fluttertoast.cancel(); 12 | return Fluttertoast.showToast( 13 | msg: message, 14 | toastLength: toastLength, 15 | gravity: ToastGravity.BOTTOM, 16 | timeInSecForIosWeb: 1, 17 | backgroundColor: Theme.of(context).colorScheme.toastBackgroundColor, 18 | textColor: Theme.of(context).colorScheme.toastTextColor, 19 | ); 20 | } 21 | 22 | Future showShortToast(context, String message) { 23 | return showToast(context, message, toastLength: Toast.LENGTH_SHORT); 24 | } 25 | -------------------------------------------------------------------------------- /lib/core/utils/totp_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:ente_auth/data/models/code.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:otp/otp.dart' as otp; 4 | 5 | String getOTP(Code code) { 6 | if (code.type == Type.hotp) { 7 | return _getHOTPCode(code); 8 | } 9 | return otp.OTP.generateTOTPCodeString( 10 | getSanitizedSecret(code.secret), 11 | DateTime.now().millisecondsSinceEpoch, 12 | length: code.digits, 13 | interval: code.period, 14 | algorithm: _getAlgorithm(code), 15 | isGoogle: true, 16 | ); 17 | } 18 | 19 | String _getHOTPCode(Code code) { 20 | return otp.OTP.generateHOTPCodeString( 21 | getSanitizedSecret(code.secret), 22 | code.counter, 23 | length: code.digits, 24 | algorithm: _getAlgorithm(code), 25 | isGoogle: true, 26 | ); 27 | } 28 | 29 | String getNextTotp(Code code) { 30 | return otp.OTP.generateTOTPCodeString( 31 | getSanitizedSecret(code.secret), 32 | DateTime.now().millisecondsSinceEpoch + code.period * 1000, 33 | length: code.digits, 34 | interval: code.period, 35 | algorithm: _getAlgorithm(code), 36 | isGoogle: true, 37 | ); 38 | } 39 | 40 | otp.Algorithm _getAlgorithm(Code code) { 41 | switch (code.algorithm) { 42 | case Algorithm.sha256: 43 | return otp.Algorithm.SHA256; 44 | case Algorithm.sha512: 45 | return otp.Algorithm.SHA512; 46 | default: 47 | return otp.Algorithm.SHA1; 48 | } 49 | } 50 | 51 | String getSanitizedSecret(String secret) { 52 | return secret.toUpperCase().trim().replaceAll(' ', ''); 53 | } 54 | 55 | String safeDecode(String value) { 56 | try { 57 | return Uri.decodeComponent(value); 58 | } catch (e) { 59 | // note: don't log the value, it might contain sensitive information 60 | debugPrint("Failed to decode $e"); 61 | return value; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/data/models/authenticator/auth_entity.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | @immutable 6 | class AuthEntity { 7 | final String id; 8 | // encryptedData will be null for diff items when item is deleted 9 | final String? encryptedData; 10 | final String? header; 11 | final bool isDeleted; 12 | final int createdAt; 13 | final int updatedAt; 14 | 15 | const AuthEntity( 16 | this.id, 17 | this.encryptedData, 18 | this.header, 19 | this.isDeleted, 20 | this.createdAt, 21 | this.updatedAt, 22 | ); 23 | 24 | Map toMap() { 25 | return { 26 | 'id': id, 27 | 'encryptedData': encryptedData, 28 | 'header': header, 29 | 'isDeleted': isDeleted, 30 | 'createdAt': createdAt, 31 | 'updatedAt': updatedAt, 32 | }; 33 | } 34 | 35 | factory AuthEntity.fromMap(Map map) { 36 | return AuthEntity( 37 | map['id'], 38 | map['encryptedData'], 39 | map['header'], 40 | map['isDeleted']!, 41 | map['createdAt']!, 42 | map['updatedAt']!, 43 | ); 44 | } 45 | 46 | String toJson() => json.encode(toMap()); 47 | 48 | factory AuthEntity.fromJson(String source) => 49 | AuthEntity.fromMap(json.decode(source)); 50 | } 51 | -------------------------------------------------------------------------------- /lib/data/models/authenticator/entity_result.dart: -------------------------------------------------------------------------------- 1 | class EntityResult { 2 | final int generatedID; 3 | final String rawData; 4 | final bool hasSynced; 5 | 6 | EntityResult(this.generatedID, this.rawData, this.hasSynced); 7 | } 8 | -------------------------------------------------------------------------------- /lib/data/models/authenticator/local_auth_entity.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | @immutable 6 | class LocalAuthEntity { 7 | final int generatedID; 8 | 9 | // id can be null if a code has been scanned locally but it's yet to be 10 | // synced with the remote server. 11 | final String? id; 12 | final String encryptedData; 13 | final String header; 14 | 15 | // createdAt and updateAt will be equal to local time of creation or updation 16 | // till remote sync is completed. 17 | final int createdAt; 18 | final int updatedAt; 19 | 20 | // shouldSync indicates that the entry was locally created or updated. The 21 | // app should try to sync it to the server during next sync 22 | final bool shouldSync; 23 | 24 | const LocalAuthEntity( 25 | this.generatedID, 26 | this.id, 27 | this.encryptedData, 28 | this.header, 29 | this.createdAt, 30 | this.updatedAt, 31 | this.shouldSync, 32 | ); 33 | 34 | LocalAuthEntity copyWith({ 35 | int? generatedID, 36 | String? id, 37 | String? encryptedData, 38 | String? header, 39 | int? createdAt, 40 | int? updatedAt, 41 | bool? shouldSync, 42 | }) { 43 | return LocalAuthEntity( 44 | generatedID ?? this.generatedID, 45 | id ?? this.id, 46 | encryptedData ?? this.encryptedData, 47 | header ?? this.header, 48 | createdAt ?? this.createdAt, 49 | updatedAt ?? this.updatedAt, 50 | shouldSync ?? this.shouldSync, 51 | ); 52 | } 53 | 54 | Map toMap() { 55 | return { 56 | '_generatedID': generatedID, 57 | 'id': id, 58 | 'encryptedData': encryptedData, 59 | 'header': header, 60 | 'createdAt': createdAt, 61 | 'updatedAt': updatedAt, 62 | // sqlite doesn't support bool type. map true to 1 and false to 0 63 | 'shouldSync': shouldSync ? 1 : 0, 64 | }; 65 | } 66 | 67 | factory LocalAuthEntity.fromMap(Map map) { 68 | return LocalAuthEntity( 69 | map['_generatedID']!, 70 | map['id'], 71 | map['encryptedData']!, 72 | map['header']!, 73 | map['createdAt']!, 74 | map['updatedAt']!, 75 | (map['shouldSync']! == 0) ? false : true, 76 | ); 77 | } 78 | 79 | String toJson() => json.encode(toMap()); 80 | 81 | factory LocalAuthEntity.fromJson(String source) => 82 | LocalAuthEntity.fromMap(json.decode(source)); 83 | } 84 | -------------------------------------------------------------------------------- /lib/data/models/derived_key_result.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | class DerivedKeyResult { 4 | final Uint8List key; 5 | final int memLimit; 6 | final int opsLimit; 7 | 8 | DerivedKeyResult(this.key, this.memLimit, this.opsLimit); 9 | } 10 | -------------------------------------------------------------------------------- /lib/data/models/encryption_result.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | class EncryptionResult { 4 | final Uint8List? encryptedData; 5 | final Uint8List? key; 6 | final Uint8List? header; 7 | final Uint8List? nonce; 8 | 9 | EncryptionResult({ 10 | this.encryptedData, 11 | this.key, 12 | this.header, 13 | this.nonce, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /lib/data/models/execution_states.dart: -------------------------------------------------------------------------------- 1 | enum ExecutionState { idle, inProgress, error, successful } 2 | -------------------------------------------------------------------------------- /lib/data/models/export/ente.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Version: 1.0 3 | KDF Algo: ARGON2ID 4 | Decrypted Data Format: It contains code.rawData [1] separated by new line. 5 | [1] otpauth://totp/provider.com:you@email.com?secret=YOUR_SECRET 6 | */ 7 | 8 | class EnteAuthExport { 9 | final int version; 10 | final KDFParams kdfParams; 11 | final String encryptedData; 12 | final String encryptionNonce; 13 | 14 | // Named constructor which can be used to specify each field individually 15 | EnteAuthExport({ 16 | required this.version, 17 | required this.kdfParams, 18 | required this.encryptedData, 19 | required this.encryptionNonce, 20 | }); 21 | 22 | // Convert EnteExport object to JSON 23 | Map toJson() => { 24 | 'version': version, 25 | 'kdfParams': kdfParams.toJson(), 26 | 'encryptedData': encryptedData, 27 | 'encryptionNonce': encryptionNonce, 28 | }; 29 | 30 | // Convert JSON to EnteExport object 31 | static EnteAuthExport fromJson(Map json) => EnteAuthExport( 32 | version: json['version'], 33 | kdfParams: KDFParams.fromJson(json['kdfParams']), 34 | encryptedData: json['encryptedData'], 35 | encryptionNonce: json['encryptionNonce'], 36 | ); 37 | } 38 | 39 | // KDFParams is a class that holds the parameters for the KDF function. 40 | // It is used to derive a key from a password. 41 | class KDFParams { 42 | final int memLimit; 43 | final int opsLimit; 44 | final String salt; 45 | 46 | // Named constructor which can be used to specify each field individually 47 | KDFParams({ 48 | required this.memLimit, 49 | required this.opsLimit, 50 | required this.salt, 51 | }); 52 | 53 | // Convert KDFParams object to JSON 54 | Map toJson() => { 55 | 'memLimit': memLimit, 56 | 'opsLimit': opsLimit, 57 | 'salt': salt, 58 | }; 59 | 60 | // Convert JSON to KDFParams object 61 | static KDFParams fromJson(Map json) => KDFParams( 62 | memLimit: json['memLimit'], 63 | opsLimit: json['opsLimit'], 64 | salt: json['salt'], 65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /lib/data/models/typedefs.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | typedef FutureVoidCallback = Future Function(); 4 | typedef BoolCallBack = bool Function(); 5 | typedef FutureVoidCallbackParamStr = Future Function(String); 6 | typedef VoidCallbackParamStr = void Function(String); 7 | typedef FutureOrVoidCallback = FutureOr Function(); 8 | -------------------------------------------------------------------------------- /lib/data/res/build_data.dart: -------------------------------------------------------------------------------- 1 | // This file is generated by ./make.dart 2 | 3 | class BuildData { 4 | static const String name = "2fa"; 5 | static const int build = 6; 6 | static const String engine = "3.16.7"; 7 | static const String buildAt = "2024-01-16 16:17:48"; 8 | static const int modifications = 2; 9 | } 10 | -------------------------------------------------------------------------------- /lib/data/res/components_constants.dart: -------------------------------------------------------------------------------- 1 | const double mobileSmallThreshold = 336; 2 | -------------------------------------------------------------------------------- /lib/data/res/theme/effects.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | const blurBase = 96.0; 4 | const blurMuted = 48.0; 5 | const blurFaint = 24.0; 6 | 7 | List shadowFloatLight = const [ 8 | BoxShadow(blurRadius: 10, color: Color.fromRGBO(0, 0, 0, 0.25)), 9 | ]; 10 | 11 | List shadowMenuLight = const [ 12 | BoxShadow(blurRadius: 6, color: Color.fromRGBO(0, 0, 0, 0.16)), 13 | BoxShadow( 14 | blurRadius: 6, 15 | color: Color.fromRGBO(0, 0, 0, 0.12), 16 | offset: Offset(0, 3), 17 | ), 18 | ]; 19 | 20 | List shadowButtonLight = const [ 21 | BoxShadow( 22 | blurRadius: 4, 23 | color: Color.fromRGBO(0, 0, 0, 0.25), 24 | offset: Offset(0, 4), 25 | ), 26 | ]; 27 | 28 | List shadowFloatDark = const [ 29 | BoxShadow( 30 | blurRadius: 12, 31 | color: Color.fromRGBO(0, 0, 0, 0.75), 32 | offset: Offset(0, 2), 33 | ), 34 | ]; 35 | 36 | List shadowMenuDark = const [ 37 | BoxShadow(blurRadius: 6, color: Color.fromRGBO(0, 0, 0, 0.50)), 38 | BoxShadow( 39 | blurRadius: 6, 40 | color: Color.fromRGBO(0, 0, 0, 0.25), 41 | offset: Offset(0, 3), 42 | ), 43 | ]; 44 | 45 | List shadowButtonDark = const [ 46 | BoxShadow( 47 | blurRadius: 4, 48 | color: Color.fromRGBO(0, 0, 0, 0.75), 49 | offset: Offset(0, 4), 50 | ), 51 | ]; 52 | -------------------------------------------------------------------------------- /lib/data/res/theme/ente_theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:ente_auth/data/res/theme/colors.dart'; 2 | import 'package:ente_auth/data/res/theme/effects.dart'; 3 | import 'package:ente_auth/data/res/theme/text_style.dart'; 4 | import 'package:ente_auth/theme.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | class EnteTheme { 8 | final EnteTextTheme textTheme; 9 | final EnteColorScheme colorScheme; 10 | final List shadowFloat; 11 | final List shadowMenu; 12 | final List shadowButton; 13 | 14 | const EnteTheme( 15 | this.textTheme, 16 | this.colorScheme, { 17 | required this.shadowFloat, 18 | required this.shadowMenu, 19 | required this.shadowButton, 20 | }); 21 | } 22 | 23 | EnteTheme lightTheme = EnteTheme( 24 | lightTextTheme, 25 | lightScheme, 26 | shadowFloat: shadowFloatLight, 27 | shadowMenu: shadowMenuLight, 28 | shadowButton: shadowButtonLight, 29 | ); 30 | 31 | EnteTheme darkTheme = EnteTheme( 32 | darkTextTheme, 33 | darkScheme, 34 | shadowFloat: shadowFloatDark, 35 | shadowMenu: shadowMenuDark, 36 | shadowButton: shadowButtonDark, 37 | ); 38 | 39 | EnteColorScheme getEnteColorScheme( 40 | BuildContext context, { 41 | bool inverse = false, 42 | }) { 43 | return inverse 44 | ? Theme.of(context).colorScheme.inverseEnteTheme.colorScheme 45 | : Theme.of(context).colorScheme.enteTheme.colorScheme; 46 | } 47 | 48 | EnteTextTheme getEnteTextTheme( 49 | BuildContext context, { 50 | bool inverse = false, 51 | }) { 52 | return inverse 53 | ? Theme.of(context).colorScheme.inverseEnteTheme.textTheme 54 | : Theme.of(context).colorScheme.enteTheme.textTheme; 55 | } 56 | -------------------------------------------------------------------------------- /lib/data/services/local_auth.dart: -------------------------------------------------------------------------------- 1 | import 'package:ente_auth/core/configuration.dart'; 2 | import 'package:ente_auth/core/utils/auth_util.dart'; 3 | import 'package:ente_auth/core/utils/dialog_util.dart'; 4 | import 'package:ente_auth/core/utils/toast_util.dart'; 5 | import 'package:ente_auth/ui/view/app_lock.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:local_auth/local_auth.dart'; 8 | 9 | class LocalAuthenticationService { 10 | LocalAuthenticationService._(); 11 | 12 | static final instance = LocalAuthenticationService._(); 13 | 14 | Future requestLocalAuthentication( 15 | BuildContext context, 16 | String infoMessage, 17 | ) async { 18 | if (await _isLocalAuthSupportedOnDevice()) { 19 | AppLock.of(context)!.setEnabled(false); 20 | final result = await requestAuthentication(context, infoMessage); 21 | AppLock.of(context)!.setEnabled( 22 | Configuration.instance.shouldShowLockScreen(), 23 | ); 24 | if (!result) { 25 | showToast(context, infoMessage); 26 | return false; 27 | } else { 28 | return true; 29 | } 30 | } 31 | return true; 32 | } 33 | 34 | Future requestLocalAuthForLockScreen( 35 | BuildContext context, 36 | bool shouldEnableLockScreen, 37 | String infoMessage, 38 | String errorDialogContent, [ 39 | String errorDialogTitle = "", 40 | ]) async { 41 | if (await LocalAuthentication().isDeviceSupported()) { 42 | AppLock.of(context)!.disable(); 43 | final result = await requestAuthentication( 44 | context, 45 | infoMessage, 46 | ); 47 | if (result) { 48 | AppLock.of(context)!.setEnabled(shouldEnableLockScreen); 49 | await Configuration.instance 50 | .setShouldShowLockScreen(shouldEnableLockScreen); 51 | return true; 52 | } else { 53 | AppLock.of(context)! 54 | .setEnabled(Configuration.instance.shouldShowLockScreen()); 55 | } 56 | } else { 57 | showErrorDialog( 58 | context, 59 | errorDialogTitle, 60 | errorDialogContent, 61 | ); 62 | } 63 | return false; 64 | } 65 | 66 | Future _isLocalAuthSupportedOnDevice() async { 67 | return await LocalAuthentication().isDeviceSupported(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/data/services/preference.dart: -------------------------------------------------------------------------------- 1 | import 'package:ente_auth/core/event_bus.dart'; 2 | import 'package:ente_auth/core/events/icons_changed_event.dart'; 3 | import 'package:shared_preferences/shared_preferences.dart'; 4 | 5 | class PreferenceService { 6 | PreferenceService._(); 7 | 8 | static final instance = PreferenceService._(); 9 | 10 | late final SharedPreferences _prefs; 11 | 12 | static const kShouldShowLargeIconsKey = "should_show_large_icons"; 13 | static const kShouldHideCodesKey = "should_hide_codes"; 14 | static const kShouldAutoFocusOnSearchBar = "should_auto_focus_on_search_bar"; 15 | static const kShouldMinimizeOnCopy = "should_minimize_on_copy"; 16 | 17 | Future init() async { 18 | _prefs = await SharedPreferences.getInstance(); 19 | } 20 | 21 | bool shouldShowLargeIcons() { 22 | if (_prefs.containsKey(kShouldShowLargeIconsKey)) { 23 | return _prefs.getBool(kShouldShowLargeIconsKey)!; 24 | } else { 25 | return false; 26 | } 27 | } 28 | 29 | Future setShowLargeIcons(bool value) async { 30 | await _prefs.setBool(kShouldShowLargeIconsKey, value); 31 | Bus.instance.fire(IconsChangedEvent()); 32 | } 33 | 34 | bool shouldHideCodes() { 35 | return _prefs.getBool(kShouldHideCodesKey) ?? false; 36 | } 37 | 38 | Future setHideCodes(bool value) async { 39 | await _prefs.setBool(kShouldHideCodesKey, value); 40 | Bus.instance.fire(IconsChangedEvent()); 41 | } 42 | 43 | bool shouldAutoFocusOnSearchBar() { 44 | if (_prefs.containsKey(kShouldAutoFocusOnSearchBar)) { 45 | return _prefs.getBool(kShouldAutoFocusOnSearchBar)!; 46 | } else { 47 | return false; 48 | } 49 | } 50 | 51 | Future setAutoFocusOnSearchBar(bool value) async { 52 | await _prefs.setBool(kShouldAutoFocusOnSearchBar, value); 53 | Bus.instance.fire(IconsChangedEvent()); 54 | } 55 | 56 | bool shouldMinimizeOnCopy() { 57 | if (_prefs.containsKey(kShouldMinimizeOnCopy)) { 58 | return _prefs.getBool(kShouldMinimizeOnCopy)!; 59 | } else { 60 | return false; 61 | } 62 | } 63 | 64 | Future setShouldMinimizeOnCopy(bool value) async { 65 | await _prefs.setBool(kShouldMinimizeOnCopy, value); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/data/store/code.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:collection/collection.dart'; 4 | import 'package:ente_auth/core/event_bus.dart'; 5 | import 'package:ente_auth/core/events/codes_updated_event.dart'; 6 | import 'package:ente_auth/data/models/code.dart'; 7 | import 'package:ente_auth/data/services/auth.dart'; 8 | import 'package:logging/logging.dart'; 9 | 10 | class CodeStore { 11 | static final CodeStore instance = CodeStore._privateConstructor(); 12 | 13 | CodeStore._privateConstructor(); 14 | 15 | late AuthenticatorService _authenticatorService; 16 | final _logger = Logger("CodeStore"); 17 | 18 | Future init() async { 19 | _authenticatorService = AuthenticatorService.instance; 20 | } 21 | 22 | Future> getAllCodes() async { 23 | final entities = await _authenticatorService.getEntities(); 24 | final List codes = []; 25 | for (final entity in entities) { 26 | final decodeJson = jsonDecode(entity.rawData); 27 | final code = Code.fromRawData(decodeJson); 28 | code.generatedID = entity.generatedID; 29 | code.hasSynced = entity.hasSynced; 30 | codes.add(code); 31 | } 32 | 33 | // sort codes by issuer,account 34 | codes.sort((a, b) { 35 | final issuerComparison = compareAsciiLowerCaseNatural(a.issuer, b.issuer); 36 | if (issuerComparison != 0) { 37 | return issuerComparison; 38 | } 39 | return compareAsciiLowerCaseNatural(a.account, b.account); 40 | }); 41 | return codes; 42 | } 43 | 44 | Future addCode( 45 | Code code, { 46 | bool shouldSync = true, 47 | }) async { 48 | final codes = await getAllCodes(); 49 | bool isExistingCode = false; 50 | for (final existingCode in codes) { 51 | if (existingCode == code) { 52 | _logger.info("Found duplicate code, skipping add"); 53 | return AddResult.duplicate; 54 | } else if (existingCode.generatedID == code.generatedID) { 55 | isExistingCode = true; 56 | break; 57 | } 58 | } 59 | late AddResult result; 60 | if (isExistingCode) { 61 | result = AddResult.updateCode; 62 | await _authenticatorService.updateEntry( 63 | code.generatedID!, 64 | jsonEncode(code.rawData), 65 | shouldSync, 66 | ); 67 | } else { 68 | result = AddResult.newCode; 69 | code.generatedID = await _authenticatorService.addEntry( 70 | jsonEncode(code.rawData), 71 | shouldSync, 72 | ); 73 | } 74 | Bus.instance.fire(CodesUpdatedEvent()); 75 | return result; 76 | } 77 | 78 | Future removeCode(Code code) async { 79 | await _authenticatorService.deleteEntry(code.generatedID!); 80 | Bus.instance.fire(CodesUpdatedEvent()); 81 | } 82 | } 83 | 84 | enum AddResult { 85 | newCode, 86 | duplicate, 87 | updateCode, 88 | } 89 | -------------------------------------------------------------------------------- /lib/l10n/arb/app_ar.arb: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /lib/l10n/l10n.dart: -------------------------------------------------------------------------------- 1 | import "package:flutter/widgets.dart"; 2 | import "package:flutter_gen/gen_l10n/app_localizations.dart"; 3 | 4 | export "package:flutter_gen/gen_l10n/app_localizations.dart"; 5 | 6 | extension AppLocalizationsX on BuildContext { 7 | AppLocalizations get l10n => AppLocalizations.of(this); 8 | } 9 | -------------------------------------------------------------------------------- /lib/locale.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shared_preferences/shared_preferences.dart'; 3 | 4 | // list of locales which are enabled for auth app. 5 | // Add more language to the list only when at least 90% of the strings are 6 | // translated in the corresponding language. 7 | const List appSupportedLocales = [ 8 | Locale('de'), 9 | Locale('en'), 10 | Locale('es', 'ES'), 11 | Locale('fa'), 12 | Locale('fr'), 13 | Locale('it'), 14 | Locale('ja'), 15 | Locale('nl'), 16 | Locale('pl'), 17 | Locale('pt', 'BR'), 18 | Locale('ru'), 19 | Locale('tr'), 20 | Locale("zh", "CN"), 21 | ]; 22 | 23 | Locale localResolutionCallBack(locales, supportedLocales) { 24 | Locale? languageCodeMatch; 25 | final Map languageCodeToLocale = { 26 | for (Locale supportedLocale in appSupportedLocales) 27 | supportedLocale.languageCode: supportedLocale, 28 | }; 29 | 30 | for (Locale locale in locales) { 31 | if (appSupportedLocales.contains(locale)) { 32 | return locale; 33 | } 34 | 35 | if (languageCodeMatch == null && 36 | languageCodeToLocale.containsKey(locale.languageCode)) { 37 | languageCodeMatch = languageCodeToLocale[locale.languageCode]; 38 | } 39 | } 40 | 41 | // Return the first language code match or default to 'en' 42 | return languageCodeMatch ?? const Locale('en'); 43 | } 44 | 45 | Future getLocale() async { 46 | final String? savedValue = 47 | (await SharedPreferences.getInstance()).getString('locale'); 48 | // if savedLocale is not null and is supported by the app, return it 49 | if (savedValue != null) { 50 | late Locale savedLocale; 51 | if (savedValue.contains('_')) { 52 | final List parts = savedValue.split('_'); 53 | savedLocale = Locale(parts[0], parts[1]); 54 | } else { 55 | savedLocale = Locale(savedValue); 56 | } 57 | if (appSupportedLocales.contains(savedLocale)) { 58 | return savedLocale; 59 | } 60 | } 61 | return null; 62 | } 63 | 64 | Future setLocale(Locale locale) async { 65 | if (!appSupportedLocales.contains(locale)) { 66 | throw Exception('Locale $locale is not supported by the app'); 67 | } 68 | final StringBuffer out = StringBuffer(locale.languageCode); 69 | if (locale.countryCode != null && locale.countryCode!.isNotEmpty) { 70 | out.write('_'); 71 | out.write(locale.countryCode); 72 | } 73 | await (await SharedPreferences.getInstance()) 74 | .setString('locale', out.toString()); 75 | } 76 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:adaptive_theme/adaptive_theme.dart'; 2 | import 'package:computer/computer.dart'; 3 | import 'package:ente_auth/app.dart'; 4 | import 'package:ente_auth/core/configuration.dart'; 5 | import 'package:ente_auth/core/utils/crypto_util.dart'; 6 | import 'package:ente_auth/data/services/auth.dart'; 7 | import 'package:ente_auth/data/services/preference.dart'; 8 | import 'package:ente_auth/data/store/code.dart'; 9 | import 'package:ente_auth/locale.dart'; 10 | import 'package:ente_auth/theme.dart'; 11 | import 'package:ente_auth/ui/view/app_lock.dart'; 12 | import 'package:ente_auth/ui/view/issuer_icon.dart'; 13 | import 'package:flutter/foundation.dart'; 14 | import "package:flutter/material.dart"; 15 | 16 | void main() async { 17 | WidgetsFlutterBinding.ensureInitialized(); 18 | final savedThemeMode = await AdaptiveTheme.getThemeMode(); 19 | await _runInForeground(savedThemeMode); 20 | } 21 | 22 | Future _runInForeground(AdaptiveThemeMode? savedThemeMode) async { 23 | await _init(); 24 | final locale = await getLocale(); 25 | runApp( 26 | AppLock( 27 | builder: (args) => App(locale: locale), 28 | lockScreen: const LockScreen(), 29 | enabled: Configuration.instance.shouldShowLockScreen(), 30 | locale: locale, 31 | lightTheme: lightThemeData, 32 | darkTheme: darkThemeData, 33 | savedThemeMode: _themeMode(savedThemeMode), 34 | ), 35 | ); 36 | } 37 | 38 | ThemeMode _themeMode(AdaptiveThemeMode? savedThemeMode) { 39 | if (savedThemeMode == null) return ThemeMode.system; 40 | if (savedThemeMode.isLight) return ThemeMode.light; 41 | if (savedThemeMode.isDark) return ThemeMode.dark; 42 | return ThemeMode.system; 43 | } 44 | 45 | Future _init() async { 46 | // Start workers asynchronously. No need to wait for them to start 47 | Computer.shared().turnOn(workersCount: 3, verbose: kDebugMode); 48 | CryptoUtil.init(); 49 | await PreferenceService.instance.init(); 50 | await CodeStore.instance.init(); 51 | await Configuration.instance.init(); 52 | await AuthenticatorService.instance.init(); 53 | await IssuerIcon.instance.init(); 54 | } 55 | -------------------------------------------------------------------------------- /lib/ui/page/qr.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:ente_auth/data/models/code.dart'; 4 | import 'package:ente_auth/data/res/theme/ente_theme.dart'; 5 | import "package:ente_auth/l10n/l10n.dart"; 6 | import "package:flutter/material.dart"; 7 | import 'package:qr_flutter/qr_flutter.dart'; 8 | 9 | class ViewQrPage extends StatelessWidget { 10 | final Code? code; 11 | 12 | const ViewQrPage({this.code, super.key}); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | final screenWidth = MediaQuery.of(context).size.width; 17 | final double qrSize = min(screenWidth - 80, 300.0); 18 | final enteTextTheme = getEnteTextTheme(context); 19 | final l10n = context.l10n; 20 | return Scaffold( 21 | appBar: AppBar( 22 | title: Text(l10n.qrCode), 23 | ), 24 | body: SafeArea( 25 | child: Padding( 26 | padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40), 27 | child: Column( 28 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 29 | children: [ 30 | const SizedBox( 31 | height: 20, 32 | ), 33 | QrImageView( 34 | data: code?.rawData ?? '', 35 | eyeStyle: QrEyeStyle( 36 | eyeShape: QrEyeShape.square, 37 | color: Theme.of(context).colorScheme.onBackground, 38 | ), 39 | dataModuleStyle: QrDataModuleStyle( 40 | dataModuleShape: QrDataModuleShape.square, 41 | color: Theme.of(context).colorScheme.onBackground, 42 | ), 43 | version: QrVersions.auto, 44 | size: qrSize, 45 | ), 46 | const SizedBox( 47 | height: 20, 48 | ), 49 | Column( 50 | mainAxisSize: MainAxisSize.min, 51 | children: [ 52 | Row( 53 | mainAxisAlignment: MainAxisAlignment.center, 54 | children: [ 55 | Text( 56 | l10n.account, 57 | style: enteTextTheme.largeMuted, 58 | ), 59 | const SizedBox( 60 | width: 10, 61 | ), 62 | Text( 63 | code?.account ?? '', 64 | style: enteTextTheme.largeBold, 65 | ), 66 | ], 67 | ), 68 | const SizedBox( 69 | height: 4, 70 | ), 71 | Row( 72 | mainAxisAlignment: MainAxisAlignment.center, 73 | children: [ 74 | Text( 75 | l10n.codeIssuerHint, 76 | style: enteTextTheme.largeMuted, 77 | ), 78 | const SizedBox( 79 | width: 10, 80 | ), 81 | Text( 82 | code?.issuer ?? '', 83 | style: enteTextTheme.largeBold, 84 | ), 85 | ], 86 | ), 87 | ], 88 | ), 89 | ], 90 | ), 91 | ), 92 | ), 93 | ); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /lib/ui/page/scanner/gauth.dart: -------------------------------------------------------------------------------- 1 | import 'package:ente_auth/core/utils/toast_util.dart'; 2 | import 'package:ente_auth/data/models/code.dart'; 3 | import 'package:ente_auth/l10n/l10n.dart'; 4 | import 'package:ente_auth/ui/page/settings/import/google_auth_import.dart'; 5 | import 'package:ente_auth/ui/view/scanner_overlay.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:mobile_scanner/mobile_scanner.dart'; 8 | 9 | class ScannerGoogleAuthPage extends StatefulWidget { 10 | const ScannerGoogleAuthPage({super.key}); 11 | 12 | @override 13 | State createState() => ScannerGoogleAuthPageState(); 14 | } 15 | 16 | class ScannerGoogleAuthPageState extends State { 17 | String? totp; 18 | late Size size; 19 | 20 | @override 21 | void didChangeDependencies() { 22 | super.didChangeDependencies(); 23 | size = MediaQuery.of(context).size; 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | final l10n = context.l10n; 29 | return Scaffold( 30 | appBar: AppBar( 31 | title: Text(l10n.scan), 32 | ), 33 | body: Column( 34 | children: [ 35 | Expanded( 36 | flex: 5, 37 | child: MobileScanner( 38 | controller: MobileScannerController( 39 | detectionSpeed: DetectionSpeed.normal, 40 | facing: CameraFacing.back, 41 | ), 42 | onDetect: (capture) { 43 | final List barcodes = capture.barcodes; 44 | final raw = barcodes 45 | .where((element) => element.rawValue != null) 46 | .map((e) => e.rawValue!) 47 | .toList() 48 | .firstOrNull; 49 | if (raw != null && raw.startsWith(kGoogleAuthExportPrefix)) { 50 | List codes = parseGoogleAuth(raw); 51 | Navigator.of(context).pop(codes); 52 | } else { 53 | showToast(context, "Invalid QR code"); 54 | } 55 | }, 56 | overlay: CustomPaint( 57 | painter: ScannerOverlay( 58 | Rect.fromCenter( 59 | center: Offset.zero, 60 | width: size.width * 0.77, 61 | height: size.width * 0.77, 62 | ), 63 | ), 64 | ), 65 | ), 66 | ), 67 | Expanded( 68 | flex: 1, 69 | child: Center( 70 | child: (totp != null) ? Text(totp!) : Text(l10n.scanACode), 71 | ), 72 | ), 73 | ], 74 | ), 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/ui/page/scanner/general.dart: -------------------------------------------------------------------------------- 1 | import 'package:ente_auth/data/models/code.dart'; 2 | import 'package:ente_auth/ui/view/scanner_overlay.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:mobile_scanner/mobile_scanner.dart'; 5 | 6 | class ScannerPage extends StatefulWidget { 7 | const ScannerPage({super.key}); 8 | 9 | @override 10 | State createState() => ScannerPageState(); 11 | } 12 | 13 | class ScannerPageState extends State { 14 | final GlobalKey qrKey = GlobalKey(debugLabel: 'QR'); 15 | String? totp; 16 | late Size size; 17 | var scanned = false; 18 | 19 | @override 20 | void didChangeDependencies() { 21 | super.didChangeDependencies(); 22 | size = MediaQuery.of(context).size; 23 | } 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return Scaffold( 28 | body: MobileScanner( 29 | controller: MobileScannerController( 30 | detectionSpeed: DetectionSpeed.normal, 31 | facing: CameraFacing.back, 32 | ), 33 | onDetect: (capture) { 34 | final List barcodes = capture.barcodes; 35 | final code = Code.fromRawData( 36 | barcodes 37 | .where((element) => element.rawValue != null) 38 | .map((e) => e.rawValue!) 39 | .toList() 40 | .first, 41 | ); 42 | if (!mounted || scanned) return; 43 | scanned = true; 44 | Navigator.of(context).pop(code); 45 | }, 46 | fit: BoxFit.cover, 47 | overlay: CustomPaint( 48 | painter: ScannerOverlay( 49 | Rect.fromCenter( 50 | center: Offset.zero, 51 | width: size.width * 0.77, 52 | height: size.width * 0.77, 53 | ), 54 | ), 55 | ), 56 | ), 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/ui/page/settings/import/bitwarden_import.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:io'; 4 | 5 | import 'package:ente_auth/core/utils/dialog_util.dart'; 6 | import 'package:ente_auth/data/models/code.dart'; 7 | import 'package:ente_auth/data/store/code.dart'; 8 | import 'package:ente_auth/l10n/l10n.dart'; 9 | import 'package:ente_auth/ui/page/settings/import/import_success.dart'; 10 | import 'package:ente_auth/ui/view/buttons/button_type.dart'; 11 | import 'package:ente_auth/ui/view/buttons/button_widget.dart'; 12 | import 'package:ente_auth/ui/view/dialog.dart'; 13 | import 'package:file_picker/file_picker.dart'; 14 | import 'package:flutter/material.dart'; 15 | 16 | Future showBitwardenImportInstruction(BuildContext context) async { 17 | final l10n = context.l10n; 18 | final result = await showDialogWidget( 19 | context: context, 20 | title: l10n.importFromApp("Bitwarden"), 21 | body: l10n.importBitwardenGuide, 22 | buttons: [ 23 | ButtonWidget( 24 | buttonType: ButtonType.primary, 25 | labelText: l10n.importSelectJsonFile, 26 | isInAlert: true, 27 | buttonSize: ButtonSize.large, 28 | buttonAction: ButtonAction.first, 29 | ), 30 | ButtonWidget( 31 | buttonType: ButtonType.secondary, 32 | labelText: context.l10n.cancel, 33 | buttonSize: ButtonSize.large, 34 | isInAlert: true, 35 | buttonAction: ButtonAction.second, 36 | ), 37 | ], 38 | ); 39 | if (result?.action != null && result!.action != ButtonAction.cancel) { 40 | if (result.action == ButtonAction.first) { 41 | await _pickBitwardenJsonFile(context); 42 | } 43 | } 44 | } 45 | 46 | Future _pickBitwardenJsonFile(BuildContext context) async { 47 | FilePickerResult? result = await FilePicker.platform.pickFiles(); 48 | if (result == null) { 49 | return; 50 | } 51 | try { 52 | String path = result.files.single.path!; 53 | int? count = await _processBitwardenExportFile(context, path); 54 | if (count != null) { 55 | await importSuccessDialog(context, count); 56 | } 57 | } catch (e) { 58 | await showErrorDialog( 59 | context, 60 | context.l10n.sorry, 61 | context.l10n.importFailureDesc, 62 | ); 63 | } 64 | } 65 | 66 | Future _processBitwardenExportFile( 67 | BuildContext context, 68 | String path, 69 | ) async { 70 | File file = File(path); 71 | final jsonString = await file.readAsString(); 72 | final data = jsonDecode(jsonString); 73 | List jsonArray = data['items']; 74 | final parsedCodes = []; 75 | for (var item in jsonArray) { 76 | if (item['login']['totp'] != null) { 77 | var issuer = item['name']; 78 | var account = item['login']['username']; 79 | var secret = item['login']['totp']; 80 | 81 | parsedCodes.add( 82 | Code.fromAccountAndSecret( 83 | account, 84 | issuer, 85 | secret, 86 | ), 87 | ); 88 | } 89 | } 90 | 91 | for (final code in parsedCodes) { 92 | await CodeStore.instance.addCode(code, shouldSync: false); 93 | } 94 | return parsedCodes.length; 95 | } 96 | -------------------------------------------------------------------------------- /lib/ui/page/settings/import/import_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:ente_auth/ui/page/settings/import/2fas_import.dart'; 2 | import 'package:ente_auth/ui/page/settings/import/aegis_import.dart'; 3 | import 'package:ente_auth/ui/page/settings/import/bitwarden_import.dart'; 4 | import 'package:ente_auth/ui/page/settings/import/encrypted_ente_import.dart'; 5 | import 'package:ente_auth/ui/page/settings/import/google_auth_import.dart'; 6 | import 'package:ente_auth/ui/page/settings/import/import.dart'; 7 | import 'package:ente_auth/ui/page/settings/import/plain_text_import.dart'; 8 | import 'package:ente_auth/ui/page/settings/import/raivo_plain_text_import.dart'; 9 | import 'package:flutter/material.dart'; 10 | 11 | class ImportService { 12 | static final ImportService _instance = ImportService._internal(); 13 | 14 | factory ImportService() => _instance; 15 | 16 | ImportService._internal(); 17 | 18 | Future initiateImport(BuildContext context, ImportType type) async { 19 | switch (type) { 20 | case ImportType.plainText: 21 | showImportInstructionDialog(context); 22 | break; 23 | case ImportType.encrypted: 24 | showEncryptedImportInstruction(context); 25 | break; 26 | case ImportType.ravio: 27 | showRaivoImportInstruction(context); 28 | break; 29 | case ImportType.googleAuthenticator: 30 | showGoogleAuthInstruction(context); 31 | break; 32 | case ImportType.aegis: 33 | showAegisImportInstruction(context); 34 | break; 35 | case ImportType.twoFas: 36 | show2FasImportInstruction(context); 37 | break; 38 | case ImportType.bitwarden: 39 | showBitwardenImportInstruction(context); 40 | break; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/ui/page/settings/import/import_success.dart: -------------------------------------------------------------------------------- 1 | import 'package:ente_auth/core/utils/dialog_util.dart'; 2 | import 'package:ente_auth/l10n/l10n.dart'; 3 | import 'package:ente_auth/ui/view/buttons/button_type.dart'; 4 | import 'package:ente_auth/ui/view/dialog.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | Future importSuccessDialog(BuildContext context, int count) async { 8 | final DialogWidget dialog = choiceDialog( 9 | title: context.l10n.importSuccessTitle, 10 | body: context.l10n.importSuccessDesc(count), 11 | firstButtonLabel: context.l10n.ok, 12 | firstButtonOnTap: () async { 13 | Navigator.of(context).pop(); 14 | if (Navigator.of(context).canPop()) { 15 | Navigator.of(context).pop(); 16 | } 17 | }, 18 | firstButtonType: ButtonType.primary, 19 | ); 20 | await showConfettiDialog( 21 | context: context, 22 | dialogBuilder: (BuildContext context) { 23 | return dialog; 24 | }, 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /lib/ui/view/buttons/button_result.dart: -------------------------------------------------------------------------------- 1 | import 'package:ente_auth/ui/view/buttons/button_widget.dart'; 2 | 3 | class ButtonResult { 4 | ///action can be null when action for the button that is returned when popping 5 | ///the widget (dialog, actionSheet) which uses a ButtonWidget isn't 6 | ///relevant/useful and so is not assigned a value when an instance of 7 | ///ButtonWidget is created. 8 | final ButtonAction? action; 9 | final Exception? exception; 10 | ButtonResult([this.action, this.exception]); 11 | } 12 | -------------------------------------------------------------------------------- /lib/ui/view/buttons/icon_button_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:ente_auth/data/res/theme/colors.dart'; 2 | import 'package:ente_auth/data/res/theme/ente_theme.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | enum IconButtonType { 6 | primary, 7 | secondary, 8 | rounded, 9 | } 10 | 11 | class IconButtonWidget extends StatefulWidget { 12 | final IconButtonType iconButtonType; 13 | final IconData icon; 14 | final bool disableGestureDetector; 15 | final VoidCallback? onTap; 16 | final Color? defaultColor; 17 | final Color? pressedColor; 18 | final Color? iconColor; 19 | const IconButtonWidget({ 20 | super.key, 21 | required this.icon, 22 | required this.iconButtonType, 23 | this.disableGestureDetector = false, 24 | this.onTap, 25 | this.defaultColor, 26 | this.pressedColor, 27 | this.iconColor, 28 | }); 29 | 30 | @override 31 | State createState() => _IconButtonWidgetState(); 32 | } 33 | 34 | class _IconButtonWidgetState extends State { 35 | Color? iconStateColor; 36 | @override 37 | void didChangeDependencies() { 38 | setState(() { 39 | iconStateColor = null; 40 | }); 41 | super.didChangeDependencies(); 42 | } 43 | 44 | @override 45 | Widget build(BuildContext context) { 46 | final bool hasPressedState = widget.onTap != null; 47 | final colorTheme = getEnteColorScheme(context); 48 | iconStateColor ?? 49 | (iconStateColor = widget.defaultColor ?? 50 | (widget.iconButtonType == IconButtonType.rounded 51 | ? colorTheme.fillFaint 52 | : null)); 53 | return widget.disableGestureDetector 54 | ? _iconButton(colorTheme) 55 | : GestureDetector( 56 | onTapDown: hasPressedState ? _onTapDown : null, 57 | onTapUp: hasPressedState ? _onTapUp : null, 58 | onTapCancel: hasPressedState ? _onTapCancel : null, 59 | onTap: widget.onTap, 60 | child: _iconButton(colorTheme), 61 | ); 62 | } 63 | 64 | Widget _iconButton(EnteColorScheme colorTheme) { 65 | return Padding( 66 | padding: const EdgeInsets.all(4.0), 67 | child: AnimatedContainer( 68 | duration: const Duration(milliseconds: 20), 69 | padding: const EdgeInsets.all(8), 70 | decoration: BoxDecoration( 71 | borderRadius: BorderRadius.circular(20), 72 | color: iconStateColor, 73 | ), 74 | child: Icon( 75 | widget.icon, 76 | color: widget.iconColor ?? 77 | (widget.iconButtonType == IconButtonType.secondary 78 | ? colorTheme.strokeMuted 79 | : colorTheme.strokeBase), 80 | size: 24, 81 | ), 82 | ), 83 | ); 84 | } 85 | 86 | _onTapDown(details) { 87 | final colorTheme = getEnteColorScheme(context); 88 | setState(() { 89 | iconStateColor = widget.pressedColor ?? 90 | (widget.iconButtonType == IconButtonType.rounded 91 | ? colorTheme.fillMuted 92 | : colorTheme.fillFaint); 93 | }); 94 | } 95 | 96 | _onTapUp(details) { 97 | Future.delayed(const Duration(milliseconds: 100), () { 98 | setState(() { 99 | iconStateColor = null; 100 | }); 101 | }); 102 | } 103 | 104 | _onTapCancel() { 105 | setState(() { 106 | iconStateColor = null; 107 | }); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /lib/ui/view/cardx.dart: -------------------------------------------------------------------------------- 1 | import 'package:ente_auth/theme.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class CardX extends StatelessWidget { 5 | const CardX(this.child, {super.key, this.color}); 6 | 7 | final Widget child; 8 | final Color? color; 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return ClipRRect( 13 | borderRadius: BorderRadius.circular(8), 14 | child: Container( 15 | color: Theme.of(context).colorScheme.codeCardBackgroundColor, 16 | child: Material( 17 | color: Colors.transparent, 18 | child: InkWell( 19 | customBorder: RoundedRectangleBorder( 20 | borderRadius: BorderRadius.circular(10), 21 | ), 22 | child: child, 23 | ), 24 | ), 25 | ), 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/ui/view/code_progress.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/scheduler.dart'; 3 | 4 | class CodeTimerProgress extends StatefulWidget { 5 | final int period; 6 | 7 | const CodeTimerProgress({ 8 | super.key, 9 | required this.period, 10 | }); 11 | 12 | @override 13 | State createState() => _CodeTimerProgressState(); 14 | } 15 | 16 | class _CodeTimerProgressState extends State 17 | with SingleTickerProviderStateMixin { 18 | late final Ticker _ticker; 19 | double _progress = 0.0; 20 | late final int _microSecondsInPeriod; 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | _microSecondsInPeriod = widget.period * 1000000; 26 | _ticker = createTicker((elapsed) { 27 | _updateTimeRemaining(); 28 | }); 29 | _ticker.start(); 30 | _updateTimeRemaining(); 31 | } 32 | 33 | void _updateTimeRemaining() { 34 | int timeRemaining = (_microSecondsInPeriod) - 35 | (DateTime.now().microsecondsSinceEpoch % _microSecondsInPeriod); 36 | setState(() { 37 | _progress = (timeRemaining / _microSecondsInPeriod); 38 | }); 39 | } 40 | 41 | @override 42 | void dispose() { 43 | _ticker.dispose(); 44 | super.dispose(); 45 | } 46 | 47 | @override 48 | Widget build(BuildContext context) { 49 | return LayoutBuilder( 50 | builder: (context, constrains) { 51 | return Container( 52 | decoration: BoxDecoration( 53 | borderRadius: BorderRadius.circular(4), 54 | color: _progress > 0.4 ? Colors.green : Colors.red, 55 | ), 56 | width: constrains.maxWidth * _progress, 57 | height: 4, 58 | ); 59 | }, 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/ui/view/expand_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | const _shape = Border(); 4 | 5 | class ExpandTile extends ExpansionTile { 6 | const ExpandTile({ 7 | super.key, 8 | super.leading, 9 | required super.title, 10 | super.children, 11 | super.subtitle, 12 | super.initiallyExpanded, 13 | super.tilePadding, 14 | super.childrenPadding = const EdgeInsets.only(left: 37), 15 | super.trailing, 16 | super.controller, 17 | }) : super(shape: _shape, collapsedShape: _shape); 18 | } 19 | -------------------------------------------------------------------------------- /lib/ui/view/gradient_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class GradientButton extends StatelessWidget { 4 | final List linearGradientColors; 5 | final Function? onTap; 6 | 7 | // text is ignored if child is specified 8 | final String text; 9 | 10 | // nullable 11 | final IconData? iconData; 12 | 13 | // padding between the text and icon 14 | final double paddingValue; 15 | 16 | const GradientButton({ 17 | super.key, 18 | this.linearGradientColors = const [ 19 | Color.fromARGB(255, 133, 44, 210), 20 | Color.fromARGB(255, 187, 26, 93), 21 | ], 22 | this.onTap, 23 | this.text = '', 24 | this.iconData, 25 | this.paddingValue = 0.0, 26 | }); 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | Widget buttonContent; 31 | if (iconData == null) { 32 | buttonContent = Text( 33 | text, 34 | style: const TextStyle( 35 | color: Colors.white, 36 | fontWeight: FontWeight.w600, 37 | fontFamily: 'Inter-SemiBold', 38 | fontSize: 18, 39 | ), 40 | ); 41 | } else { 42 | buttonContent = Row( 43 | mainAxisAlignment: MainAxisAlignment.center, 44 | crossAxisAlignment: CrossAxisAlignment.center, 45 | children: [ 46 | Icon( 47 | iconData, 48 | size: 20, 49 | color: Colors.white, 50 | ), 51 | const Padding(padding: EdgeInsets.symmetric(horizontal: 6)), 52 | Text( 53 | text, 54 | style: const TextStyle( 55 | color: Colors.white, 56 | fontWeight: FontWeight.w600, 57 | fontFamily: 'Inter-SemiBold', 58 | fontSize: 18, 59 | ), 60 | ), 61 | ], 62 | ); 63 | } 64 | return InkWell( 65 | onTap: onTap as void Function()?, 66 | child: Container( 67 | height: 56, 68 | decoration: BoxDecoration( 69 | gradient: LinearGradient( 70 | begin: const Alignment(0.1, -0.9), 71 | end: const Alignment(-0.6, 0.9), 72 | colors: linearGradientColors, 73 | ), 74 | borderRadius: BorderRadius.circular(8), 75 | ), 76 | child: Center(child: buttonContent), 77 | ), 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/ui/view/lifecycle_handler.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | 4 | class LifecycleEventHandler extends WidgetsBindingObserver { 5 | final AsyncCallback? resumeCallBack; 6 | final AsyncCallback? suspendingCallBack; 7 | 8 | LifecycleEventHandler({ 9 | this.resumeCallBack, 10 | this.suspendingCallBack, 11 | }); 12 | 13 | @override 14 | Future didChangeAppLifecycleState(AppLifecycleState state) async { 15 | switch (state) { 16 | case AppLifecycleState.resumed: 17 | if (resumeCallBack != null) { 18 | await resumeCallBack!(); 19 | } 20 | break; 21 | case AppLifecycleState.inactive: 22 | case AppLifecycleState.hidden: 23 | case AppLifecycleState.paused: 24 | case AppLifecycleState.detached: 25 | if (suspendingCallBack != null) { 26 | await suspendingCallBack!(); 27 | } 28 | break; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/ui/view/loading.dart: -------------------------------------------------------------------------------- 1 | import 'package:ente_auth/data/res/theme/ente_theme.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class EnteLoadingWidget extends StatelessWidget { 5 | final Color? color; 6 | final double size; 7 | final double padding; 8 | final Alignment alignment; 9 | const EnteLoadingWidget({ 10 | this.color, 11 | this.size = 14, 12 | this.padding = 5, 13 | this.alignment = Alignment.center, 14 | super.key, 15 | }); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Align( 20 | alignment: alignment, 21 | child: Padding( 22 | padding: EdgeInsets.all(padding), 23 | child: SizedBox.fromSize( 24 | size: Size.square(size), 25 | child: CircularProgressIndicator( 26 | strokeWidth: 2, 27 | color: color ?? getEnteColorScheme(context).strokeBase, 28 | ), 29 | ), 30 | ), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/ui/view/scanner_overlay.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ScannerOverlay extends CustomPainter { 4 | ScannerOverlay(this.scanWindow); 5 | 6 | final Rect scanWindow; 7 | final double borderRadius = 12.0; 8 | 9 | @override 10 | void paint(Canvas canvas, Size size) { 11 | final backgroundPath = Path()..addRect(Rect.largest); 12 | final cutoutPath = Path() 13 | ..addRRect( 14 | RRect.fromRectAndCorners( 15 | scanWindow, 16 | topLeft: Radius.circular(borderRadius), 17 | topRight: Radius.circular(borderRadius), 18 | bottomLeft: Radius.circular(borderRadius), 19 | bottomRight: Radius.circular(borderRadius), 20 | ), 21 | ); 22 | 23 | final backgroundPaint = Paint() 24 | ..color = Colors.black.withOpacity(0.5) 25 | ..style = PaintingStyle.fill 26 | ..blendMode = BlendMode.dstOut; 27 | 28 | final backgroundWithCutout = Path.combine( 29 | PathOperation.difference, 30 | backgroundPath, 31 | cutoutPath, 32 | ); 33 | 34 | // Create a Paint object for the white border 35 | final borderPaint = Paint() 36 | ..color = Colors.white 37 | ..style = PaintingStyle.stroke 38 | ..strokeWidth = 4.0; // Adjust the border width as needed 39 | 40 | // Calculate the border rectangle with rounded corners 41 | // Adjust the radius as needed 42 | final borderRect = RRect.fromRectAndCorners( 43 | scanWindow, 44 | topLeft: Radius.circular(borderRadius), 45 | topRight: Radius.circular(borderRadius), 46 | bottomLeft: Radius.circular(borderRadius), 47 | bottomRight: Radius.circular(borderRadius), 48 | ); 49 | 50 | // Draw the white border 51 | canvas.drawPath(backgroundWithCutout, backgroundPaint); 52 | canvas.drawRRect(borderRect, borderPaint); 53 | } 54 | 55 | @override 56 | bool shouldRepaint(covariant CustomPainter oldDelegate) { 57 | return false; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /migration-guides/decrypt/crypt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "fmt" 7 | "io" 8 | "log" 9 | 10 | "github.com/jamesruan/sodium" 11 | "golang.org/x/crypto/argon2" 12 | ) 13 | 14 | // deriveArgonKey generates a 32-bit cryptographic key using the Argon2id algorithm. 15 | // Parameters: 16 | // - password: The plaintext password to be hashed. 17 | // - salt: The salt as a base64 encoded string. 18 | // - memLimit: The memory limit in bytes. 19 | // - opsLimit: The number of iterations. 20 | // 21 | // Returns: 22 | // - A byte slice representing the derived key. 23 | // - An error object, which is nil if no error occurs. 24 | func deriveArgonKey(password, salt string, memLimit, opsLimit int) ([]byte, error) { 25 | if memLimit < 1024 || opsLimit < 1 { 26 | return nil, fmt.Errorf("invalid memory or operation limits") 27 | } 28 | 29 | // Decode salt from base64 30 | saltBytes, err := base64.StdEncoding.DecodeString(salt) 31 | if err != nil { 32 | return nil, fmt.Errorf("invalid salt: %v", err) 33 | } 34 | 35 | // Generate key using Argon2id 36 | // Note: We're assuming a fixed key length of 32 bytes and changing the threads 37 | key := argon2.IDKey([]byte(password), saltBytes, uint32(opsLimit), uint32(memLimit/1024), 1, 32) 38 | 39 | return key, nil 40 | } 41 | 42 | // decryptChaCha20poly1305 decrypts the given data using the ChaCha20-Poly1305 algorithm. 43 | // Parameters: 44 | // - data: The encrypted data as a byte slice. 45 | // - key: The key for decryption as a byte slice. 46 | // - nonce: The nonce for decryption as a byte slice. 47 | // 48 | // Returns: 49 | // - A byte slice representing the decrypted data. 50 | // - An error object, which is nil if no error occurs. 51 | func decryptChaCha20poly1305(data []byte, key []byte, nonce []byte) ([]byte, error) { 52 | reader := bytes.NewReader(data) 53 | header := sodium.SecretStreamXCPHeader{Bytes: nonce} 54 | decoder, err := sodium.MakeSecretStreamXCPDecoder( 55 | sodium.SecretStreamXCPKey{Bytes: key}, 56 | reader, 57 | header) 58 | if err != nil { 59 | log.Println("Failed to make secret stream decoder", err) 60 | return nil, err 61 | } 62 | // Buffer to store the decrypted data 63 | decryptedData := make([]byte, len(data)) 64 | n, err := decoder.Read(decryptedData) 65 | if err != nil && err != io.EOF { 66 | log.Println("Failed to read from decoder", err) 67 | return nil, err 68 | } 69 | return decryptedData[:n], nil 70 | } 71 | -------------------------------------------------------------------------------- /migration-guides/decrypt/crypt_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/base64" 5 | "testing" 6 | ) 7 | 8 | const ( 9 | password = "test_password" 10 | kdfSalt = "vd0dcYMGNLKn/gpT6uTFTw==" 11 | memLimit = 64 * 1024 * 1024 // 64MB 12 | opsLimit = 2 13 | cipherText = "kBXQ2PuX6y/aje5r22H0AehRPh6sQ0ULoeAO" 14 | cipherNonce = "v7wsI+BFZsRMIjDm3rTxPhmi/CaUdkdJ" 15 | expectedPlainText = "plain_text" 16 | expectedDerivedKey = "vp8d8Nee0BbIML4ab8Cp34uYnyrN77cRwTl920flyT0=" 17 | ) 18 | 19 | func TestDeriveArgonKey(t *testing.T) { 20 | derivedKey, err := deriveArgonKey(password, kdfSalt, memLimit, opsLimit) 21 | if err != nil { 22 | t.Fatalf("Failed to derive key: %v", err) 23 | } 24 | 25 | if base64.StdEncoding.EncodeToString(derivedKey) != expectedDerivedKey { 26 | t.Fatalf("Derived key does not match expected key") 27 | } 28 | } 29 | 30 | func TestDecryptChaCha20poly1305(t *testing.T) { 31 | derivedKey, err := deriveArgonKey(password, kdfSalt, memLimit, opsLimit) 32 | if err != nil { 33 | t.Fatalf("Failed to derive key: %v", err) 34 | } 35 | 36 | decodedCipherText, err := base64.StdEncoding.DecodeString(cipherText) 37 | if err != nil { 38 | t.Fatalf("Failed to decode cipher text: %v", err) 39 | } 40 | 41 | decodedCipherNonce, err := base64.StdEncoding.DecodeString(cipherNonce) 42 | if err != nil { 43 | t.Fatalf("Failed to decode cipher nonce: %v", err) 44 | } 45 | 46 | decryptedText, err := decryptChaCha20poly1305(decodedCipherText, derivedKey, decodedCipherNonce) 47 | if err != nil { 48 | t.Fatalf("Failed to decrypt: %v", err) 49 | } 50 | if string(decryptedText) != expectedPlainText { 51 | t.Fatalf("Decrypted text : %s does not match the expected text: %s", string(decryptedText), expectedPlainText) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /migration-guides/decrypt/decrypt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/migration-guides/decrypt/decrypt -------------------------------------------------------------------------------- /migration-guides/decrypt/decrypt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "fmt" 7 | "os" 8 | ) 9 | 10 | type Export struct { 11 | Version int `json:"version"` 12 | KDFParams KDF `json:"kdfParams"` 13 | EncryptedData string `json:"encryptedData"` 14 | EncryptionNonce string `json:"encryptionNonce"` 15 | } 16 | 17 | type KDF struct { 18 | MemLimit int `json:"memLimit"` 19 | OpsLimit int `json:"opsLimit"` 20 | Salt string `json:"salt"` 21 | } 22 | 23 | func resolvePath(path string) (string, error) { 24 | if path[:2] != "~/" { 25 | return path, nil 26 | } 27 | home, err := os.UserHomeDir() 28 | if err != nil { 29 | return "", err 30 | } 31 | return home + path[1:], nil 32 | } 33 | 34 | func main() { 35 | defer func() { 36 | if err := recover(); err != nil { 37 | fmt.Println("Error:", err) 38 | } 39 | }() 40 | 41 | if len(os.Args) != 4 { 42 | fmt.Println("Usage: ./decrypt ") 43 | return 44 | } 45 | 46 | exportFile, err := resolvePath(os.Args[1]) 47 | if err != nil { 48 | fmt.Println("Error resolving exportFile path:", err) 49 | return 50 | } 51 | password := os.Args[2] 52 | outputFile, err := resolvePath(os.Args[3]) 53 | if err != nil { 54 | fmt.Println("Error resolving outputFile path:", err) 55 | return 56 | } 57 | 58 | data, err := os.ReadFile(exportFile) 59 | if err != nil { 60 | fmt.Println("Error reading file:", err) 61 | return 62 | } 63 | 64 | var export Export 65 | if err := json.Unmarshal(data, &export); err != nil { 66 | fmt.Println("Error parsing JSON:", err) 67 | return 68 | } 69 | 70 | if export.Version != 1 { 71 | fmt.Println("Unsupported version") 72 | return 73 | } 74 | 75 | encryptedData, err := base64.StdEncoding.DecodeString(export.EncryptedData) 76 | if err != nil { 77 | fmt.Println("Error decoding encrypted data:", err) 78 | return 79 | } 80 | 81 | nonce, err := base64.StdEncoding.DecodeString(export.EncryptionNonce) 82 | if err != nil { 83 | fmt.Println("Error decoding nonce:", err) 84 | return 85 | } 86 | 87 | key, err := deriveArgonKey(password, export.KDFParams.Salt, export.KDFParams.MemLimit, export.KDFParams.OpsLimit) 88 | if err != nil { 89 | fmt.Println("Error deriving key:", err) 90 | return 91 | } 92 | 93 | decryptedData, err := decryptChaCha20poly1305(encryptedData, key, nonce) 94 | if err != nil { 95 | fmt.Println("Error decrypting data:", err) 96 | return 97 | } 98 | 99 | if err := os.WriteFile(outputFile, decryptedData, 0644); err != nil { 100 | fmt.Println("Error writing decrypted data to file:", err) 101 | return 102 | } 103 | 104 | fmt.Printf("Decrypted data written to %s\n", outputFile) 105 | } 106 | -------------------------------------------------------------------------------- /migration-guides/decrypt/go.mod: -------------------------------------------------------------------------------- 1 | module decrypt 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/jamesruan/sodium v1.0.14 7 | golang.org/x/crypto v0.11.0 8 | ) 9 | 10 | require golang.org/x/sys v0.10.0 // indirect 11 | -------------------------------------------------------------------------------- /migration-guides/decrypt/go.sum: -------------------------------------------------------------------------------- 1 | github.com/jamesruan/sodium v1.0.14 h1:JfOHobip/lUWouxHV3PwYwu3gsLewPrDrZXO3HuBzUU= 2 | github.com/jamesruan/sodium v1.0.14/go.mod h1:GK2+LACf7kuVQ9k7Irk0MB2B65j5rVqkz+9ylGIggZk= 3 | golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= 4 | golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= 5 | golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= 6 | golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 7 | -------------------------------------------------------------------------------- /migration-guides/encrypted_export.md: -------------------------------------------------------------------------------- 1 | # Auth Encrypted Export format 2 | 3 | ## Overview 4 | 5 | When we export the auth codes, the data is encrypted using a key derived from the user's password. 6 | This document describes the JSON structure used to organize exported data, including versioning and key derivation 7 | parameters. 8 | 9 | ## Export JSON Sample 10 | 11 | ```json 12 | { 13 | "version": 1, 14 | "kdfParams": { 15 | "memLimit": 4096, 16 | "opsLimit": 3, 17 | "salt": "example_salt" 18 | }, 19 | "encryptedData": "encrypted_data_here", 20 | "encryptionNonce": "nonce_here" 21 | } 22 | ``` 23 | 24 | The main object used to represent the export data. It contains the following key-value pairs: 25 | 26 | - `version`: The version of the export format. 27 | - `kdfParams`: Key derivation function parameters. 28 | - `encryptedData"`: The encrypted authentication data. 29 | - `encryptionNonce`: The nonce used for encryption. 30 | 31 | ### Version 32 | 33 | Export version is used to identify the format of the export data. 34 | 35 | #### Ver: 1 36 | 37 | * KDF Algorithm: `ARGON2ID` 38 | * Decrypted data format: `otpauth://totp/...`, separated by a new line. 39 | * Encryption Algo: `XChaCha20-Poly1305` 40 | 41 | #### Key Derivation Function Params (KDF) 42 | 43 | This section contains the parameters that were using during KDF operation: 44 | 45 | - `memLimit`: Memory limit for the algorithm. 46 | - `opsLimit`: Operations limit for the algorithm. 47 | - `salt`: The salt used in the derivation process. 48 | 49 | #### Encrypted Data 50 | 51 | As mentioned above, the auth data is encrypted using a key that's derived by using user provided password & kdf params. 52 | For encryption, we are using `XChaCha20-Poly1305` algorithm. 53 | 54 | ## How to use the exported data 55 | 56 | * **ente Authenticator app**: You can directly import the codes in the ente Authenticator app. 57 | > Settings -> Data -> Import Codes -> ente Encrypted export. 58 | 59 | * **Decryption Tool** : You can download the prebuilt [decryption tool](decrypt/decrypt) (or build it from [source](decrypt)) and run the following command. 60 | 61 | ``` 62 | ./decrypt 63 | ``` 64 | -------------------------------------------------------------------------------- /protos/googleauth.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package googleauth; 3 | 4 | message MigrationPayload { 5 | enum Algorithm { 6 | ALGORITHM_UNSPECIFIED = 0; 7 | ALGORITHM_SHA1 = 1; 8 | ALGORITHM_SHA256 = 2; 9 | ALGORITHM_SHA512 = 3; 10 | ALGORITHM_MD5 = 4; 11 | } 12 | 13 | enum DigitCount { 14 | DIGIT_COUNT_UNSPECIFIED = 0; 15 | DIGIT_COUNT_SIX = 1; 16 | DIGIT_COUNT_EIGHT = 2; 17 | } 18 | 19 | enum OtpType { 20 | OTP_TYPE_UNSPECIFIED = 0; 21 | OTP_TYPE_HOTP = 1; 22 | OTP_TYPE_TOTP = 2; 23 | } 24 | 25 | message OtpParameters { 26 | bytes secret = 1; 27 | string name = 2; 28 | string issuer = 3; 29 | Algorithm algorithm = 4; 30 | DigitCount digits = 5; 31 | OtpType type = 6; 32 | int64 counter = 7; 33 | } 34 | 35 | repeated OtpParameters otp_parameters = 1; 36 | int32 version = 2; 37 | int32 batch_size = 3; 38 | int32 batch_index = 4; 39 | int32 batch_id = 5; 40 | } -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ente_auth 2 | description: ente two-factor authenticator 3 | version: 2.0.26+226 4 | publish_to: none 5 | 6 | environment: 7 | sdk: ">=3.0.0 <4.0.0" 8 | 9 | dependencies: 10 | adaptive_theme: ^3.1.0 # done 11 | archive: ^3.3.7 12 | base32: ^2.1.3 13 | bip39: ^1.0.6 #done 14 | bloc: ^8.0.3 #done 15 | collection: # dart 16 | computer: 17 | git: "https://github.com/ente-io/computer.git" 18 | confetti: ^0.7.0 19 | convert: ^3.1.1 20 | event_bus: ^2.0.0 21 | file_picker: ^6.1.1 22 | fixnum: ^1.1.0 23 | flutter: 24 | sdk: flutter 25 | flutter_bloc: ^8.0.1 26 | flutter_localizations: 27 | sdk: flutter 28 | flutter_secure_storage: ^9.0.0 29 | flutter_slidable: ^3.0.0 30 | flutter_sodium: 31 | git: 32 | url: https://github.com/ente-io/flutter_sodium.git 33 | flutter_svg: ^2.0.5 34 | fluttertoast: ^8.1.1 35 | intl: ^0.18.0 36 | json_annotation: ^4.5.0 37 | local_auth: ^2.1.7 38 | local_auth_android: ^1.0.31 39 | local_auth_ios: ^1.1.3 40 | logging: ^1.0.1 41 | mobile_scanner: ^3.5.5 42 | move_to_background: ^1.0.2 43 | otp: ^3.1.4 44 | path: ^1.8.3 45 | path_provider: ^2.0.11 46 | pointycastle: ^3.7.3 47 | protobuf: ^3.0.0 48 | qr_flutter: ^4.1.0 49 | share_plus: ^7.2.1 50 | shared_preferences: ^2.0.5 51 | sqflite: ^2.1.0 52 | uni_links: ^0.5.1 53 | url_launcher: ^6.1.5 54 | uuid: ^3.0.4 55 | 56 | dev_dependencies: 57 | bloc_test: ^9.0.3 58 | build_runner: ^2.1.11 59 | flutter_native_splash: ^2.3.6 60 | flutter_test: 61 | sdk: flutter 62 | json_serializable: ^6.2.0 63 | lints: ^3.0.0 64 | 65 | # The following section is specific to Flutter. 66 | flutter: 67 | uses-material-design: true 68 | generate: true 69 | 70 | # https://docs:flutter:dev/development/ui/assets-and-images: 71 | assets: 72 | - assets/ 73 | - assets/simple-icons/icons/ 74 | - assets/simple-icons/_data/ 75 | - assets/custom-icons/icons/ 76 | - assets/custom-icons/_data/ 77 | 78 | fonts: 79 | - family: Inter 80 | fonts: 81 | - asset: fonts/Inter-Regular.ttf 82 | - asset: fonts/Inter-Medium.ttf 83 | - asset: fonts/Inter-Light.ttf 84 | - asset: fonts/Inter-SemiBold.ttf 85 | - asset: fonts/Inter-Bold.ttf 86 | - family: Montserrat 87 | fonts: 88 | - asset: fonts/Montserrat-Bold.ttf 89 | 90 | flutter_native_splash: 91 | # 253 225 223 92 | color: "#FDE1DF" 93 | color_dark: "#000000" 94 | image: assets/app_icon.png 95 | image_dark: assets/app_icon.png 96 | android_gravity: center 97 | ios_content_mode: center 98 | -------------------------------------------------------------------------------- /screenshots/screenshots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/screenshots/screenshots.png -------------------------------------------------------------------------------- /test/helpers/helpers.dart: -------------------------------------------------------------------------------- 1 | export "pump_app.dart"; 2 | -------------------------------------------------------------------------------- /test/helpers/pump_app.dart: -------------------------------------------------------------------------------- 1 | import "package:ente_auth/l10n/l10n.dart"; 2 | import "package:flutter/material.dart"; 3 | import "package:flutter_localizations/flutter_localizations.dart"; 4 | import "package:flutter_test/flutter_test.dart"; 5 | 6 | extension PumpApp on WidgetTester { 7 | Future pumpApp(Widget widget) { 8 | return pumpWidget( 9 | MaterialApp( 10 | localizationsDelegates: const [ 11 | AppLocalizations.delegate, 12 | GlobalMaterialLocalizations.delegate, 13 | ], 14 | supportedLocales: AppLocalizations.supportedLocales, 15 | home: widget, 16 | ), 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/models/code_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:ente_auth/data/models/code.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | 4 | void main() { 5 | test("parseCodeFromRawData", () { 6 | final code1 = Code.fromRawData( 7 | "otpauth://totp/example%20finance%3Aee%40ff.gg?secret=ASKZNWOU6SVYAMVS", 8 | ); 9 | expect(code1.issuer, "example finance", reason: "issuerMismatch"); 10 | expect(code1.account, "ee@ff.gg", reason: "accountMismatch"); 11 | expect(code1.secret, "ASKZNWOU6SVYAMVS"); 12 | }); 13 | 14 | test("parseDocumentedFormat", () { 15 | final code = Code.fromRawData( 16 | "otpauth://totp/testdata@ente.io?secret=ASKZNWOU6SVYAMVS&issuer=GitHub", 17 | ); 18 | expect(code.issuer, "GitHub", reason: "issuerMismatch"); 19 | expect(code.account, "testdata@ente.io", reason: "accountMismatch"); 20 | expect(code.secret, "ASKZNWOU6SVYAMVS"); 21 | }); 22 | 23 | test("validateCount", () { 24 | final code = Code.fromRawData( 25 | "otpauth://hotp/testdata@ente.io?secret=ASKZNWOU6SVYAMVS&issuer=GitHub&counter=15", 26 | ); 27 | expect(code.issuer, "GitHub", reason: "issuerMismatch"); 28 | expect(code.account, "testdata@ente.io", reason: "accountMismatch"); 29 | expect(code.secret, "ASKZNWOU6SVYAMVS"); 30 | expect(code.counter, 15); 31 | }); 32 | // 33 | 34 | test("parseWithFunnyAccountName", () { 35 | final code = Code.fromRawData( 36 | "otpauth://totp/Mongo Atlas:Acc !@#444?algorithm=sha1&digits=6&issuer=Mongo Atlas&period=30&secret=NI4CTTFEV4G2JFE6", 37 | ); 38 | expect(code.issuer, "Mongo Atlas", reason: "issuerMismatch"); 39 | expect(code.account, "Acc !@#444", reason: "accountMismatch"); 40 | expect(code.secret, "NI4CTTFEV4G2JFE6"); 41 | }); 42 | 43 | test("parseAndUpdateInChinese", () { 44 | const String rubberDuckQr = 45 | 'otpauth://totp/%E6%A9%A1%E7%9A%AE%E9%B8%AD?secret=2CWDCK4EOIN5DJDRMYUMYBBO4MKSR5AX&issuer=ente.io'; 46 | final code = Code.fromRawData(rubberDuckQr); 47 | expect(code.account, '橡皮鸭'); 48 | final String updatedRawCode = 49 | code.copyWith(account: '伍迪', issuer: '鸭子').rawData; 50 | final updateCode = Code.fromRawData(updatedRawCode); 51 | expect(updateCode.account, '伍迪', reason: 'updated accountMismatch'); 52 | expect(updateCode.issuer, '鸭子', reason: 'updated issuerMismatch'); 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /web/icons/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/web/icons/favicon.png -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "My App", 3 | "short_name": "My App", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "ente two-factor authenticator", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /web/splash/img/dark-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/web/splash/img/dark-1x.png -------------------------------------------------------------------------------- /web/splash/img/dark-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/web/splash/img/dark-2x.png -------------------------------------------------------------------------------- /web/splash/img/dark-3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/web/splash/img/dark-3x.png -------------------------------------------------------------------------------- /web/splash/img/dark-4x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/web/splash/img/dark-4x.png -------------------------------------------------------------------------------- /web/splash/img/light-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/web/splash/img/light-1x.png -------------------------------------------------------------------------------- /web/splash/img/light-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/web/splash/img/light-2x.png -------------------------------------------------------------------------------- /web/splash/img/light-3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/web/splash/img/light-3x.png -------------------------------------------------------------------------------- /web/splash/img/light-4x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/web/splash/img/light-4x.png -------------------------------------------------------------------------------- /web/splash/splash.js: -------------------------------------------------------------------------------- 1 | function removeSplashFromWeb() { 2 | document.getElementById("splash")?.remove(); 3 | document.getElementById("splash-branding")?.remove(); 4 | document.body.style.background = "transparent"; 5 | } 6 | -------------------------------------------------------------------------------- /web/splash/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin:0; 3 | height:100%; 4 | background: #ffffff; 5 | 6 | background-size: 100% 100%; 7 | } 8 | 9 | .center { 10 | margin: 0; 11 | position: absolute; 12 | top: 50%; 13 | left: 50%; 14 | -ms-transform: translate(-50%, -50%); 15 | transform: translate(-50%, -50%); 16 | } 17 | 18 | .contain { 19 | display:block; 20 | width:100%; height:100%; 21 | object-fit: contain; 22 | } 23 | 24 | .stretch { 25 | display:block; 26 | width:100%; height:100%; 27 | } 28 | 29 | .cover { 30 | display:block; 31 | width:100%; height:100%; 32 | object-fit: cover; 33 | } 34 | 35 | .bottom { 36 | position: absolute; 37 | bottom: 0; 38 | left: 50%; 39 | -ms-transform: translate(-50%, 0); 40 | transform: translate(-50%, 0); 41 | } 42 | 43 | .bottomLeft { 44 | position: absolute; 45 | bottom: 0; 46 | left: 0; 47 | } 48 | 49 | .bottomRight { 50 | position: absolute; 51 | bottom: 0; 52 | right: 0; 53 | } 54 | 55 | @media (prefers-color-scheme: dark) { 56 | body { 57 | margin:0; 58 | height:100%; 59 | background: #000000; 60 | 61 | background-size: 100% 100%; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | void RegisterPlugins(flutter::PluginRegistry* registry) { 15 | FlutterSecureStorageWindowsPluginRegisterWithRegistrar( 16 | registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); 17 | LocalAuthPluginRegisterWithRegistrar( 18 | registry->GetRegistrarForPlugin("LocalAuthPlugin")); 19 | SharePlusWindowsPluginCApiRegisterWithRegistrar( 20 | registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); 21 | UrlLauncherWindowsRegisterWithRegistrar( 22 | registry->GetRegistrarForPlugin("UrlLauncherWindows")); 23 | } 24 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | flutter_secure_storage_windows 7 | local_auth_windows 8 | share_plus 9 | url_launcher_windows 10 | ) 11 | 12 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 13 | ) 14 | 15 | set(PLUGIN_BUNDLED_LIBRARIES) 16 | 17 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 18 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 19 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 22 | endforeach(plugin) 23 | 24 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 25 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 26 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 27 | endforeach(ffi_plugin) 28 | -------------------------------------------------------------------------------- /windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | add_executable(${BINARY_NAME} WIN32 5 | "flutter_window.cpp" 6 | "main.cpp" 7 | "utils.cpp" 8 | "win32_window.cpp" 9 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 10 | "Runner.rc" 11 | "runner.exe.manifest" 12 | ) 13 | apply_standard_settings(${BINARY_NAME}) 14 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 15 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 16 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 17 | add_dependencies(${BINARY_NAME} flutter_assemble) 18 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | return true; 30 | } 31 | 32 | void FlutterWindow::OnDestroy() { 33 | if (flutter_controller_) { 34 | flutter_controller_ = nullptr; 35 | } 36 | 37 | Win32Window::OnDestroy(); 38 | } 39 | 40 | LRESULT 41 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 42 | WPARAM const wparam, 43 | LPARAM const lparam) noexcept { 44 | // Give Flutter, including plugins, an opportunity to handle window messages. 45 | if (flutter_controller_) { 46 | std::optional result = 47 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 48 | lparam); 49 | if (result) { 50 | return *result; 51 | } 52 | } 53 | 54 | switch (message) { 55 | case WM_FONTCHANGE: 56 | flutter_controller_->engine()->ReloadSystemFonts(); 57 | break; 58 | } 59 | 60 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 61 | } 62 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.CreateAndShow(L"My App", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | // 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lollipopkit/flutter_2fa/702d5087b69ca548fa33a3a02df62172676eb0db/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr); 51 | if (target_length == 0) { 52 | return std::string(); 53 | } 54 | std::string utf8_string; 55 | utf8_string.resize(target_length); 56 | int converted_length = ::WideCharToMultiByte( 57 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 58 | -1, utf8_string.data(), 59 | target_length, nullptr, nullptr); 60 | if (converted_length == 0) { 61 | return std::string(); 62 | } 63 | return utf8_string; 64 | } 65 | -------------------------------------------------------------------------------- /windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | --------------------------------------------------------------------------------