├── .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 | 
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 |
7 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/ascendex.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/bitfinex.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/bitmex.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/bitwarden.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/bybit.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/cih.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/cloudflare.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/ente.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/github.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/google.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/jagex.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/laposte.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/mastodon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/microsoft.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/nextdns.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/ngrok.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/njalla.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/notion.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/nvidia.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/parsec.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/paypal.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/pingvinshare.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/privacy.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/proton.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/proxmox.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/revolt.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/simplelogin.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/termius.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/trading212.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/tradingview.svg:
--------------------------------------------------------------------------------
1 |
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 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/unity.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/whmcs.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/windscribe.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/assets/custom-icons/icons/x.svg:
--------------------------------------------------------------------------------
1 |
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 |
--------------------------------------------------------------------------------