Merge branch 'main' into f-droid
BIN
.github/assets/github-badge.png
vendored
Before Width: | Height: | Size: 12 KiB |
8
.github/workflows/auth-crowdin.yml
vendored
|
@ -2,15 +2,15 @@ name: "Sync Crowdin translations (auth)"
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
# Run workflow when auth's intl_en.arb is changed
|
||||
- "mobile/lib/l10n/arb/app_en.arb"
|
||||
# Or the workflow itself is changed
|
||||
- ".github/workflows/auth-crowdin.yml"
|
||||
branches: [main]
|
||||
schedule:
|
||||
# See: [Note: Run every 24 hours]
|
||||
- cron: "50 1 * * *"
|
||||
# See: [Note: Run workflow on specific days of the week]
|
||||
- cron: "50 1 * * 2,5"
|
||||
# Also allow manually running the workflow
|
||||
workflow_dispatch:
|
||||
|
||||
|
@ -28,7 +28,7 @@ jobs:
|
|||
base_path: "auth/"
|
||||
config: "auth/crowdin.yml"
|
||||
upload_sources: true
|
||||
upload_translations: true
|
||||
upload_translations: false
|
||||
download_translations: true
|
||||
localization_branch_name: crowdin-translations-auth
|
||||
create_pull_request: true
|
||||
|
|
4
.github/workflows/auth-lint.yml
vendored
|
@ -3,13 +3,13 @@ name: "Lint (auth)"
|
|||
on:
|
||||
# Run on every push to a branch other than main that changes auth/
|
||||
push:
|
||||
branches-ignore: [main]
|
||||
branches-ignore: [main, "deploy/**"]
|
||||
paths:
|
||||
- "auth/**"
|
||||
- ".github/workflows/auth-lint.yml"
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.16.9"
|
||||
FLUTTER_VERSION: "3.19.3"
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
|
|
34
.github/workflows/auth-release.yml
vendored
|
@ -29,11 +29,11 @@ on:
|
|||
- "auth-v*"
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.13.4"
|
||||
FLUTTER_VERSION: "3.19.3"
|
||||
|
||||
jobs:
|
||||
build-ubuntu:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
@ -72,6 +72,8 @@ jobs:
|
|||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||
|
||||
- name: Build PlayStore AAB
|
||||
# disable this step if release tag contains nightly or beta
|
||||
if: startsWith(github.ref, 'refs/tags/auth-v') && !contains(github.ref, 'nightly') && !contains(github.ref, 'beta')
|
||||
run: |
|
||||
flutter build appbundle --release --flavor playstore --dart-define=app.flavor=playstore
|
||||
env:
|
||||
|
@ -83,7 +85,7 @@ jobs:
|
|||
- name: Install dependencies for desktop build
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y libsecret-1-dev libsodium-dev libwebkit2gtk-4.0-dev libfuse2 ninja-build libgtk-3-dev dpkg-dev pkg-config rpm libsqlite3-dev locate
|
||||
sudo apt-get install -y libsecret-1-dev libsodium-dev libwebkit2gtk-4.0-dev libfuse2 ninja-build libgtk-3-dev dpkg-dev pkg-config rpm libsqlite3-dev locate appindicator3-0.1 libappindicator3-dev libffi7
|
||||
|
||||
- name: Install appimagetool
|
||||
run: |
|
||||
|
@ -92,8 +94,6 @@ jobs:
|
|||
mv appimagetool /usr/local/bin/
|
||||
|
||||
- name: Build desktop app
|
||||
# Temporarily disable desktop builds
|
||||
if: false
|
||||
run: |
|
||||
flutter config --enable-linux-desktop
|
||||
dart pub global activate flutter_distributor
|
||||
|
@ -118,6 +118,8 @@ jobs:
|
|||
updateOnlyUnreleased: true
|
||||
|
||||
- name: Upload AAB to PlayStore
|
||||
# disable this step if release tag contains nightly or beta
|
||||
if: startsWith(github.ref, 'refs/tags/auth-v') && !contains(github.ref, 'nightly') && !contains(github.ref, 'beta')
|
||||
uses: r0adkll/upload-google-play@v1
|
||||
with:
|
||||
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
|
||||
|
@ -149,8 +151,6 @@ jobs:
|
|||
run: mkdir artifacts
|
||||
|
||||
- name: Build Windows installer
|
||||
# Temporarily disable desktop builds
|
||||
if: false
|
||||
run: |
|
||||
flutter config --enable-windows-desktop
|
||||
dart pub global activate flutter_distributor
|
||||
|
@ -159,13 +159,9 @@ jobs:
|
|||
mv dist/**/ente_auth-*-windows-setup.exe artifacts/ente-${{ github.ref_name }}-installer.exe
|
||||
|
||||
- name: Retain Windows EXE and DLLs
|
||||
# Temporarily disable desktop builds
|
||||
if: false
|
||||
run: cp -r build/windows/x64/runner/Release ente-${{ github.ref_name }}-windows
|
||||
|
||||
- name: Code sign Windows installer and EXE
|
||||
# Temporarily disable desktop builds
|
||||
if: false
|
||||
uses: dlemstra/code-sign-action@v1
|
||||
with:
|
||||
certificate: "${{ secrets.WINDOWS_CERTIFICATE }}"
|
||||
|
@ -175,9 +171,10 @@ jobs:
|
|||
auth/ente-${{ github.ref_name }}-windows/auth.exe
|
||||
|
||||
- name: Zip Windows EXE and DLLs
|
||||
# Temporarily disable desktop builds
|
||||
if: false
|
||||
run: tar.exe -a -c -f auth/artifacts/ente-${{ github.ref_name }}-windows.zip auth/ente-${{ github.ref_name }}-windows
|
||||
run: tar.exe -a -c -f artifacts/ente-${{ github.ref_name }}-windows.zip ente-${{ github.ref_name }}-windows
|
||||
|
||||
- name: Generate checksums
|
||||
run: sha256sum artifacts/ente-* > artifacts/sha256sum-windows
|
||||
|
||||
- name: Create a draft GitHub release
|
||||
uses: ncipollo/release-action@v1
|
||||
|
@ -248,8 +245,6 @@ jobs:
|
|||
run: mkdir artifacts
|
||||
|
||||
- name: Build macOS DMG
|
||||
# Temporarily disable desktop builds
|
||||
if: false
|
||||
run: |
|
||||
flutter config --enable-macos-desktop
|
||||
dart pub global activate flutter_distributor
|
||||
|
@ -257,16 +252,12 @@ jobs:
|
|||
mv dist/**/ente_auth-*-macos.dmg artifacts/ente-${{ github.ref_name }}.dmg
|
||||
|
||||
- name: Code sign DMG
|
||||
# Temporarily disable desktop builds
|
||||
if: false
|
||||
run: |
|
||||
CERT_NAME=$(security find-identity -v -p codesigning | grep "Developer ID Application" | awk -F'"' '{print $2}' | grep -m1 "")
|
||||
codesign --force --timestamp --sign "$CERT_NAME" --options runtime artifacts/ente-${{ github.ref_name }}.dmg
|
||||
codesign --verify --verbose=4 artifacts/ente-${{ github.ref_name }}.dmg
|
||||
|
||||
- name: Notarize and staple DMG
|
||||
# Temporarily disable desktop builds
|
||||
if: false
|
||||
run: |
|
||||
xcrun notarytool submit artifacts/ente-${{ github.ref_name }}.dmg \
|
||||
--wait \
|
||||
|
@ -279,6 +270,9 @@ jobs:
|
|||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
|
||||
- name: Generate checksums
|
||||
run: shasum -a 256 artifacts/ente-* > artifacts/sha256sum-macos
|
||||
|
||||
- name: Create a draft GitHub release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
|
|
24
.github/workflows/copycat-db-release.yaml
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
name: "Release (copycat-db)"
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Run manually
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
name: Check out code
|
||||
|
||||
- uses: mr-smithers-excellent/docker-build-push@v6
|
||||
name: Build & Push
|
||||
with:
|
||||
dockerfile: infra/copycat-db/Dockerfile
|
||||
directory: infra/copycat-db
|
||||
image: ente/copycat-db
|
||||
registry: rg.fr-par.scw.cloud
|
||||
enableBuildKit: true
|
||||
buildArgs: GIT_COMMIT=${GITHUB_SHA}
|
||||
tags: ${GITHUB_SHA}, latest
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
2
.github/workflows/docs-verify-build.yml
vendored
|
@ -6,7 +6,7 @@ name: "Verify build (docs)"
|
|||
on:
|
||||
# Run on every push to a branch other than main that changes docs/
|
||||
push:
|
||||
branches-ignore: [main]
|
||||
branches-ignore: [main, "deploy/**"]
|
||||
paths:
|
||||
- "docs/**"
|
||||
- ".github/workflows/docs-verify-build.yml"
|
||||
|
|
8
.github/workflows/mobile-crowdin.yml
vendored
|
@ -2,15 +2,15 @@ name: "Sync Crowdin translations (mobile)"
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
# Run workflow when mobiles's intl_en.arb is changed
|
||||
- "mobile/lib/l10n/intl_en.arb"
|
||||
# Or the workflow itself is changed
|
||||
- ".github/workflows/mobile-crowdin.yml"
|
||||
branches: [main]
|
||||
schedule:
|
||||
# See: [Note: Run every 24 hours]
|
||||
- cron: "40 1 * * *"
|
||||
# See: [Note: Run workflow on specific days of the week]
|
||||
- cron: "40 1 * * 2,5"
|
||||
# Also allow manually running the workflow
|
||||
workflow_dispatch:
|
||||
|
||||
|
@ -28,7 +28,7 @@ jobs:
|
|||
base_path: "mobile/"
|
||||
config: "mobile/crowdin.yml"
|
||||
upload_sources: true
|
||||
upload_translations: true
|
||||
upload_translations: false
|
||||
download_translations: true
|
||||
localization_branch_name: crowdin-translations-mobile
|
||||
create_pull_request: true
|
||||
|
|
2
.github/workflows/mobile-lint.yml
vendored
|
@ -3,7 +3,7 @@ name: "Lint (mobile)"
|
|||
on:
|
||||
# Run on every push to a branch other than main that changes mobile/
|
||||
push:
|
||||
branches-ignore: [main, f-droid]
|
||||
branches-ignore: [main, f-droid, "deploy/**"]
|
||||
paths:
|
||||
- "mobile/**"
|
||||
- ".github/workflows/mobile-lint.yml"
|
||||
|
|
2
.github/workflows/server-lint.yml
vendored
|
@ -3,7 +3,7 @@ name: "Lint (server)"
|
|||
on:
|
||||
# Run on every push to a branch other than main that changes server/
|
||||
push:
|
||||
branches-ignore: [main]
|
||||
branches-ignore: [main, "deploy/**"]
|
||||
paths:
|
||||
- "server/**"
|
||||
- ".github/workflows/server-lint.yml"
|
||||
|
|
38
.github/workflows/server-publish.yml
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
name: "Publish (server)"
|
||||
|
||||
on:
|
||||
# Run manually, providing it the commit.
|
||||
#
|
||||
# To obtain the commit from the currently deployed museum, do:
|
||||
# curl -s https://api.ente.io/ping | jq -r '.id'
|
||||
#
|
||||
# See server/docs/publish.md for more details.
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
commit:
|
||||
description: "Commit to publish the image from"
|
||||
type: string
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.commit }}
|
||||
|
||||
- name: Build and push
|
||||
uses: mr-smithers-excellent/docker-build-push@v6
|
||||
with:
|
||||
dockerfile: server/Dockerfile
|
||||
directory: server
|
||||
# Resultant package name will be ghcr.io/ente-io/server
|
||||
image: server
|
||||
registry: ghcr.io
|
||||
enableBuildKit: true
|
||||
buildArgs: GIT_COMMIT=${{ inputs.commit }}
|
||||
tags: ${{ inputs.commit }}, latest
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
8
.github/workflows/server-release.yml
vendored
|
@ -7,11 +7,11 @@ jobs:
|
|||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
name: Check out code
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: mr-smithers-excellent/docker-build-push@v6
|
||||
name: Build & Push
|
||||
- name: Build and push
|
||||
uses: mr-smithers-excellent/docker-build-push@v6
|
||||
with:
|
||||
dockerfile: server/Dockerfile
|
||||
directory: server
|
||||
|
|
14
.github/workflows/web-crowdin.yml
vendored
|
@ -2,15 +2,21 @@ name: "Sync Crowdin translations (web)"
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
# Run workflow when web's en-US/translation.json is changed
|
||||
- "web/apps/photos/public/locales/en-US/translation.json"
|
||||
# Or the workflow itself is changed
|
||||
- ".github/workflows/web-crowdin.yml"
|
||||
branches: [main]
|
||||
schedule:
|
||||
# See: [Note: Run every 24 hours]
|
||||
- cron: "20 1 * * *"
|
||||
# [Note: Run workflow on specific days of the week]
|
||||
#
|
||||
# The last (5th) component of the cron syntax denotes the day of the
|
||||
# week, with 0 == SUN and 6 == SAT. So, for example, to run on every TUE
|
||||
# and FRI, this can be set to `2,5`.
|
||||
#
|
||||
# See also: [Note: Run workflow every 24 hours]
|
||||
- cron: "20 1 * * 2,5"
|
||||
# Also allow manually running the workflow
|
||||
workflow_dispatch:
|
||||
|
||||
|
@ -28,7 +34,7 @@ jobs:
|
|||
base_path: "web/"
|
||||
config: "web/crowdin.yml"
|
||||
upload_sources: true
|
||||
upload_translations: true
|
||||
upload_translations: false
|
||||
download_translations: true
|
||||
localization_branch_name: crowdin-translations-web
|
||||
create_pull_request: true
|
||||
|
|
43
.github/workflows/web-deploy-payments.yml
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
name: "Deploy (payments)"
|
||||
|
||||
on:
|
||||
push:
|
||||
# Run workflow on pushes to the deploy/payments
|
||||
branches: [deploy/payments]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: web
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup node and enable yarn caching
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "yarn"
|
||||
cache-dependency-path: "docs/yarn.lock"
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
|
||||
- name: Build payments
|
||||
run: yarn build:payments
|
||||
|
||||
- name: Publish payments
|
||||
uses: cloudflare/pages-action@1
|
||||
with:
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
projectName: ente
|
||||
branch: deploy/payments
|
||||
directory: web/apps/payments/out
|
||||
wranglerVersion: "3"
|
2
.github/workflows/web-lint.yml
vendored
|
@ -3,7 +3,7 @@ name: "Lint (web)"
|
|||
on:
|
||||
# Run on every push to a branch other than main that changes web/
|
||||
push:
|
||||
branches-ignore: [main]
|
||||
branches-ignore: [main, "deploy/**"]
|
||||
paths:
|
||||
- "web/**"
|
||||
- ".github/workflows/web-lint.yml"
|
||||
|
|
15
.github/workflows/web-nightly.yml
vendored
|
@ -2,7 +2,7 @@ name: "Nightly (web)"
|
|||
|
||||
on:
|
||||
schedule:
|
||||
# [Note: Run every 24 hours]
|
||||
# [Note: Run workflow every 24 hours]
|
||||
#
|
||||
# Run every 24 hours - First field is minute, second is hour of the day
|
||||
# This runs 23:15 UTC everyday - 1 and 15 are just arbitrary offset to
|
||||
|
@ -78,6 +78,19 @@ jobs:
|
|||
directory: web/apps/cast/out
|
||||
wranglerVersion: "3"
|
||||
|
||||
- name: Build payments
|
||||
run: yarn build:payments
|
||||
|
||||
- name: Publish payments
|
||||
uses: cloudflare/pages-action@1
|
||||
with:
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
projectName: ente
|
||||
branch: n-payments
|
||||
directory: web/apps/payments/out
|
||||
wranglerVersion: "3"
|
||||
|
||||
- name: Build photos
|
||||
run: yarn build:photos
|
||||
env:
|
||||
|
|
1
.github/workflows/web-preview.yml
vendored
|
@ -12,6 +12,7 @@ on:
|
|||
- "accounts"
|
||||
- "auth"
|
||||
- "cast"
|
||||
- "payments"
|
||||
- "photos"
|
||||
|
||||
jobs:
|
||||
|
|
|
@ -70,7 +70,7 @@ existing users will be grandfathered in.
|
|||
[<img height="42" src=".github/assets/app-store-badge.svg">](https://apps.apple.com/app/id6444121398)
|
||||
[<img height="42" src=".github/assets/play-store-badge.png">](https://play.google.com/store/apps/details?id=io.ente.auth)
|
||||
[<img height="42" src=".github/assets/f-droid-badge.png">](https://f-droid.org/packages/io.ente.auth/)
|
||||
[<img height="42" src=".github/assets/github-badge.png">](https://github.com/ente-io/ente/releases?q=tag%3Aauth-v2)
|
||||
[<img height="42" src=".github/assets/desktop-badge.png">](https://github.com/ente-io/ente/releases?q=tag%3Aauth-v2)
|
||||
[<img height="42" src=".github/assets/web-badge.svg">](https://auth.ente.io)
|
||||
|
||||
</div>
|
||||
|
|
9
auth/.gitignore
vendored
|
@ -9,12 +9,20 @@
|
|||
.history
|
||||
.svn/
|
||||
|
||||
# Editors
|
||||
.vscode/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
|
@ -32,3 +40,4 @@ lib/generated_plugin_registrant.dart
|
|||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||
|
||||
android/key.properties
|
||||
dist/
|
|
@ -1,11 +1,11 @@
|
|||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled.
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
|
||||
channel: unknown
|
||||
revision: "ba393198430278b6595976de84fe170f553cc728"
|
||||
channel: "[user-branch]"
|
||||
|
||||
project_type: app
|
||||
|
||||
|
@ -13,17 +13,26 @@ project_type: app
|
|||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
|
||||
base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
|
||||
create_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
base_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
- platform: android
|
||||
create_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
base_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
- platform: ios
|
||||
create_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
base_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
- platform: linux
|
||||
create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
|
||||
base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
|
||||
create_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
base_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
- platform: macos
|
||||
create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
|
||||
base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
|
||||
create_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
base_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
- platform: web
|
||||
create_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
base_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
- platform: windows
|
||||
create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
|
||||
base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
|
||||
create_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
base_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
|
||||
# User provided section
|
||||
|
||||
|
|
|
@ -31,14 +31,16 @@ You can alternatively install the build from PlayStore or F-Droid.
|
|||
<img height="59" src="../.github/assets/app-store-badge.svg">
|
||||
</a>
|
||||
|
||||
### Desktop
|
||||
|
||||
You can [**download**](https://github.com/ente-io/ente/releases?q=tag%3Aauth-v2)
|
||||
a native desktop app from this repository's GitHub releases. The desktop app
|
||||
works on Windows, Linux and macOS.
|
||||
|
||||
### Web
|
||||
|
||||
You can view your 2FA codes at [auth.ente.io](https://auth.ente.io). For adding
|
||||
or managing your secrets, please use our mobile app.
|
||||
|
||||
### Desktop
|
||||
|
||||
A native desktop app is coming soon!
|
||||
or managing your secrets, please use our mobile or desktop app.
|
||||
|
||||
## 🧑💻 Build from source
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ if (keystorePropertiesFile.exists()) {
|
|||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
compileSdkVersion 34
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
|
@ -46,7 +46,7 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
applicationId "io.ente.auth"
|
||||
minSdkVersion 20
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
|
@ -56,11 +56,11 @@ android {
|
|||
|
||||
signingConfigs {
|
||||
release {
|
||||
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : System.getenv("SIGNING_KEY_PATH") ? file(System.getenv("SIGNING_KEY_PATH")) : null
|
||||
keyAlias keystoreProperties['keyAlias'] ? keystoreProperties['keyAlias'] : System.getenv("SIGNING_KEY_ALIAS")
|
||||
keyPassword keystoreProperties['keyPassword'] ? keystoreProperties['keyPassword'] : System.getenv("SIGNING_KEY_PASSWORD")
|
||||
storePassword keystoreProperties['storePassword'] ? keystoreProperties['storePassword'] : System.getenv("SIGNING_STORE_PASSWORD")
|
||||
}
|
||||
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : System.getenv("SIGNING_KEY_PATH") ? file(System.getenv("SIGNING_KEY_PATH")) : null
|
||||
keyAlias keystoreProperties['keyAlias'] ? keystoreProperties['keyAlias'] : System.getenv("SIGNING_KEY_ALIAS")
|
||||
keyPassword keystoreProperties['keyPassword'] ? keystoreProperties['keyPassword'] : System.getenv("SIGNING_KEY_PASSWORD")
|
||||
storePassword keystoreProperties['storePassword'] ? keystoreProperties['storePassword'] : System.getenv("SIGNING_STORE_PASSWORD")
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions "default"
|
||||
|
@ -109,6 +109,7 @@ dependencies {
|
|||
implementation 'io.sentry:sentry-android:2.0.0'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'com.android.support:multidex:1.0.3'
|
||||
implementation 'com.google.guava:guava:28.2-android'
|
||||
implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test:runner:1.1.1'
|
||||
|
|
|
@ -36,7 +36,9 @@
|
|||
},
|
||||
{
|
||||
"title": "BorgBase",
|
||||
"altNames": ["borg"],
|
||||
"altNames": [
|
||||
"borg"
|
||||
],
|
||||
"slug": "BorgBase"
|
||||
},
|
||||
{
|
||||
|
@ -46,11 +48,17 @@
|
|||
{
|
||||
"title": "Bybit"
|
||||
},
|
||||
{
|
||||
"title": "CERN"
|
||||
},
|
||||
{
|
||||
"title": "Channel Island Hosting",
|
||||
"slug": "cih",
|
||||
"hex": "D14633"
|
||||
},
|
||||
{
|
||||
"title": "ConfigCat"
|
||||
},
|
||||
{
|
||||
"title": "Cloudflare"
|
||||
},
|
||||
|
@ -62,6 +70,13 @@
|
|||
{
|
||||
"title": "Crowdpear"
|
||||
},
|
||||
{
|
||||
"title": "DCS",
|
||||
"altNames": [
|
||||
"Digital Combat Simulator"
|
||||
],
|
||||
"slug": "dcs"
|
||||
},
|
||||
{
|
||||
"title": "DEGIRO"
|
||||
},
|
||||
|
@ -107,9 +122,14 @@
|
|||
},
|
||||
{
|
||||
"title": "Gosuslugi",
|
||||
"altNames": ["Госуслуги"],
|
||||
"altNames": [
|
||||
"Госуслуги"
|
||||
],
|
||||
"slug": "Gosuslugi"
|
||||
},
|
||||
{
|
||||
"title": "Habbo"
|
||||
},
|
||||
{
|
||||
"title": "Healthchecks.io",
|
||||
"slug": "healthchecks"
|
||||
|
@ -172,13 +192,24 @@
|
|||
},
|
||||
{
|
||||
"title": "Mastodon",
|
||||
"altNames": ["mstdn", "fediscience", "mathstodon", "fosstodon"],
|
||||
"altNames": [
|
||||
"mstdn",
|
||||
"fediscience",
|
||||
"mathstodon",
|
||||
"fosstodon"
|
||||
],
|
||||
"slug": "mastodon",
|
||||
"hex": "6364FF"
|
||||
},
|
||||
{
|
||||
"title": "Mercado Livre",
|
||||
"slug": "mercado_livre"
|
||||
},
|
||||
{
|
||||
"title": "Murena",
|
||||
"altNames": ["eCloud"],
|
||||
"altNames": [
|
||||
"eCloud"
|
||||
],
|
||||
"slug": "ecloud"
|
||||
},
|
||||
{
|
||||
|
@ -267,11 +298,18 @@
|
|||
"title": "Revolt",
|
||||
"hex": "858585"
|
||||
},
|
||||
{
|
||||
"title": "Rockstar Games",
|
||||
"slug": "rockstar_games"
|
||||
},
|
||||
{
|
||||
"title": "Rust Language Forum",
|
||||
"slug": "rust_language_forum",
|
||||
"hex": "000000"
|
||||
},
|
||||
{
|
||||
"title": "Sendgrid"
|
||||
},
|
||||
{
|
||||
"title": "service-bw"
|
||||
},
|
||||
|
@ -356,15 +394,24 @@
|
|||
{
|
||||
"title": "Wise"
|
||||
},
|
||||
{
|
||||
"title": "WYZE",
|
||||
"slug": "wyze"
|
||||
},
|
||||
{
|
||||
"title": "X",
|
||||
"altNames": ["twitter"],
|
||||
"altNames": [
|
||||
"twitter"
|
||||
],
|
||||
"slug": "x"
|
||||
},
|
||||
{
|
||||
"title": "Yandex",
|
||||
"altNames": ["Ya", "Яндекс"],
|
||||
"altNames": [
|
||||
"Ya",
|
||||
"Яндекс"
|
||||
],
|
||||
"slug": "Yandex"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
47
auth/assets/custom-icons/icons/CERN.svg
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 283.465 283.465" enable-background="new 0 0 283.465 283.465" xml:space="preserve">
|
||||
<rect x="0.752" y="-0.337" fill="#FFFFFF" width="283.465" height="283.465"/>
|
||||
<path fill="#0033A0" d="M210.282,120.915c0.429,24.998-11.023,41.967-14.629,47.856c-3.143,5.13-10.654,15.024-25.11,30.374
|
||||
c-18.235,19.365-75.231,79.947-79.029,83.983h154.558l-35.636-162.227L210.282,120.915z M224.498,203.72l1.277,5.803
|
||||
c-15.302,16.528-38.73,28.912-66,28.912c-5.841,0-11.941-0.565-18.719-2.005c1.375-1.463,2.693-2.867,3.937-4.188
|
||||
c4.352,0.773,9.361,1.284,14.506,1.286C186.023,233.541,209.198,221.605,224.498,203.72z M0.752-0.337v283.465h84.595l83.686-88.992
|
||||
l-0.117-0.107c-13.455,9.491-30.532,14.646-48.943,14.646c-34.121,0-64.191-20.244-77.232-45.437l-0.132,0.088l17.755,59.9h-4.806
|
||||
c0,0-8.809-30.352-16.33-56.812c-5.692-20.026-9.198-34.861-9.154-48.029c0.156-45.284,35.77-86.809,83.128-89.874
|
||||
c1.3-0.086,5.328-0.506,11.328-0.511c36.48-0.022,148.884,0.84,159.687,0.923v-29.26H0.752z M100.261,210.453
|
||||
c6.786,5.88,17.542,13.273,30.838,18.05c-1.113,1.185-2.321,2.469-3.603,3.833c-13.279-5.257-26.271-13.409-36.369-24.392
|
||||
C93.969,208.901,97.099,209.768,100.261,210.453z M145.883,32.427c13.977,3.923,27.086,11.109,37.504,20.825
|
||||
c-3.006-0.803-6.071-1.451-9.188-1.939c-14.647-11.578-33.714-18.482-53.64-18.482c-47.069,0-85.592,38.386-85.592,85.565
|
||||
c0,47.18,38.382,85.564,85.565,85.564c47.179,0,85.565-38.384,85.565-85.564c0-18.252-6.876-36.487-16.318-49.367
|
||||
c2.255,0.678,5.004,1.841,8.162,3.708c6.393,9.69,12.32,25.515,17.398,49.787c5.33,25.462,32.732,147.426,35.694,160.605h33.182
|
||||
V33.152l-138.333-0.85C145.883,32.303,145.883,32.311,145.883,32.427z M50.595,116.383c0-11.441,8.652-19.051,20.412-19.051
|
||||
c4.577,0,9.814,1.409,12.738,2.664c-0.611,1.353-1.113,3.142-1.326,4.258l-0.319,0.106c-2.261-2.503-5.898-4.672-11.279-4.672
|
||||
c-6.83,0-14.689,5.528-14.689,16.557c0,10.737,8.009,16.442,15.169,16.442c6.434,0,9.513-3,12.278-5.337l0.212,0.213l-0.783,4.172
|
||||
c-1.268,0.96-5.664,3.695-12.279,3.695C58.754,135.429,50.595,127.885,50.595,116.383z M78.765,187.656
|
||||
c-6.344-12.899-9.219-25.995-9.6-38.519c1.612,0,3.481,0.067,5.093,0.067c0.556,11.987,3.274,26.892,12.648,43.087
|
||||
C83.521,191.047,80.974,189.405,78.765,187.656z M112.787,134.755c0,0.001,0,0.001,0,0.001c-1.911-0.098-4.565-0.176-7.084-0.221
|
||||
c-1.451-0.024-2.861-0.041-3.973-0.044c-0.158,0-0.319,0-0.47,0c-3.247,0-8.227,0.105-11.475,0.266
|
||||
c0.214-4.631,0.429-9.26,0.429-13.836v-9.15c0-4.578-0.215-9.206-0.429-13.728c3.193,0.16,8.121,0.265,11.313,0.265
|
||||
c3.193,0,9.149-0.141,10.991-0.265c-0.078,0.498-0.127,1.089-0.127,1.815c0,0.725,0.071,1.473,0.127,1.836
|
||||
c-3.505-0.263-9.766-0.692-16.682-0.692c-0.056,2.287-0.162,11.991-0.162,13.324c6.278,0,10.301-0.269,13.441-0.532
|
||||
c-0.105,0.532-0.16,1.486-0.16,2.017c0,0.532,0.054,1.32,0.16,1.853c-3.671-0.374-11.887-0.48-13.441-0.48
|
||||
c-0.095,1.779-0.012,13.294,0.053,14.266c3.889-0.057,13.857-0.359,17.489-0.693c-0.057,0.403-0.125,1.233-0.125,2.042
|
||||
C112.661,133.608,112.708,134.198,112.787,134.755z M144.039,134.58c-0.485,0-2.321,0.017-3.334,0.177
|
||||
c-2.103-3.205-8.839-13.302-13.419-18.015c-0.137,0-2.708,0.003-2.708,0.003v4.23c0,4.575,0.212,9.205,0.426,13.781
|
||||
c-0.906-0.161-2.542-0.177-2.877-0.177c-0.335,0-1.971,0.017-2.878,0.177c0.214-4.577,0.428-9.206,0.428-13.781v-9.152
|
||||
c0-4.577-0.213-9.207-0.428-13.782c2.024,0.16,4.584,0.265,6.606,0.265c2.021,0,4.043-0.265,6.064-0.265
|
||||
c6.013,0,11.523,1.776,11.523,8.472c0,7.084-7.06,9.632-11.104,10.163c2.606,3.246,11.946,14.619,15.032,18.08
|
||||
C146.309,134.596,144.525,134.58,144.039,134.58z M184.319,135.253l-1.742-0.017c-2.13-2.888-24.461-26.18-26.395-28.237
|
||||
c-0.053,1.967-0.055,6.062-0.055,10.043c0,5.287,0.4,13.354,0.634,17.714c-0.54-0.098-1.338-0.195-2.268-0.195
|
||||
c-0.939,0-1.709,0.086-2.331,0.195c0.436-5.625,0.558-14.755,0.558-23.337c0-6.704-0.099-10.38-0.178-13.762l1.744,0.015
|
||||
c2.256,2.45,24.457,25.428,26.392,27.487c0.053-1.967,0.057-5.441,0.057-9.424c0-5.285-0.402-13.354-0.636-17.711
|
||||
c0.542,0.094,1.338,0.192,2.269,0.192c0.942,0,1.71-0.084,2.332-0.192c-0.438,5.624-0.56,14.753-0.56,23.336
|
||||
C184.14,128.063,184.239,131.868,184.319,135.253z M230.093,88.11c9.889,12.128,17.896,31.314,19.065,47.379h0.156l8.24-85.104
|
||||
l4.646-0.003c0,0-5.271,55.013-8.341,82.351c-3.844,34.241-8.729,48.448-18.071,63.403l-1.43-6.508
|
||||
c7.28-12.787,9.357-23.946,10.306-29.823c2.956-18.304-1.273-43.291-14.025-62.789c-14.291-21.849-39.84-37.924-71.2-37.924
|
||||
c-25.762,0-48.143,11.327-63.766,28.993l-3.789-3.003c16.539-18.764,40.576-30.737,67.558-30.737
|
||||
C187.735,54.346,212.924,67.058,230.093,88.11z M138.305,107.241c0-5.29-4.629-6.973-8.248-6.973c-2.448,0-4.043,0.161-5.162,0.268
|
||||
c-0.159,3.885-0.318,7.457-0.318,11.287v2.926c0.529,0.071,3.021,0.059,3.566,0.049
|
||||
C132.537,114.707,138.305,113.312,138.305,107.241z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 4.9 KiB |
9
auth/assets/custom-icons/icons/configcat.svg
Normal file
After Width: | Height: | Size: 68 KiB |
9
auth/assets/custom-icons/icons/dcs.svg
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;">
|
||||
<g transform="matrix(1.59257,0,0,1.59257,0,9.06171)">
|
||||
<path d="M0.87,0.08L3.17,0.08C4.2,0.08 5.13,0.51 5.13,1.67C5.13,2.92 3.91,3.6 2.78,3.6L0.06,3.6C0.06,3.6 0.87,0.09 0.87,0.08ZM2.85,2.82C3.44,2.82 4.14,2.39 4.14,1.74C4.14,1.19 3.7,0.85 3.17,0.85L1.71,0.85L1.26,2.81L2.85,2.81L2.85,2.82Z" style="fill:rgb(245,158,15);fill-rule:nonzero;stroke:rgb(245,158,15);stroke-width:0.09px;"/>
|
||||
<path d="M11.95,1.33C11.87,1.31 11.65,1.26 11.65,1.14C11.65,0.81 12.52,0.81 12.74,0.81C13.25,0.81 13.96,0.91 14.4,1.17L15,0.59C14.39,0.18 13.59,0.04 12.86,0.04C12.07,0.04 10.65,0.18 10.65,1.24C10.65,2.45 13.65,1.96 13.65,2.52C13.65,2.86 12.89,2.88 12.66,2.88C11.9,2.88 11.39,2.74 10.77,2.29C10.57,2.48 10.36,2.67 10.16,2.86C10.95,3.44 11.7,3.64 12.67,3.64C13.4,3.64 14.68,3.36 14.68,2.42C14.68,1.34 12.67,1.5 11.97,1.32L11.95,1.32L11.95,1.33Z" style="fill:rgb(245,158,15);fill-rule:nonzero;stroke:rgb(245,158,15);stroke-width:0.09px;"/>
|
||||
<path d="M9.18,2.35L9.16,2.35C9.07,2.43 8.41,2.95 7.67,2.87C7.14,2.81 6.41,2.44 6.41,1.95C6.41,1.15 7.26,0.83 7.94,0.83C8.39,0.83 8.82,0.93 9.17,1.18C9.48,1.07 9.8,0.97 10.11,0.86C9.59,0.3 8.82,0.07 8.06,0.07C6.92,0.07 5.43,0.73 5.43,2.06C5.43,3.22 6.65,3.63 7.61,3.63C8.41,3.63 9.18,3.4 9.78,2.88C9.78,2.88 9.18,2.38 9.17,2.37L9.16,2.37L9.18,2.35Z" style="fill:rgb(245,158,15);fill-rule:nonzero;stroke:rgb(245,158,15);stroke-width:0.09px;"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
9
auth/assets/custom-icons/icons/habbo.svg
Normal file
After Width: | Height: | Size: 26 KiB |
9
auth/assets/custom-icons/icons/mercado_livre.svg
Normal file
After Width: | Height: | Size: 57 KiB |
1
auth/assets/custom-icons/icons/rockstar_games.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="6.525 7.459 339.266 319.582"><path fill="orange" d="M71.598 11.25H280.72c33.844 0 61.282 25.782 61.282 57.586v196.512c0 31.804-27.437 57.586-61.282 57.586H71.598c-33.845 0-61.28-25.782-61.28-57.586V68.836c0-31.804 27.435-57.586 61.28-57.586z"/><path d="M280.719 326.725H71.598c-35.881 0-65.072-27.533-65.072-61.377V68.836c0-33.844 29.19-61.377 65.072-61.377H280.72c35.88 0 65.072 27.533 65.072 61.377v196.512c0 33.844-29.192 61.377-65.073 61.377zM71.598 15.042c-31.7 0-57.49 24.131-57.49 53.794v196.512c0 29.662 25.79 53.794 57.49 53.794H280.72c31.7 0 57.49-24.132 57.49-53.794V68.836c0-29.662-25.79-53.794-57.49-53.794H71.598z"/><path d="M127.423 64.013l62.975.149c13.161-.099 22.989 2.002 29.48 6.303 7.928 5.272 11.89 14.343 11.89 27.213 0 21.19-9.828 33.195-29.482 36.012v.297c8.667 2.159 13.048 9.335 13.146 21.528 0 6.245-.233 14.245-.7 24 0 6.542 1.384 12.202 4.156 16.98H184.38c-1.611-1.747-2.416-5.305-2.416-10.677.66-8.98.99-16.347.99-22.098 0-11.617-5.582-17.425-16.746-17.425h-22.329l-9.738 46.987h-33.613l26.895-129.269zm53.236 26.941h-24.744l-6.453 31.192h26.775c14.967 0 22.497-5.906 22.595-17.721.001-8.98-6.058-13.47-18.173-13.47z"/><path d="M223.456 196.346l24.915-43.18 6.717 43.478h42.506l-38.349 27.534 6.14 43.18-33.204-26.05-44.633 27.089 20.878-45.973-24.333-26.2 39.363.122zm113.568 113.887c1.38 0 2.726.362 4.04 1.086 1.315.723 2.339 1.76 3.07 3.108.735 1.348 1.101 2.753 1.101 4.216 0 1.449-.36 2.841-1.084 4.177a7.735 7.735 0 01-3.039 3.114c-1.302.74-2.665 1.108-4.09 1.108-1.421 0-2.784-.37-4.089-1.108a7.762 7.762 0 01-3.044-3.114c-.725-1.336-1.089-2.73-1.089-4.177 0-1.463.369-2.868 1.106-4.216.738-1.348 1.763-2.384 3.077-3.108 1.316-.725 2.662-1.086 4.04-1.086zm0 1.391c-1.154 0-2.278.303-3.37.909a6.451 6.451 0 00-2.566 2.594c-.617 1.126-.926 2.297-.926 3.514 0 1.211.304 2.372.91 3.482a6.542 6.542 0 002.542 2.596c1.089.619 2.224.93 3.409.93 1.183 0 2.32-.311 3.41-.93a6.498 6.498 0 002.536-2.596c.603-1.11.904-2.27.904-3.482 0-1.218-.308-2.388-.92-3.514a6.39 6.39 0 00-2.565-2.594c-1.094-.606-2.216-.909-3.364-.909zm-3.604 11.664v-9.045h3.038c1.039 0 1.79.083 2.254.25.466.167.835.459 1.11.875.277.416.415.857.415 1.325 0 .661-.23 1.237-.692 1.726-.46.49-1.072.764-1.835.824.312.135.563.294.75.48.358.356.792.954 1.308 1.792l1.078 1.772h-1.742l-.783-1.426c-.617-1.12-1.115-1.823-1.492-2.105-.262-.209-.643-.313-1.145-.313h-.838v3.844h-1.426zm1.426-5.092h1.732c.829 0 1.393-.126 1.694-.38.3-.25.451-.586.451-1.002a1.24 1.24 0 00-.218-.718 1.302 1.302 0 00-.604-.473c-.258-.103-.734-.157-1.431-.157h-1.624v2.73z"/><path fill="#FFF" d="M252.503 221.088l25.47-18.142h-28.177l-4.881-31.464-17.882 31.168h-28.467l17.302 18.587-14.305 31.193 31.052-18.735 24.48 19.18-4.592-31.787z"/></svg>
|
After Width: | Height: | Size: 2.7 KiB |
9
auth/assets/custom-icons/icons/sendgrid.svg
Normal file
After Width: | Height: | Size: 59 KiB |
1
auth/assets/custom-icons/icons/wyze.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg fill="#1DF0BB" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Wyze</title><path d="M5.475 13.171 7.3 9.469h.974L5.779 14.53h-.608l-1.034-2.082-1.034 2.082h-.609L0 9.469h.973l1.826 3.673.851-1.706-.973-1.967h.973l1.825 3.702Zm8.457-3.702-2.251 3.442v1.591h-.882v-1.591L8.517 9.469h1.034l1.673 2.545 1.673-2.545h1.035Zm5.444 4.194H24v.867h-4.624v-.867Zm0-4.194H24v.868h-4.624v-.868Zm0 2.083H24v.867h-4.624v-.867Zm-.273-2.083-3.438 4.223h3.133v.838H13.84l3.407-4.222h-3.042v-.839h4.898Z"/></svg>
|
After Width: | Height: | Size: 523 B |
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
BIN
auth/assets/icons/auth-icon.ico
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
auth/assets/icons/auth-icon.png
Normal file
After Width: | Height: | Size: 38 KiB |
25
auth/distribute_options.yaml
Normal file
|
@ -0,0 +1,25 @@
|
|||
output: dist/
|
||||
|
||||
releases:
|
||||
- name: dev
|
||||
jobs:
|
||||
- name: release-dev-linux-zip
|
||||
package:
|
||||
platform: linux
|
||||
target: zip
|
||||
- name: release-dev-linux-deb
|
||||
package:
|
||||
platform: linux
|
||||
target: deb
|
||||
- name: release-dev-linux-appimage
|
||||
package:
|
||||
platform: linux
|
||||
target: appimage
|
||||
- name: release-dev-windows-exe
|
||||
package:
|
||||
platform: windows
|
||||
target: exe
|
||||
- name: release-dev-macos-dmg
|
||||
package:
|
||||
platform: macos
|
||||
target: dmg
|
|
@ -1,7 +1,14 @@
|
|||
# Releases
|
||||
|
||||
Create a PR to bump up the version in `pubspec.yaml`. Once that is merged, tag
|
||||
main, and push the tag.
|
||||
Create a PR to bump up the version in `pubspec.yaml`.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> Use [semver](https://semver.org/) for the tags, with `auth-` as a prefix.
|
||||
> Multiple beta releases for the same upcoming version can be done by adding
|
||||
> build metadata at the end, e.g. `auth-v1.2.3-beta+3`.
|
||||
|
||||
Once that is merged, tag main, and push the tag.
|
||||
|
||||
```sh
|
||||
git tag auth-v1.2.3
|
||||
|
@ -16,6 +23,11 @@ This'll trigger a GitHub workflow that:
|
|||
* Creates a new release in the internal track on Play Store.
|
||||
|
||||
Once the workflow completes, go to the draft GitHub release that was created.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> Keep the title of the release same as the tag.
|
||||
|
||||
Set "Previous tag" to the last release of auth and press "Generate release
|
||||
notes". The generated release note will contain all PRs and new contributors
|
||||
from all the releases in the monorepo, so you'll need to filter them to keep
|
||||
|
|
|
@ -37,4 +37,4 @@ file, that adheres to the above format.
|
|||
SUPPORT
|
||||
|
||||
If you need help, please reach out to support@ente.io, and a human will get in touch with you.
|
||||
If you have feature requests, please create an issue @ https://github.com/ente-io/auth
|
||||
If you have feature requests, please create an issue @ https://github.com/ente-io/ente
|
||||
|
|
|
@ -1 +1 @@
|
|||
ente Authenticator
|
||||
Ente Authenticator
|
|
@ -1,6 +1,6 @@
|
|||
flutter_icons:
|
||||
android: "launcher_icon"
|
||||
image_path: "assets/icon-light.png"
|
||||
adaptive_icon_foreground: "assets/icon-light-adaptive-fg.png"
|
||||
adaptive_icon_background: "#ffffff"
|
||||
|
||||
flutter_icons:
|
||||
android: "launcher_icon"
|
||||
image_path: "assets/generation-icons/icon-light.png"
|
||||
adaptive_icon_foreground: "assets/generation-icons/icon-light-adaptive-fg.png"
|
||||
adaptive_icon_background: "#ffffff"
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 41456452f29d64e8deb623a3c927524bcf9f111b
|
||||
Subproject commit ba393198430278b6595976de84fe170f553cc728
|
|
@ -1,7 +1,9 @@
|
|||
PODS:
|
||||
- connectivity (0.0.1):
|
||||
- app_links (0.0.1):
|
||||
- Flutter
|
||||
- Reachability
|
||||
- connectivity_plus (0.0.1):
|
||||
- Flutter
|
||||
- ReachabilitySwift
|
||||
- device_info_plus (0.0.1):
|
||||
- Flutter
|
||||
- DKImagePickerController/Core (4.3.4):
|
||||
|
@ -45,27 +47,26 @@ PODS:
|
|||
- Flutter (1.0.0)
|
||||
- flutter_email_sender (0.0.1):
|
||||
- Flutter
|
||||
- flutter_inappwebview (0.0.1):
|
||||
- flutter_inappwebview_ios (0.0.1):
|
||||
- Flutter
|
||||
- flutter_inappwebview/Core (= 0.0.1)
|
||||
- flutter_inappwebview_ios/Core (= 0.0.1)
|
||||
- OrderedSet (~> 5.0)
|
||||
- flutter_inappwebview/Core (0.0.1):
|
||||
- flutter_inappwebview_ios/Core (0.0.1):
|
||||
- Flutter
|
||||
- OrderedSet (~> 5.0)
|
||||
- flutter_local_authentication (1.2.0):
|
||||
- Flutter
|
||||
- flutter_local_notifications (0.0.1):
|
||||
- Flutter
|
||||
- flutter_native_splash (0.0.1):
|
||||
- Flutter
|
||||
- flutter_secure_storage (6.0.0):
|
||||
- Flutter
|
||||
- flutter_sodium (0.0.1):
|
||||
- Flutter
|
||||
- fluttertoast (0.0.2):
|
||||
- Flutter
|
||||
- Toast
|
||||
- FMDB (2.7.5):
|
||||
- FMDB/standard (= 2.7.5)
|
||||
- FMDB/standard (2.7.5)
|
||||
- local_auth_darwin (0.0.1):
|
||||
- Flutter
|
||||
- local_auth_ios (0.0.1):
|
||||
- Flutter
|
||||
- move_to_background (0.0.1):
|
||||
|
@ -82,46 +83,65 @@ PODS:
|
|||
- qr_code_scanner (0.2.0):
|
||||
- Flutter
|
||||
- MTBBarcodeScanner
|
||||
- Reachability (3.2)
|
||||
- SDWebImage (5.17.0):
|
||||
- SDWebImage/Core (= 5.17.0)
|
||||
- SDWebImage/Core (5.17.0)
|
||||
- Sentry/HybridSDK (8.9.1):
|
||||
- SentryPrivate (= 8.9.1)
|
||||
- ReachabilitySwift (5.2.1)
|
||||
- SDWebImage (5.19.0):
|
||||
- SDWebImage/Core (= 5.19.0)
|
||||
- SDWebImage/Core (5.19.0)
|
||||
- Sentry/HybridSDK (8.21.0):
|
||||
- SentryPrivate (= 8.21.0)
|
||||
- sentry_flutter (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- Sentry/HybridSDK (= 8.9.1)
|
||||
- SentryPrivate (8.9.1)
|
||||
- Sentry/HybridSDK (= 8.21.0)
|
||||
- SentryPrivate (8.21.0)
|
||||
- share_plus (0.0.1):
|
||||
- Flutter
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- smart_auth (0.0.1):
|
||||
- Flutter
|
||||
- sodium_libs (2.2.1):
|
||||
- Flutter
|
||||
- sqflite (0.0.3):
|
||||
- Flutter
|
||||
- FMDB (>= 2.7.5)
|
||||
- SwiftyGif (5.4.4)
|
||||
- Toast (4.0.0)
|
||||
- uni_links (0.0.1):
|
||||
- FlutterMacOS
|
||||
- sqlite3 (3.45.1):
|
||||
- sqlite3/common (= 3.45.1)
|
||||
- sqlite3/common (3.45.1)
|
||||
- sqlite3/fts5 (3.45.1):
|
||||
- sqlite3/common
|
||||
- sqlite3/perf-threadsafe (3.45.1):
|
||||
- sqlite3/common
|
||||
- sqlite3/rtree (3.45.1):
|
||||
- sqlite3/common
|
||||
- sqlite3_flutter_libs (0.0.1):
|
||||
- Flutter
|
||||
- sqlite3 (~> 3.45.1)
|
||||
- sqlite3/fts5
|
||||
- sqlite3/perf-threadsafe
|
||||
- sqlite3/rtree
|
||||
- SwiftyGif (5.4.4)
|
||||
- Toast (4.1.0)
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
- connectivity (from `.symlinks/plugins/connectivity/ios`)
|
||||
- app_links (from `.symlinks/plugins/app_links/ios`)
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||
- file_saver (from `.symlinks/plugins/file_saver/ios`)
|
||||
- fk_user_agent (from `.symlinks/plugins/fk_user_agent/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_email_sender (from `.symlinks/plugins/flutter_email_sender/ios`)
|
||||
- flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`)
|
||||
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
|
||||
- flutter_local_authentication (from `.symlinks/plugins/flutter_local_authentication/ios`)
|
||||
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||
- flutter_sodium (from `.symlinks/plugins/flutter_sodium/ios`)
|
||||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
|
||||
- local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`)
|
||||
- move_to_background (from `.symlinks/plugins/move_to_background/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
|
@ -131,27 +151,31 @@ DEPENDENCIES:
|
|||
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||
- uni_links (from `.symlinks/plugins/uni_links/ios`)
|
||||
- smart_auth (from `.symlinks/plugins/smart_auth/ios`)
|
||||
- sodium_libs (from `.symlinks/plugins/sodium_libs/ios`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
|
||||
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/ios`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- DKImagePickerController
|
||||
- DKPhotoGallery
|
||||
- FMDB
|
||||
- MTBBarcodeScanner
|
||||
- OrderedSet
|
||||
- Reachability
|
||||
- ReachabilitySwift
|
||||
- SDWebImage
|
||||
- Sentry
|
||||
- SentryPrivate
|
||||
- sqlite3
|
||||
- SwiftyGif
|
||||
- Toast
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
connectivity:
|
||||
:path: ".symlinks/plugins/connectivity/ios"
|
||||
app_links:
|
||||
:path: ".symlinks/plugins/app_links/ios"
|
||||
connectivity_plus:
|
||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||
device_info_plus:
|
||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||
file_picker:
|
||||
|
@ -164,18 +188,20 @@ EXTERNAL SOURCES:
|
|||
:path: Flutter
|
||||
flutter_email_sender:
|
||||
:path: ".symlinks/plugins/flutter_email_sender/ios"
|
||||
flutter_inappwebview:
|
||||
:path: ".symlinks/plugins/flutter_inappwebview/ios"
|
||||
flutter_inappwebview_ios:
|
||||
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
|
||||
flutter_local_authentication:
|
||||
:path: ".symlinks/plugins/flutter_local_authentication/ios"
|
||||
flutter_local_notifications:
|
||||
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
||||
flutter_native_splash:
|
||||
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||
flutter_secure_storage:
|
||||
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
||||
flutter_sodium:
|
||||
:path: ".symlinks/plugins/flutter_sodium/ios"
|
||||
fluttertoast:
|
||||
:path: ".symlinks/plugins/fluttertoast/ios"
|
||||
local_auth_darwin:
|
||||
:path: ".symlinks/plugins/local_auth_darwin/darwin"
|
||||
local_auth_ios:
|
||||
:path: ".symlinks/plugins/local_auth_ios/ios"
|
||||
move_to_background:
|
||||
|
@ -194,50 +220,58 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/share_plus/ios"
|
||||
shared_preferences_foundation:
|
||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||
smart_auth:
|
||||
:path: ".symlinks/plugins/smart_auth/ios"
|
||||
sodium_libs:
|
||||
:path: ".symlinks/plugins/sodium_libs/ios"
|
||||
sqflite:
|
||||
:path: ".symlinks/plugins/sqflite/ios"
|
||||
uni_links:
|
||||
:path: ".symlinks/plugins/uni_links/ios"
|
||||
:path: ".symlinks/plugins/sqflite/darwin"
|
||||
sqlite3_flutter_libs:
|
||||
:path: ".symlinks/plugins/sqlite3_flutter_libs/ios"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467
|
||||
device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed
|
||||
app_links: e70ca16b4b0f88253b3b3660200d4a10b4ea9795
|
||||
connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d
|
||||
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
|
||||
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
|
||||
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
|
||||
file_picker: ce3938a0df3cc1ef404671531facef740d03f920
|
||||
file_picker: 15fd9539e4eb735dc54bae8c0534a7a9511a03de
|
||||
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
|
||||
fk_user_agent: 1f47ec39291e8372b1d692b50084b0d54103c545
|
||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
flutter_email_sender: 02d7443217d8c41483223627972bfdc09f74276b
|
||||
flutter_inappwebview: acd4fc0f012cefd09015000c241137d82f01ba62
|
||||
flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743
|
||||
flutter_inappwebview_ios: 97215cf7d4677db55df76782dbd2930c5e1c1ea0
|
||||
flutter_local_authentication: 1172a4dd88f6306dadce067454e2c4caf07977bb
|
||||
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
|
||||
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
|
||||
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
|
||||
flutter_sodium: c84426b4de738514b5b66cfdeb8a06634e72fe0b
|
||||
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
|
||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||
local_auth_ios: c6cf091ded637a88f24f86a8875d8b0f526e2605
|
||||
local_auth_darwin: c7e464000a6a89e952235699e32b329457608d98
|
||||
local_auth_ios: 5046a18c018dd973247a0564496c8898dbb5adf9
|
||||
move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d
|
||||
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
|
||||
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
|
||||
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
|
||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
|
||||
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
||||
privacy_screen: 1a131c052ceb3c3659934b003b0d397c2381a24e
|
||||
qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e
|
||||
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
|
||||
SDWebImage: 750adf017a315a280c60fde706ab1e552a3ae4e9
|
||||
Sentry: e3203780941722a1fcfee99e351de14244c7f806
|
||||
sentry_flutter: 8f0ffd53088e6a4d50c095852c5cad9e4405025c
|
||||
SentryPrivate: 5e3683390f66611fc7c6215e27645873adb55d13
|
||||
ReachabilitySwift: 5ae15e16814b5f9ef568963fb2c87aeb49158c66
|
||||
SDWebImage: 981fd7e860af070920f249fd092420006014c3eb
|
||||
Sentry: ebc12276bd17613a114ab359074096b6b3725203
|
||||
sentry_flutter: dff1df05dc39c83d04f9330b36360fc374574c5e
|
||||
SentryPrivate: d651efb234cf385ec9a1cdd3eff94b5e78a0e0fe
|
||||
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
|
||||
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
|
||||
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
||||
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
|
||||
smart_auth: 4bedbc118723912d0e45a07e8ab34039c19e04f2
|
||||
sodium_libs: 1faae17af662384acbd13e41867a0008cd2e2318
|
||||
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
||||
sqlite3: 73b7fc691fdc43277614250e04d183740cb15078
|
||||
sqlite3_flutter_libs: af0e8fe9bce48abddd1ffdbbf839db0302d72d80
|
||||
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
|
||||
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
||||
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
|
||||
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
|
||||
Toast: ec33c32b8688982cecc6348adeae667c1b9938da
|
||||
url_launcher_ios: 6116280ddcfe98ab8820085d8d76ae7449447586
|
||||
|
||||
PODFILE CHECKSUM: b4e3a7eabb03395b66e81fc061789f61526ee6bb
|
||||
|
||||
|
|
|
@ -159,7 +159,7 @@
|
|||
97C146E61CF9000F007C117D /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1430;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
97C146ED1CF9000F007C117D = {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1430"
|
||||
LastUpgradeVersion = "1510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import UIKit
|
||||
import Flutter
|
||||
import UIKit
|
||||
import app_links
|
||||
|
||||
@UIApplicationMain
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
|
@ -8,6 +9,15 @@ import Flutter
|
|||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
|
||||
super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
|
||||
if let url = AppLinks.shared.getLink(launchOptions: launchOptions) {
|
||||
AppLinks.shared.handleLink(url: url)
|
||||
}
|
||||
|
||||
return false
|
||||
|
||||
// return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,5 +78,9 @@
|
|||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -12,15 +12,18 @@ import 'package:ente_auth/locale.dart';
|
|||
import "package:ente_auth/onboarding/view/onboarding_page.dart";
|
||||
import 'package:ente_auth/services/update_service.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/services/window_listener_service.dart';
|
||||
import 'package:ente_auth/ui/home_page.dart';
|
||||
import 'package:ente_auth/ui/settings/app_update_dialog.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import "package:flutter/material.dart";
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:tray_manager/tray_manager.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
class App extends StatefulWidget {
|
||||
final Locale locale;
|
||||
const App({Key? key, this.locale = const Locale("en")}) : super(key: key);
|
||||
const App({super.key, this.locale = const Locale("en")});
|
||||
|
||||
static void setLocale(BuildContext context, Locale newLocale) {
|
||||
_AppState state = context.findAncestorStateOfType<_AppState>()!;
|
||||
|
@ -31,7 +34,7 @@ class App extends StatefulWidget {
|
|||
State<App> createState() => _AppState();
|
||||
}
|
||||
|
||||
class _AppState extends State<App> {
|
||||
class _AppState extends State<App> with WindowListener, TrayListener {
|
||||
late StreamSubscription<SignedOutEvent> _signedOutEvent;
|
||||
late StreamSubscription<SignedInEvent> _signedInEvent;
|
||||
Locale? locale;
|
||||
|
@ -41,8 +44,19 @@ class _AppState extends State<App> {
|
|||
});
|
||||
}
|
||||
|
||||
Future<void> initWindowManager() async {
|
||||
windowManager.addListener(this);
|
||||
}
|
||||
|
||||
Future<void> initTrayManager() async {
|
||||
trayManager.addListener(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
initWindowManager();
|
||||
initTrayManager();
|
||||
|
||||
_signedOutEvent = Bus.instance.on<SignedOutEvent>().listen((event) {
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
|
@ -76,6 +90,10 @@ class _AppState extends State<App> {
|
|||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
|
||||
windowManager.removeListener(this);
|
||||
trayManager.removeListener(this);
|
||||
|
||||
_signedOutEvent.cancel();
|
||||
_signedInEvent.cancel();
|
||||
}
|
||||
|
@ -134,4 +152,45 @@ class _AppState extends State<App> {
|
|||
: const OnboardingPage(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowResize() {
|
||||
WindowListenerService.instance.onWindowResize().ignore();
|
||||
}
|
||||
|
||||
@override
|
||||
void onTrayIconMouseDown() {
|
||||
if (Platform.isWindows) {
|
||||
windowManager.show();
|
||||
} else {
|
||||
trayManager.popUpContextMenu();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onTrayIconRightMouseDown() {
|
||||
if (Platform.isWindows) {
|
||||
trayManager.popUpContextMenu();
|
||||
} else {
|
||||
windowManager.show();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onTrayIconRightMouseUp() {}
|
||||
|
||||
@override
|
||||
void onTrayMenuItemClick(MenuItem menuItem) {
|
||||
switch (menuItem.key) {
|
||||
case 'hide_window':
|
||||
windowManager.hide();
|
||||
break;
|
||||
case 'show_window':
|
||||
windowManager.show();
|
||||
break;
|
||||
case 'exit_app':
|
||||
windowManager.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
class AppRoute {
|
||||
static const String enterSecretKeyPage = "enterSecretKeyPage";
|
||||
static const String settings = "settings";
|
||||
}
|
|
@ -1,163 +0,0 @@
|
|||
import "package:flutter/material.dart";
|
||||
|
||||
final lightTheme = ThemeData(
|
||||
fontFamily: "Inter",
|
||||
brightness: Brightness.light,
|
||||
scaffoldBackgroundColor: Colors.white,
|
||||
appBarTheme: const AppBarTheme().copyWith(
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.black,
|
||||
iconTheme: const IconThemeData(color: Colors.black),
|
||||
elevation: 0,
|
||||
),
|
||||
colorScheme: const ColorScheme.light(
|
||||
primary: Colors.black,
|
||||
),
|
||||
textTheme: _buildTextTheme(Colors.black),
|
||||
outlinedButtonTheme: buildOutlinedButtonThemeData(
|
||||
bgDisabled: Colors.grey.shade500,
|
||||
bgEnabled: Colors.black,
|
||||
fgDisabled: Colors.white,
|
||||
fgEnabled: Colors.white,
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
fillColor: null,
|
||||
filled: true,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 14,
|
||||
),
|
||||
border: UnderlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final darkTheme = ThemeData(
|
||||
fontFamily: "Inter",
|
||||
brightness: Brightness.dark,
|
||||
scaffoldBackgroundColor: Colors.black,
|
||||
appBarTheme: const AppBarTheme(color: Colors.orange),
|
||||
textTheme: _buildTextTheme(Colors.white),
|
||||
outlinedButtonTheme: buildOutlinedButtonThemeData(
|
||||
bgDisabled: Colors.grey.shade500,
|
||||
bgEnabled: Colors.white,
|
||||
fgDisabled: Colors.white,
|
||||
fgEnabled: Colors.black,
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
fillColor: null,
|
||||
filled: true,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 14,
|
||||
),
|
||||
border: UnderlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
), colorScheme: const ColorScheme.dark(primary: Colors.white).copyWith(background: Colors.black),
|
||||
);
|
||||
|
||||
TextTheme _buildTextTheme(Color textColor) {
|
||||
return const TextTheme().copyWith(
|
||||
headlineMedium: TextStyle(
|
||||
color: textColor,
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontFamily: "Inter",
|
||||
),
|
||||
headlineSmall: TextStyle(
|
||||
color: textColor,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFamily: "Inter",
|
||||
),
|
||||
// AG: Body
|
||||
titleLarge: TextStyle(
|
||||
color: textColor,
|
||||
fontSize: 18,
|
||||
fontFamily: "Inter",
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
// Use labels for buttons or notifications
|
||||
labelMedium: TextStyle(
|
||||
color: textColor,
|
||||
fontFamily: "Inter",
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w700,
|
||||
height: 28,
|
||||
),
|
||||
|
||||
titleMedium: TextStyle(
|
||||
color: textColor,
|
||||
fontFamily: "Inter",
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
titleSmall: TextStyle(
|
||||
color: textColor,
|
||||
fontFamily: "Inter",
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
bodyLarge: TextStyle(
|
||||
fontFamily: "Inter",
|
||||
color: textColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
bodyMedium: TextStyle(
|
||||
fontFamily: "Inter",
|
||||
color: textColor,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
bodySmall: TextStyle(
|
||||
color: textColor.withOpacity(0.6),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
OutlinedButtonThemeData buildOutlinedButtonThemeData({
|
||||
required Color bgDisabled,
|
||||
required Color bgEnabled,
|
||||
required Color fgDisabled,
|
||||
required Color fgEnabled,
|
||||
}) {
|
||||
return OutlinedButtonThemeData(
|
||||
style: OutlinedButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 50),
|
||||
textStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
fontFamily: "Inter",
|
||||
fontSize: 18,
|
||||
),
|
||||
).copyWith(
|
||||
backgroundColor: MaterialStateProperty.resolveWith<Color>(
|
||||
(Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return bgDisabled;
|
||||
}
|
||||
return bgEnabled;
|
||||
},
|
||||
),
|
||||
foregroundColor: MaterialStateProperty.resolveWith<Color>(
|
||||
(Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return fgDisabled;
|
||||
}
|
||||
return fgEnabled;
|
||||
},
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
),
|
||||
);
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
// ignore_for_file: deprecated_member_use
|
||||
|
||||
import "dart:async";
|
||||
import "dart:developer";
|
||||
|
||||
|
|
|
@ -13,12 +13,12 @@ import 'package:ente_auth/models/key_attributes.dart';
|
|||
import 'package:ente_auth/models/key_gen_result.dart';
|
||||
import 'package:ente_auth/models/private_key_attributes.dart';
|
||||
import 'package:ente_auth/store/authenticator_db.dart';
|
||||
import 'package:ente_auth/utils/crypto_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class Configuration {
|
||||
|
@ -72,9 +72,10 @@ class Configuration {
|
|||
|
||||
Future<void> init() async {
|
||||
_preferences = await SharedPreferences.getInstance();
|
||||
sqfliteFfiInit();
|
||||
_secureStorage = const FlutterSecureStorage();
|
||||
_documentsDirectory = (await getApplicationDocumentsDirectory()).path;
|
||||
_tempDirectory = _documentsDirectory + "/temp/";
|
||||
_tempDirectory = "$_documentsDirectory/temp/";
|
||||
final tempDirectory = io.Directory(_tempDirectory);
|
||||
try {
|
||||
final currentTime = DateTime.now().microsecondsSinceEpoch;
|
||||
|
@ -162,7 +163,7 @@ class Configuration {
|
|||
// decrypt the master key
|
||||
final kekSalt = CryptoUtil.getSaltToDeriveKey();
|
||||
final derivedKeyResult = await CryptoUtil.deriveSensitiveKey(
|
||||
utf8.encode(password) as Uint8List,
|
||||
utf8.encode(password),
|
||||
kekSalt,
|
||||
);
|
||||
final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key);
|
||||
|
@ -172,28 +173,28 @@ class Configuration {
|
|||
CryptoUtil.encryptSync(masterKey, derivedKeyResult.key);
|
||||
|
||||
// Generate a public-private keypair and encrypt the latter
|
||||
final keyPair = await CryptoUtil.generateKeyPair();
|
||||
final keyPair = CryptoUtil.generateKeyPair();
|
||||
final encryptedSecretKeyData =
|
||||
CryptoUtil.encryptSync(keyPair.sk, masterKey);
|
||||
CryptoUtil.encryptSync(keyPair.secretKey.extractBytes(), masterKey);
|
||||
|
||||
final attributes = KeyAttributes(
|
||||
Sodium.bin2base64(kekSalt),
|
||||
Sodium.bin2base64(encryptedKeyData.encryptedData!),
|
||||
Sodium.bin2base64(encryptedKeyData.nonce!),
|
||||
Sodium.bin2base64(keyPair.pk),
|
||||
Sodium.bin2base64(encryptedSecretKeyData.encryptedData!),
|
||||
Sodium.bin2base64(encryptedSecretKeyData.nonce!),
|
||||
CryptoUtil.bin2base64(kekSalt),
|
||||
CryptoUtil.bin2base64(encryptedKeyData.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptedKeyData.nonce!),
|
||||
CryptoUtil.bin2base64(keyPair.publicKey),
|
||||
CryptoUtil.bin2base64(encryptedSecretKeyData.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptedSecretKeyData.nonce!),
|
||||
derivedKeyResult.memLimit,
|
||||
derivedKeyResult.opsLimit,
|
||||
Sodium.bin2base64(encryptedMasterKey.encryptedData!),
|
||||
Sodium.bin2base64(encryptedMasterKey.nonce!),
|
||||
Sodium.bin2base64(encryptedRecoveryKey.encryptedData!),
|
||||
Sodium.bin2base64(encryptedRecoveryKey.nonce!),
|
||||
CryptoUtil.bin2base64(encryptedMasterKey.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptedMasterKey.nonce!),
|
||||
CryptoUtil.bin2base64(encryptedRecoveryKey.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptedRecoveryKey.nonce!),
|
||||
);
|
||||
final privateAttributes = PrivateKeyAttributes(
|
||||
Sodium.bin2base64(masterKey),
|
||||
Sodium.bin2hex(recoveryKey),
|
||||
Sodium.bin2base64(keyPair.sk),
|
||||
CryptoUtil.bin2base64(masterKey),
|
||||
CryptoUtil.bin2hex(recoveryKey),
|
||||
CryptoUtil.bin2base64(keyPair.secretKey.extractBytes()),
|
||||
);
|
||||
return KeyGenResult(attributes, privateAttributes, loginKey);
|
||||
}
|
||||
|
@ -208,7 +209,7 @@ class Configuration {
|
|||
// decrypt the master key
|
||||
final kekSalt = CryptoUtil.getSaltToDeriveKey();
|
||||
final derivedKeyResult = await CryptoUtil.deriveSensitiveKey(
|
||||
utf8.encode(password) as Uint8List,
|
||||
utf8.encode(password),
|
||||
kekSalt,
|
||||
);
|
||||
final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key);
|
||||
|
@ -220,9 +221,9 @@ class Configuration {
|
|||
final existingAttributes = getKeyAttributes();
|
||||
|
||||
final updatedAttributes = existingAttributes!.copyWith(
|
||||
kekSalt: Sodium.bin2base64(kekSalt),
|
||||
encryptedKey: Sodium.bin2base64(encryptedKeyData.encryptedData!),
|
||||
keyDecryptionNonce: Sodium.bin2base64(encryptedKeyData.nonce!),
|
||||
kekSalt: CryptoUtil.bin2base64(kekSalt),
|
||||
encryptedKey: CryptoUtil.bin2base64(encryptedKeyData.encryptedData!),
|
||||
keyDecryptionNonce: CryptoUtil.bin2base64(encryptedKeyData.nonce!),
|
||||
memLimit: derivedKeyResult.memLimit,
|
||||
opsLimit: derivedKeyResult.opsLimit,
|
||||
);
|
||||
|
@ -240,8 +241,8 @@ class Configuration {
|
|||
}) async {
|
||||
_logger.info('Start decryptAndSaveSecrets');
|
||||
keyEncryptionKey ??= await CryptoUtil.deriveKey(
|
||||
utf8.encode(password) as Uint8List,
|
||||
Sodium.base642bin(attributes.kekSalt),
|
||||
utf8.encode(password),
|
||||
CryptoUtil.base642bin(attributes.kekSalt),
|
||||
attributes.memLimit,
|
||||
attributes.opsLimit,
|
||||
);
|
||||
|
@ -250,31 +251,31 @@ class Configuration {
|
|||
Uint8List key;
|
||||
try {
|
||||
key = CryptoUtil.decryptSync(
|
||||
Sodium.base642bin(attributes.encryptedKey),
|
||||
CryptoUtil.base642bin(attributes.encryptedKey),
|
||||
keyEncryptionKey,
|
||||
Sodium.base642bin(attributes.keyDecryptionNonce),
|
||||
CryptoUtil.base642bin(attributes.keyDecryptionNonce),
|
||||
);
|
||||
} catch (e) {
|
||||
_logger.severe('master-key failed, incorrect password?', e);
|
||||
throw Exception("Incorrect password");
|
||||
}
|
||||
_logger.info("master-key done");
|
||||
await setKey(Sodium.bin2base64(key));
|
||||
await setKey(CryptoUtil.bin2base64(key));
|
||||
final secretKey = CryptoUtil.decryptSync(
|
||||
Sodium.base642bin(attributes.encryptedSecretKey),
|
||||
CryptoUtil.base642bin(attributes.encryptedSecretKey),
|
||||
key,
|
||||
Sodium.base642bin(attributes.secretKeyDecryptionNonce),
|
||||
CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce),
|
||||
);
|
||||
_logger.info("secret-key done");
|
||||
await setSecretKey(Sodium.bin2base64(secretKey));
|
||||
await setSecretKey(CryptoUtil.bin2base64(secretKey));
|
||||
final token = CryptoUtil.openSealSync(
|
||||
Sodium.base642bin(getEncryptedToken()!),
|
||||
Sodium.base642bin(attributes.publicKey),
|
||||
CryptoUtil.base642bin(getEncryptedToken()!),
|
||||
CryptoUtil.base642bin(attributes.publicKey),
|
||||
secretKey,
|
||||
);
|
||||
_logger.info('appToken done');
|
||||
await setToken(
|
||||
Sodium.bin2base64(token, variant: Sodium.base64VariantUrlsafe),
|
||||
CryptoUtil.bin2base64(token, urlSafe: true),
|
||||
);
|
||||
return keyEncryptionKey;
|
||||
}
|
||||
|
@ -293,28 +294,28 @@ class Configuration {
|
|||
Uint8List masterKey;
|
||||
try {
|
||||
masterKey = await CryptoUtil.decrypt(
|
||||
Sodium.base642bin(attributes!.masterKeyEncryptedWithRecoveryKey),
|
||||
Sodium.hex2bin(recoveryKey),
|
||||
Sodium.base642bin(attributes.masterKeyDecryptionNonce),
|
||||
CryptoUtil.base642bin(attributes!.masterKeyEncryptedWithRecoveryKey),
|
||||
CryptoUtil.hex2bin(recoveryKey),
|
||||
CryptoUtil.base642bin(attributes.masterKeyDecryptionNonce),
|
||||
);
|
||||
} catch (e) {
|
||||
_logger.severe(e);
|
||||
rethrow;
|
||||
}
|
||||
await setKey(Sodium.bin2base64(masterKey));
|
||||
await setKey(CryptoUtil.bin2base64(masterKey));
|
||||
final secretKey = CryptoUtil.decryptSync(
|
||||
Sodium.base642bin(attributes.encryptedSecretKey),
|
||||
CryptoUtil.base642bin(attributes.encryptedSecretKey),
|
||||
masterKey,
|
||||
Sodium.base642bin(attributes.secretKeyDecryptionNonce),
|
||||
CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce),
|
||||
);
|
||||
await setSecretKey(Sodium.bin2base64(secretKey));
|
||||
await setSecretKey(CryptoUtil.bin2base64(secretKey));
|
||||
final token = CryptoUtil.openSealSync(
|
||||
Sodium.base642bin(getEncryptedToken()!),
|
||||
Sodium.base642bin(attributes.publicKey),
|
||||
CryptoUtil.base642bin(getEncryptedToken()!),
|
||||
CryptoUtil.base642bin(attributes.publicKey),
|
||||
secretKey,
|
||||
);
|
||||
await setToken(
|
||||
Sodium.bin2base64(token, variant: Sodium.base64VariantUrlsafe),
|
||||
CryptoUtil.bin2base64(token, urlSafe: true),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -407,27 +408,31 @@ class Configuration {
|
|||
}
|
||||
|
||||
Uint8List? getKey() {
|
||||
return _key == null ? null : Sodium.base642bin(_key!);
|
||||
return _key == null ? null : CryptoUtil.base642bin(_key!);
|
||||
}
|
||||
|
||||
Uint8List? getSecretKey() {
|
||||
return _secretKey == null ? null : Sodium.base642bin(_secretKey!);
|
||||
return _secretKey == null ? null : CryptoUtil.base642bin(_secretKey!);
|
||||
}
|
||||
|
||||
Uint8List? getAuthSecretKey() {
|
||||
return _authSecretKey == null ? null : Sodium.base642bin(_authSecretKey!);
|
||||
return _authSecretKey == null
|
||||
? null
|
||||
: CryptoUtil.base642bin(_authSecretKey!);
|
||||
}
|
||||
|
||||
Uint8List? getOfflineSecretKey() {
|
||||
return _offlineAuthKey == null ? null : Sodium.base642bin(_offlineAuthKey!);
|
||||
return _offlineAuthKey == null
|
||||
? null
|
||||
: CryptoUtil.base642bin(_offlineAuthKey!);
|
||||
}
|
||||
|
||||
Uint8List getRecoveryKey() {
|
||||
final keyAttributes = getKeyAttributes()!;
|
||||
return CryptoUtil.decryptSync(
|
||||
Sodium.base642bin(keyAttributes.recoveryKeyEncryptedWithMasterKey),
|
||||
CryptoUtil.base642bin(keyAttributes.recoveryKeyEncryptedWithMasterKey),
|
||||
getKey()!,
|
||||
Sodium.base642bin(keyAttributes.recoveryKeyDecryptionNonce),
|
||||
CryptoUtil.base642bin(keyAttributes.recoveryKeyDecryptionNonce),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -454,7 +459,7 @@ class Configuration {
|
|||
iOptions: _secureStorageOptionsIOS,
|
||||
);
|
||||
} else {
|
||||
_offlineAuthKey = Sodium.bin2base64(CryptoUtil.generateKey());
|
||||
_offlineAuthKey = CryptoUtil.bin2base64(CryptoUtil.generateKey());
|
||||
await _secureStorage.write(
|
||||
key: offlineAuthSecretKey,
|
||||
value: _offlineAuthKey,
|
||||
|
|
|
@ -7,7 +7,8 @@ const String sentryDSN =
|
|||
"https://ed4ddd6309b847ba8849935e26e9b648@sentry.ente.io/9";
|
||||
const String sentryTunnel = "https://sentry-reporter.ente.io";
|
||||
const String roadmapURL = "https://roadmap.ente.io";
|
||||
const String githubDiscussionsUrl = "https://github.com/ente-io/ente/discussions";
|
||||
const String githubIssuesUrl =
|
||||
"https://github.com/ente-io/ente/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc";
|
||||
const int microSecondsInDay = 86400000000;
|
||||
const int android11SDKINT = 30;
|
||||
const int galleryLoadStartTime = -8000000000000000; // Wednesday, March 6, 1748
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
class InvalidFileError extends ArgumentError {
|
||||
InvalidFileError(String message) : super(message);
|
||||
InvalidFileError(String super.message);
|
||||
}
|
||||
|
||||
class InvalidFileUploadState extends AssertionError {
|
||||
InvalidFileUploadState(String message) : super(message);
|
||||
InvalidFileUploadState(String super.message);
|
||||
}
|
||||
|
||||
class SubscriptionAlreadyClaimedError extends Error {}
|
||||
|
@ -30,19 +30,15 @@ class UnauthorizedError extends Error {}
|
|||
class RequestCancelledError extends Error {}
|
||||
|
||||
class InvalidSyncStatusError extends AssertionError {
|
||||
InvalidSyncStatusError(String message) : super(message);
|
||||
InvalidSyncStatusError(String super.message);
|
||||
}
|
||||
|
||||
class UnauthorizedEditError extends AssertionError {}
|
||||
|
||||
class InvalidStateError extends AssertionError {
|
||||
InvalidStateError(String message) : super(message);
|
||||
InvalidStateError(String super.message);
|
||||
}
|
||||
|
||||
class KeyDerivationError extends Error {}
|
||||
|
||||
class LoginKeyDerivationError extends Error {}
|
||||
|
||||
class SrpSetupNotCompleteError extends Error {}
|
||||
|
||||
class AuthenticatorKeyNotFound extends Error {}
|
||||
|
|
|
@ -235,14 +235,14 @@ class SuperLogging {
|
|||
extraLines = null;
|
||||
}
|
||||
|
||||
final str = (config.prefix) + " " + rec.toPrettyString(extraLines);
|
||||
final str = "${config.prefix} ${rec.toPrettyString(extraLines)}";
|
||||
|
||||
// write to stdout
|
||||
printLog(str);
|
||||
|
||||
// push to log queue
|
||||
if (fileIsEnabled) {
|
||||
fileQueueEntries.add(str + '\n');
|
||||
fileQueueEntries.add('$str\n');
|
||||
if (fileQueueEntries.length == 1) {
|
||||
flushQueue();
|
||||
}
|
||||
|
@ -275,7 +275,7 @@ class SuperLogging {
|
|||
static var logChunkSize = 800;
|
||||
|
||||
static void printLog(String text) {
|
||||
text.chunked(logChunkSize).forEach(print);
|
||||
text.chunked(logChunkSize).forEach(debugPrint);
|
||||
}
|
||||
|
||||
/// A queue to be consumed by [setupSentry].
|
||||
|
@ -354,7 +354,7 @@ class SuperLogging {
|
|||
final date = config.dateFmt!.parse(basename(file.path));
|
||||
dates[file as File] = date;
|
||||
files.add(file);
|
||||
} on FormatException {}
|
||||
} on Exception catch (_) {}
|
||||
}
|
||||
final nowTime = DateTime.now();
|
||||
|
||||
|
@ -374,7 +374,7 @@ class SuperLogging {
|
|||
"deleting log file ${file.path}",
|
||||
);
|
||||
await file.delete();
|
||||
} catch (ignore) {}
|
||||
} on Exception catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ class TunneledTransport implements Transport {
|
|||
_options.logger(
|
||||
SentryLevel.error,
|
||||
'API returned an error, statusCode = ${response.statusCode}, '
|
||||
'body = ${response.body}',
|
||||
'body = ${response.body}',
|
||||
);
|
||||
}
|
||||
return const SentryId.empty();
|
||||
|
@ -65,8 +65,8 @@ class TunneledTransport implements Transport {
|
|||
}
|
||||
|
||||
Future<StreamedRequest> _createStreamedRequest(
|
||||
SentryEnvelope envelope,
|
||||
) async {
|
||||
SentryEnvelope envelope,
|
||||
) async {
|
||||
final streamedRequest = StreamedRequest('POST', _tunnel);
|
||||
envelope
|
||||
.envelopeStream(_options)
|
||||
|
@ -91,10 +91,10 @@ class _CredentialBuilder {
|
|||
_clock = clock;
|
||||
|
||||
factory _CredentialBuilder(
|
||||
Dsn? dsn,
|
||||
String sdkIdentifier,
|
||||
ClockProvider clock,
|
||||
) {
|
||||
Dsn? dsn,
|
||||
String sdkIdentifier,
|
||||
ClockProvider clock,
|
||||
) {
|
||||
final authHeader = _buildAuthHeader(
|
||||
publicKey: dsn?.publicKey,
|
||||
secretKey: dsn?.secretKey,
|
||||
|
|
|
@ -4,9 +4,10 @@ import 'package:dio/dio.dart';
|
|||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/events/endpoint_updated_event.dart';
|
||||
import 'package:ente_auth/utils/package_info_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:fk_user_agent/fk_user_agent.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
int kConnectTimeout = 15000;
|
||||
|
@ -16,34 +17,41 @@ class Network {
|
|||
late Dio _enteDio;
|
||||
|
||||
Future<void> init() async {
|
||||
await FkUserAgent.init();
|
||||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
if (PlatformUtil.isMobile()) await FkUserAgent.init();
|
||||
final packageInfo = await PackageInfoUtil().getPackageInfo();
|
||||
final version = PackageInfoUtil().getVersion(packageInfo);
|
||||
final packageName = PackageInfoUtil().getPackageName(packageInfo);
|
||||
final endpoint = Configuration.instance.getHttpEndpoint();
|
||||
|
||||
|
||||
_dio = Dio(
|
||||
BaseOptions(
|
||||
connectTimeout: kConnectTimeout,
|
||||
connectTimeout: Duration(milliseconds: kConnectTimeout),
|
||||
headers: {
|
||||
HttpHeaders.userAgentHeader: FkUserAgent.userAgent,
|
||||
'X-Client-Version': packageInfo.version,
|
||||
'X-Client-Package': packageInfo.packageName,
|
||||
HttpHeaders.userAgentHeader: PlatformUtil.isMobile()
|
||||
? FkUserAgent.userAgent
|
||||
: Platform.operatingSystem,
|
||||
'X-Client-Version': version,
|
||||
'X-Client-Package': packageName,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
_enteDio = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: endpoint,
|
||||
connectTimeout: kConnectTimeout,
|
||||
connectTimeout: Duration(milliseconds: kConnectTimeout),
|
||||
headers: {
|
||||
HttpHeaders.userAgentHeader: FkUserAgent.userAgent,
|
||||
'X-Client-Version': packageInfo.version,
|
||||
'X-Client-Package': packageInfo.packageName,
|
||||
if (PlatformUtil.isMobile())
|
||||
HttpHeaders.userAgentHeader: FkUserAgent.userAgent
|
||||
else
|
||||
HttpHeaders.userAgentHeader: Platform.operatingSystem,
|
||||
'X-Client-Version': version,
|
||||
'X-Client-Package': packageName,
|
||||
},
|
||||
),
|
||||
);
|
||||
_setupInterceptors(endpoint);
|
||||
|
||||
|
||||
Bus.instance.on<EndpointUpdatedEvent>().listen((event) {
|
||||
final endpoint = Configuration.instance.getHttpEndpoint();
|
||||
_enteDio.options.baseUrl = endpoint;
|
||||
|
|
|
@ -5,12 +5,16 @@ import 'package:flutter/material.dart';
|
|||
final lightThemeData = ThemeData(
|
||||
fontFamily: 'Inter',
|
||||
brightness: Brightness.light,
|
||||
dividerTheme: const DividerThemeData(
|
||||
color: Colors.black12,
|
||||
),
|
||||
hintColor: const Color.fromRGBO(158, 158, 158, 1),
|
||||
primaryColor: const Color.fromRGBO(255, 110, 64, 1),
|
||||
primaryColorLight: const Color.fromRGBO(0, 0, 0, 0.541),
|
||||
iconTheme: const IconThemeData(color: Colors.black),
|
||||
primaryIconTheme:
|
||||
const IconThemeData(color: Colors.red, opacity: 1.0, size: 50.0),
|
||||
buttonTheme: const ButtonThemeData(),
|
||||
outlinedButtonTheme: buildOutlinedButtonThemeData(
|
||||
bgDisabled: const Color.fromRGBO(158, 158, 158, 1),
|
||||
bgEnabled: const Color.fromRGBO(0, 0, 0, 1),
|
||||
|
@ -72,24 +76,42 @@ final lightThemeData = ThemeData(
|
|||
? const Color.fromRGBO(255, 255, 255, 1)
|
||||
: const Color.fromRGBO(0, 0, 0, 1);
|
||||
}),
|
||||
), radioTheme: RadioThemeData(
|
||||
fillColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) { return null; }
|
||||
if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
|
||||
return null;
|
||||
}),
|
||||
), switchTheme: SwitchThemeData(
|
||||
thumbColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) { return null; }
|
||||
if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
|
||||
return null;
|
||||
}),
|
||||
trackColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) { return null; }
|
||||
if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
|
||||
return null;
|
||||
}),
|
||||
), colorScheme: const ColorScheme.light(
|
||||
),
|
||||
radioTheme: RadioThemeData(
|
||||
fillColor:
|
||||
MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return null;
|
||||
}
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return const Color.fromRGBO(102, 187, 106, 1);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
),
|
||||
switchTheme: SwitchThemeData(
|
||||
thumbColor:
|
||||
MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return null;
|
||||
}
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return const Color.fromRGBO(102, 187, 106, 1);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
trackColor:
|
||||
MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return null;
|
||||
}
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return const Color.fromRGBO(102, 187, 106, 1);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
),
|
||||
colorScheme: const ColorScheme.light(
|
||||
primary: Colors.black,
|
||||
secondary: Color.fromARGB(255, 163, 163, 163),
|
||||
).copyWith(background: const Color.fromRGBO(255, 255, 255, 1)),
|
||||
|
@ -98,6 +120,9 @@ final lightThemeData = ThemeData(
|
|||
final darkThemeData = ThemeData(
|
||||
fontFamily: 'Inter',
|
||||
brightness: Brightness.dark,
|
||||
dividerTheme: const DividerThemeData(
|
||||
color: Colors.white12,
|
||||
),
|
||||
primaryColorLight: const Color.fromRGBO(255, 255, 255, 0.702),
|
||||
iconTheme: const IconThemeData(color: Colors.white),
|
||||
primaryIconTheme:
|
||||
|
@ -105,6 +130,7 @@ final darkThemeData = ThemeData(
|
|||
hintColor: const Color.fromRGBO(158, 158, 158, 1),
|
||||
buttonTheme: const ButtonThemeData().copyWith(
|
||||
buttonColor: const Color.fromRGBO(45, 194, 98, 1.0),
|
||||
height: 56,
|
||||
),
|
||||
textTheme: _buildTextTheme(const Color.fromRGBO(255, 255, 255, 1)),
|
||||
outlinedButtonTheme: buildOutlinedButtonThemeData(
|
||||
|
@ -164,24 +190,43 @@ final darkThemeData = ThemeData(
|
|||
return const Color.fromRGBO(158, 158, 158, 1);
|
||||
}
|
||||
}),
|
||||
), radioTheme: RadioThemeData(
|
||||
fillColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) { return null; }
|
||||
if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
|
||||
return null;
|
||||
}),
|
||||
), switchTheme: SwitchThemeData(
|
||||
thumbColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) { return null; }
|
||||
if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
|
||||
return null;
|
||||
}),
|
||||
trackColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) { return null; }
|
||||
if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
|
||||
return null;
|
||||
}),
|
||||
), colorScheme: const ColorScheme.dark(primary: Colors.white).copyWith(background: const Color.fromRGBO(0, 0, 0, 1)),
|
||||
),
|
||||
radioTheme: RadioThemeData(
|
||||
fillColor:
|
||||
MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return null;
|
||||
}
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return const Color.fromRGBO(102, 187, 106, 1);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
),
|
||||
switchTheme: SwitchThemeData(
|
||||
thumbColor:
|
||||
MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return null;
|
||||
}
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return const Color.fromRGBO(102, 187, 106, 1);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
trackColor:
|
||||
MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return null;
|
||||
}
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return const Color.fromRGBO(102, 187, 106, 1);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
),
|
||||
colorScheme: const ColorScheme.dark(primary: Colors.white)
|
||||
.copyWith(background: const Color.fromRGBO(0, 0, 0, 1)),
|
||||
);
|
||||
|
||||
TextTheme _buildTextTheme(Color textColor) {
|
||||
|
@ -400,6 +445,7 @@ OutlinedButtonThemeData buildOutlinedButtonThemeData({
|
|||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
fixedSize: const Size.fromHeight(56),
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.fromLTRB(50, 16, 50, 16),
|
||||
textStyle: const TextStyle(
|
||||
|
@ -436,7 +482,9 @@ ElevatedButtonThemeData buildElevatedButtonThemeData({
|
|||
}) {
|
||||
return ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
foregroundColor: onPrimary, backgroundColor: primary, elevation: elevation,
|
||||
foregroundColor: onPrimary,
|
||||
backgroundColor: primary,
|
||||
elevation: elevation,
|
||||
alignment: Alignment.center,
|
||||
textStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
|
|
|
@ -25,7 +25,7 @@ class AuthenticatorGateway {
|
|||
try {
|
||||
final response = await _enteDio.get("/authenticator/key");
|
||||
return AuthKey.fromMap(response.data);
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
if (e.response != null && (e.response!.statusCode ?? 0) == 404) {
|
||||
throw AuthenticatorKeyNotFound();
|
||||
} else {
|
||||
|
@ -90,7 +90,7 @@ class AuthenticatorGateway {
|
|||
}
|
||||
return authEntities;
|
||||
} catch (e) {
|
||||
if (e is DioError && e.response?.statusCode == 401) {
|
||||
if (e is DioException && e.response?.statusCode == 401) {
|
||||
throw UnauthorizedError();
|
||||
} else {
|
||||
rethrow;
|
||||
|
|
|
@ -145,6 +145,7 @@
|
|||
"lostDeviceTitle": "Gerät verloren?",
|
||||
"twoFactorAuthTitle": "Zwei-Faktor-Authentifizierung",
|
||||
"passkeyAuthTitle": "Passkey Authentifizierung",
|
||||
"verifyPasskey": "Passkey verifizieren",
|
||||
"recoverAccount": "Konto wiederherstellen",
|
||||
"enterRecoveryKeyHint": "Geben Sie Ihren Wiederherstellungsschlüssel ein",
|
||||
"recover": "Wiederherstellen",
|
||||
|
@ -407,7 +408,7 @@
|
|||
"hearUsWhereTitle": "Wie hast du von Ente erfahren? (optional)",
|
||||
"hearUsExplanation": "Wir tracken keine App-Installationen. Es würde uns jedoch helfen, wenn du uns mitteilst, wie du von uns erfahren hast!",
|
||||
"waitingForBrowserRequest": "Warten auf Browseranfrage...",
|
||||
"launchPasskeyUrlAgain": "Passwort-URL erneut starten",
|
||||
"waitingForVerification": "Warte auf Bestätigung...",
|
||||
"passkey": "Passkey",
|
||||
"developerSettingsWarning": "Sind Sie sicher, dass Sie die Entwicklereinstellungen ändern möchten?",
|
||||
"developerSettings": "Entwicklereinstellungen",
|
||||
|
|
|
@ -199,6 +199,10 @@
|
|||
"recoveryKeySaveDescription": "We don't store this key, please save this 24 word key in a safe place.",
|
||||
"doThisLater": "Do this later",
|
||||
"saveKey": "Save key",
|
||||
"save": "Save",
|
||||
"send": "Send",
|
||||
"saveOrSendDescription": "Do you want to save this to your storage (Downloads folder by default) or send it to other apps?",
|
||||
"saveOnlyDescription": "Do you want to save this to your storage (Downloads folder by default)?",
|
||||
"back": "Back",
|
||||
"createAccount": "Create account",
|
||||
"passwordStrength": "Password strength: {passwordStrengthValue}",
|
||||
|
@ -407,6 +411,7 @@
|
|||
"doNotSignOut": "Do not sign out",
|
||||
"hearUsWhereTitle": "How did you hear about Ente? (optional)",
|
||||
"hearUsExplanation": "We don't track app installs. It'd help if you told us where you found us!",
|
||||
"recoveryKeySaved": "Recovery key saved in Downloads folder!",
|
||||
"waitingForBrowserRequest": "Waiting for browser request...",
|
||||
"waitingForVerification": "Waiting for verification...",
|
||||
"passkey": "Passkey",
|
||||
|
|
|
@ -145,6 +145,7 @@
|
|||
"lostDeviceTitle": "デバイスを紛失しましたか?",
|
||||
"twoFactorAuthTitle": "2 要素認証",
|
||||
"passkeyAuthTitle": "パスキー認証",
|
||||
"verifyPasskey": "パスキーの認証",
|
||||
"recoverAccount": "アカウントを回復",
|
||||
"enterRecoveryKeyHint": "回復キーを入力",
|
||||
"recover": "回復",
|
||||
|
@ -407,7 +408,7 @@
|
|||
"hearUsWhereTitle": "Ente についてどのようにお聞きになりましたか?(任意)",
|
||||
"hearUsExplanation": "私たちはアプリのインストールを追跡していません。私たちをお知りになった場所を教えてください!",
|
||||
"waitingForBrowserRequest": "ブラウザのリクエストを待っています...",
|
||||
"launchPasskeyUrlAgain": "パスキーのURLを再度起動する",
|
||||
"waitingForVerification": "認証を待っています...",
|
||||
"passkey": "パスキー",
|
||||
"developerSettingsWarning": "開発者向け設定を変更してもよろしいですか?",
|
||||
"developerSettings": "開発者向け設定",
|
||||
|
|
1
auth/lib/l10n/arb/app_ko.arb
Normal file
|
@ -0,0 +1 @@
|
|||
{}
|
|
@ -144,6 +144,8 @@
|
|||
"enterCodeHint": "Voer de 6-cijferige code van je verificatie-app in",
|
||||
"lostDeviceTitle": "Apparaat verloren?",
|
||||
"twoFactorAuthTitle": "Tweestapsverificatie",
|
||||
"passkeyAuthTitle": "Passkey verificatie",
|
||||
"verifyPasskey": "Bevestig passkey",
|
||||
"recoverAccount": "Account herstellen",
|
||||
"enterRecoveryKeyHint": "Voer je herstelsleutel in",
|
||||
"recover": "Herstellen",
|
||||
|
@ -404,5 +406,15 @@
|
|||
"signOutOtherDevices": "Afmelden bij andere apparaten",
|
||||
"doNotSignOut": "Niet uitloggen",
|
||||
"hearUsWhereTitle": "Hoe hoorde je over Ente? (optioneel)",
|
||||
"hearUsExplanation": "Wij gebruiken geen tracking. Het zou helpen als je ons vertelt waar je ons gevonden hebt!"
|
||||
"hearUsExplanation": "Wij gebruiken geen tracking. Het zou helpen als je ons vertelt waar je ons gevonden hebt!",
|
||||
"waitingForBrowserRequest": "Wachten op browserverzoek...",
|
||||
"waitingForVerification": "Wachten op verificatie...",
|
||||
"passkey": "Passkey",
|
||||
"developerSettingsWarning": "Weet u zeker dat u de ontwikkelaarsinstellingen wilt wijzigen?",
|
||||
"developerSettings": "Ontwikkelaarsinstellingen",
|
||||
"serverEndpoint": "Server eindpunt",
|
||||
"invalidEndpoint": "Ongeldig eindpunt",
|
||||
"invalidEndpointMessage": "Sorry, het eindpunt dat u hebt ingevoerd is ongeldig. Voer een geldig eindpunt in en probeer het opnieuw.",
|
||||
"endpointUpdatedMessage": "Eindpunt met succes bijgewerkt",
|
||||
"customEndpoint": "Verbonden met {endpoint}"
|
||||
}
|
|
@ -338,5 +338,22 @@
|
|||
"deleteCodeAuthMessage": "Uwierzytelnij, aby usunąć kod",
|
||||
"showQRAuthMessage": "Uwierzytelnij, aby pokazać kod QR",
|
||||
"confirmAccountDeleteTitle": "Potwierdź usunięcie konta",
|
||||
"confirmAccountDeleteMessage": "To konto jest połączone z innymi aplikacjami ente, jeśli ich używasz.\n\nTwoje przesłane dane, we wszystkich aplikacjach ente, zostaną zaplanowane do usunięcia, a Twoje konto zostanie trwale usunięte."
|
||||
"confirmAccountDeleteMessage": "To konto jest połączone z innymi aplikacjami ente, jeśli ich używasz.\n\nTwoje przesłane dane, we wszystkich aplikacjach ente, zostaną zaplanowane do usunięcia, a Twoje konto zostanie trwale usunięte.",
|
||||
"androidBiometricNotRecognized": "Nie rozpoznano. Spróbuj ponownie.",
|
||||
"@androidBiometricNotRecognized": {
|
||||
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidSignInTitle": "Wymagana autoryzacja",
|
||||
"@androidSignInTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"goToSettings": "Przejdź do Ustawień",
|
||||
"@goToSettings": {
|
||||
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
|
||||
},
|
||||
"noInternetConnection": "Brak połączenia z Internetem",
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain": "Proszę sprawdzić połączenie internetowe i spróbować ponownie.",
|
||||
"hearUsWhereTitle": "Jak usłyszałeś o Ente? (opcjonalnie)",
|
||||
"waitingForVerification": "Oczekiwanie na weryfikację...",
|
||||
"developerSettings": "Ustawienia deweloperskie"
|
||||
}
|
|
@ -47,9 +47,9 @@
|
|||
},
|
||||
"copyEmailAction": "Copiar e-mail",
|
||||
"exportLogsAction": "Exportar logs",
|
||||
"reportABug": "Reportar um bug",
|
||||
"reportABug": "Reportar um problema",
|
||||
"crashAndErrorReporting": "Reporte de erros e falhas",
|
||||
"reportBug": "Reportar bug",
|
||||
"reportBug": "Reportar problema",
|
||||
"emailUsMessage": "Por favor, envie um e-mail para {email}",
|
||||
"@emailUsMessage": {
|
||||
"placeholders": {
|
||||
|
@ -145,6 +145,7 @@
|
|||
"lostDeviceTitle": "Perdeu seu dispositivo?",
|
||||
"twoFactorAuthTitle": "Autenticação de dois fatores",
|
||||
"passkeyAuthTitle": "Autenticação via Chave de acesso",
|
||||
"verifyPasskey": "Verificar chave de acesso",
|
||||
"recoverAccount": "Recuperar conta",
|
||||
"enterRecoveryKeyHint": "Digite sua chave de recuperação",
|
||||
"recover": "Recuperar",
|
||||
|
@ -162,7 +163,7 @@
|
|||
"invalidEmailMessage": "Por favor, insira um endereço de e-mail válido.",
|
||||
"deleteAccount": "Excluir conta",
|
||||
"deleteAccountQuery": "Sentiremos muito por vê-lo partir. Você está enfrentando algum problema?",
|
||||
"yesSendFeedbackAction": "Sim, enviar feedback",
|
||||
"yesSendFeedbackAction": "Sim, enviar comentário",
|
||||
"noDeleteAccountAction": "Não, excluir conta",
|
||||
"initiateAccountDeleteTitle": "Por favor, autentique-se para iniciar a exclusão de conta",
|
||||
"sendEmail": "Enviar e-mail",
|
||||
|
@ -407,7 +408,7 @@
|
|||
"hearUsWhereTitle": "Como você ouviu sobre o Ente? (opcional)",
|
||||
"hearUsExplanation": "Não rastreamos instalações do aplicativo. Seria útil se você nos contasse onde nos encontrou!",
|
||||
"waitingForBrowserRequest": "Aguardando solicitação do navegador...",
|
||||
"launchPasskeyUrlAgain": "Iniciar a URL de chave de acesso novamente",
|
||||
"waitingForVerification": "Esperando por verificação...",
|
||||
"passkey": "Chave de acesso",
|
||||
"developerSettingsWarning": "Tem certeza de que deseja modificar as configurações de Desenvolvedor?",
|
||||
"developerSettings": "Configurações de desenvolvedor",
|
||||
|
|
|
@ -145,6 +145,7 @@
|
|||
"lostDeviceTitle": "丢失了设备吗?",
|
||||
"twoFactorAuthTitle": "双因素认证",
|
||||
"passkeyAuthTitle": "通行密钥认证",
|
||||
"verifyPasskey": "验证通行密钥",
|
||||
"recoverAccount": "恢复账户",
|
||||
"enterRecoveryKeyHint": "输入您的恢复密钥",
|
||||
"recover": "恢复",
|
||||
|
@ -407,7 +408,7 @@
|
|||
"hearUsWhereTitle": "您是如何知道Ente的? (可选的)",
|
||||
"hearUsExplanation": "我们不跟踪应用程序安装情况。如果您告诉我们您是在哪里找到我们的,将会有所帮助!",
|
||||
"waitingForBrowserRequest": "正在等待浏览器请求...",
|
||||
"launchPasskeyUrlAgain": "再次启动 通行密钥 URL",
|
||||
"waitingForVerification": "等待验证...",
|
||||
"passkey": "通行密钥",
|
||||
"developerSettingsWarning": "您确定要修改开发者设置吗?",
|
||||
"developerSettings": "开发者设置",
|
||||
|
|
|
@ -2,7 +2,6 @@ import 'dart:async';
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:adaptive_theme/adaptive_theme.dart';
|
||||
import 'package:computer/computer.dart';
|
||||
import "package:ente_auth/app/view/app.dart";
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/constants.dart';
|
||||
|
@ -17,11 +16,14 @@ import 'package:ente_auth/services/preference_service.dart';
|
|||
import 'package:ente_auth/services/update_service.dart';
|
||||
import 'package:ente_auth/services/user_remote_flag_service.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/services/window_listener_service.dart';
|
||||
import 'package:ente_auth/store/code_store.dart';
|
||||
import 'package:ente_auth/ui/tools/app_lock.dart';
|
||||
import 'package:ente_auth/ui/tools/lock_screen.dart';
|
||||
import 'package:ente_auth/ui/utils/icon_utils.dart';
|
||||
import 'package:ente_auth/utils/crypto_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_auth/utils/window_protocol_handler.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import "package:flutter/material.dart";
|
||||
import 'package:flutter/scheduler.dart';
|
||||
|
@ -29,11 +31,52 @@ import 'package:flutter_displaymode/flutter_displaymode.dart';
|
|||
import 'package:logging/logging.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:privacy_screen/privacy_screen.dart';
|
||||
import 'package:tray_manager/tray_manager.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
final _logger = Logger("main");
|
||||
|
||||
Future<void> initSystemTray() async {
|
||||
String path = Platform.isWindows
|
||||
? 'assets/icons/auth-icon.ico'
|
||||
: 'assets/icons/auth-icon.png';
|
||||
await trayManager.setIcon(path);
|
||||
Menu menu = Menu(
|
||||
items: [
|
||||
MenuItem(
|
||||
key: 'hide_window',
|
||||
label: 'Hide Window',
|
||||
),
|
||||
MenuItem(
|
||||
key: 'show_window',
|
||||
label: 'Show Window',
|
||||
),
|
||||
MenuItem.separator(),
|
||||
MenuItem(
|
||||
key: 'exit_app',
|
||||
label: 'Exit App',
|
||||
),
|
||||
],
|
||||
);
|
||||
await trayManager.setContextMenu(menu);
|
||||
}
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
initSystemTray().ignore();
|
||||
|
||||
if (PlatformUtil.isDesktop()) {
|
||||
await windowManager.ensureInitialized();
|
||||
await WindowListenerService.instance.init();
|
||||
WindowOptions windowOptions = WindowOptions(
|
||||
size: WindowListenerService.instance.getWindowSize(),
|
||||
);
|
||||
await windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||
await windowManager.show();
|
||||
await windowManager.focus();
|
||||
});
|
||||
}
|
||||
await _runInForeground();
|
||||
await _setupPrivacyScreen();
|
||||
if (Platform.isAndroid) {
|
||||
|
@ -70,10 +113,14 @@ ThemeMode _themeMode(AdaptiveThemeMode? savedThemeMode) {
|
|||
}
|
||||
|
||||
Future _runWithLogs(Function() function, {String prefix = ""}) async {
|
||||
String dir = "";
|
||||
try {
|
||||
dir = "${(await getApplicationSupportDirectory()).path}/logs";
|
||||
} catch (_) {}
|
||||
await SuperLogging.main(
|
||||
LogConfig(
|
||||
body: function,
|
||||
logDirPath: (await getApplicationSupportDirectory()).path + "/logs",
|
||||
logDirPath: dir,
|
||||
maxLogFiles: 5,
|
||||
sentryDsn: sentryDSN,
|
||||
enableInDebugMode: true,
|
||||
|
@ -82,10 +129,19 @@ Future _runWithLogs(Function() function, {String prefix = ""}) async {
|
|||
);
|
||||
}
|
||||
|
||||
void _registerWindowsProtocol() {
|
||||
const kWindowsScheme = 'ente';
|
||||
// Register our protocol only on Windows platform
|
||||
if (!kIsWeb && Platform.isWindows) {
|
||||
WindowsProtocolHandler()
|
||||
.register(kWindowsScheme, executable: null, arguments: null);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _init(bool bool, {String? via}) async {
|
||||
// Start workers asynchronously. No need to wait for them to start
|
||||
Computer.shared().turnOn(workersCount: 4, verbose: kDebugMode).ignore();
|
||||
CryptoUtil.init();
|
||||
_registerWindowsProtocol();
|
||||
await initCryptoUtil();
|
||||
|
||||
await PreferenceService.instance.init();
|
||||
await CodeStore.instance.init();
|
||||
await Configuration.instance.init();
|
||||
|
@ -100,6 +156,7 @@ Future<void> _init(bool bool, {String? via}) async {
|
|||
}
|
||||
|
||||
Future<void> _setupPrivacyScreen() async {
|
||||
if (!PlatformUtil.isMobile()) return;
|
||||
final brightness =
|
||||
SchedulerBinding.instance.platformDispatcher.platformBrightness;
|
||||
bool isInDarkMode = brightness == Brightness.dark;
|
||||
|
|
|
@ -57,14 +57,7 @@ class Code {
|
|||
updatedAlgo,
|
||||
updatedType,
|
||||
updatedCounter,
|
||||
"otpauth://${updatedType.name}/" +
|
||||
updateIssuer +
|
||||
":" +
|
||||
updateAccount +
|
||||
"?algorithm=${updatedAlgo.name}&digits=$updatedDigits&issuer=" +
|
||||
updateIssuer +
|
||||
"&period=$updatePeriod&secret=" +
|
||||
updatedSecret + (updatedType == Type.hotp ? "&counter=$updatedCounter" : ""),
|
||||
"otpauth://${updatedType.name}/$updateIssuer:$updateAccount?algorithm=${updatedAlgo.name}&digits=$updatedDigits&issuer=$updateIssuer&period=$updatePeriod&secret=$updatedSecret${updatedType == Type.hotp ? "&counter=$updatedCounter" : ""}",
|
||||
generatedID: generatedID,
|
||||
);
|
||||
}
|
||||
|
@ -83,35 +76,28 @@ class Code {
|
|||
Algorithm.sha1,
|
||||
Type.totp,
|
||||
0,
|
||||
"otpauth://totp/" +
|
||||
issuer +
|
||||
":" +
|
||||
account +
|
||||
"?algorithm=SHA1&digits=6&issuer=" +
|
||||
issuer +
|
||||
"&period=30&secret=" +
|
||||
secret,
|
||||
"otpauth://totp/$issuer:$account?algorithm=SHA1&digits=6&issuer=$issuer&period=30&secret=$secret",
|
||||
);
|
||||
}
|
||||
|
||||
static Code fromRawData(String rawData) {
|
||||
Uri uri = Uri.parse(rawData);
|
||||
try {
|
||||
return Code(
|
||||
_getAccount(uri),
|
||||
_getIssuer(uri),
|
||||
_getDigits(uri),
|
||||
_getPeriod(uri),
|
||||
getSanitizedSecret(uri.queryParameters['secret']!),
|
||||
_getAlgorithm(uri),
|
||||
_getType(uri),
|
||||
_getCounter(uri),
|
||||
rawData,
|
||||
);
|
||||
} catch(e) {
|
||||
return Code(
|
||||
_getAccount(uri),
|
||||
_getIssuer(uri),
|
||||
_getDigits(uri),
|
||||
_getPeriod(uri),
|
||||
getSanitizedSecret(uri.queryParameters['secret']!),
|
||||
_getAlgorithm(uri),
|
||||
_getType(uri),
|
||||
_getCounter(uri),
|
||||
rawData,
|
||||
);
|
||||
} catch (e) {
|
||||
// if account name contains # without encoding,
|
||||
// rest of the url are treated as url fragment
|
||||
if(rawData.contains("#")) {
|
||||
if (rawData.contains("#")) {
|
||||
return Code.fromRawData(rawData.replaceAll("#", '%23'));
|
||||
} else {
|
||||
rethrow;
|
||||
|
@ -141,7 +127,7 @@ class Code {
|
|||
if (uri.queryParameters.containsKey("issuer")) {
|
||||
String issuerName = uri.queryParameters['issuer']!;
|
||||
// Handle issuer name with period
|
||||
// See https://github.com/ente-io/auth/pull/77
|
||||
// See https://github.com/ente-io/ente/pull/77
|
||||
if (issuerName.contains("period=")) {
|
||||
return issuerName.substring(0, issuerName.indexOf("period="));
|
||||
}
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
class DerivedKeyResult {
|
||||
final Uint8List key;
|
||||
final int memLimit;
|
||||
final int opsLimit;
|
||||
|
||||
DerivedKeyResult(this.key, this.memLimit, this.opsLimit);
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
class EncryptionResult {
|
||||
final Uint8List? encryptedData;
|
||||
final Uint8List? key;
|
||||
final Uint8List? header;
|
||||
final Uint8List? nonce;
|
||||
|
||||
EncryptionResult({
|
||||
this.encryptedData,
|
||||
this.key,
|
||||
this.header,
|
||||
this.nonce,
|
||||
});
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ente_auth/app/view/app.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
|
@ -28,7 +29,7 @@ import "package:flutter/material.dart";
|
|||
import 'package:local_auth/local_auth.dart';
|
||||
|
||||
class OnboardingPage extends StatefulWidget {
|
||||
const OnboardingPage({Key? key}) : super(key: key);
|
||||
const OnboardingPage({super.key});
|
||||
|
||||
@override
|
||||
State<OnboardingPage> createState() => _OnboardingPageState();
|
||||
|
@ -86,118 +87,128 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
|||
}
|
||||
}
|
||||
},
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
|
||||
child: Column(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
kDebugMode
|
||||
? GestureDetector(
|
||||
child: const Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: Text("Lang"),
|
||||
),
|
||||
onTap: () async {
|
||||
final locale = await getLocale();
|
||||
// ignore: unawaited_futures
|
||||
routeToPage(
|
||||
context,
|
||||
LanguageSelectorPage(
|
||||
appSupportedLocales,
|
||||
(locale) async {
|
||||
await setLocale(locale);
|
||||
App.setLocale(context, locale);
|
||||
},
|
||||
locale,
|
||||
),
|
||||
).then((value) {
|
||||
setState(() {});
|
||||
});
|
||||
},
|
||||
)
|
||||
: const SizedBox(),
|
||||
Image.asset(
|
||||
"assets/sheild-front-gradient.png",
|
||||
width: 200,
|
||||
height: 200,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Text(
|
||||
"ente",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: 'Montserrat',
|
||||
fontSize: 42,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
"Authenticator",
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
Text(
|
||||
l10n.onBoardingBody,
|
||||
textAlign: TextAlign.center,
|
||||
style:
|
||||
Theme.of(context).textTheme.titleLarge!.copyWith(
|
||||
color: Colors.white38,
|
||||
child: SingleChildScrollView(
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints:
|
||||
const BoxConstraints.tightFor(height: 800, width: 450),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 40.0,
|
||||
horizontal: 40,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
kDebugMode
|
||||
? GestureDetector(
|
||||
child: const Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: Text("Lang"),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 100),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: GradientButton(
|
||||
onTap: _navigateToSignUpPage,
|
||||
text: l10n.newUser,
|
||||
onTap: () async {
|
||||
final locale = await getLocale();
|
||||
// ignore: unawaited_futures
|
||||
routeToPage(
|
||||
context,
|
||||
LanguageSelectorPage(
|
||||
appSupportedLocales,
|
||||
(locale) async {
|
||||
await setLocale(locale);
|
||||
App.setLocale(context, locale);
|
||||
},
|
||||
locale,
|
||||
),
|
||||
).then((value) {
|
||||
setState(() {});
|
||||
});
|
||||
},
|
||||
)
|
||||
: const SizedBox(),
|
||||
Image.asset(
|
||||
"assets/sheild-front-gradient.png",
|
||||
width: 200,
|
||||
height: 200,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Text(
|
||||
"ente",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: 'Montserrat',
|
||||
fontSize: 42,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
"Authenticator",
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
Text(
|
||||
l10n.onBoardingBody,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge!
|
||||
.copyWith(
|
||||
color: Colors.white38,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(20, 12, 20, 0),
|
||||
child: Hero(
|
||||
tag: "log_in",
|
||||
child: ElevatedButton(
|
||||
style: Theme.of(context)
|
||||
.colorScheme
|
||||
.optionalActionButtonStyle,
|
||||
onPressed: _navigateToSignInPage,
|
||||
child: Text(
|
||||
l10n.existingUser,
|
||||
style: const TextStyle(
|
||||
color: Colors.black, // same for both themes
|
||||
const SizedBox(height: 100),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: GradientButton(
|
||||
onTap: _navigateToSignUpPage,
|
||||
text: l10n.newUser,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
height: 56,
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
|
||||
child: Hero(
|
||||
tag: "log_in",
|
||||
child: ElevatedButton(
|
||||
style: Theme.of(context)
|
||||
.colorScheme
|
||||
.optionalActionButtonStyle,
|
||||
onPressed: _navigateToSignInPage,
|
||||
child: Text(
|
||||
l10n.existingUser,
|
||||
style: const TextStyle(
|
||||
color: Colors.black, // same for both themes
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.only(top: 20, bottom: 20),
|
||||
child: GestureDetector(
|
||||
onTap: _optForOfflineMode,
|
||||
child: Center(
|
||||
child: Text(
|
||||
l10n.useOffline,
|
||||
style: body.copyWith(
|
||||
color:
|
||||
Theme.of(context).colorScheme.mutedTextColor,
|
||||
const SizedBox(height: 4),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.only(top: 20, bottom: 20),
|
||||
child: GestureDetector(
|
||||
onTap: _optForOfflineMode,
|
||||
child: Center(
|
||||
child: Text(
|
||||
l10n.useOffline,
|
||||
style: body.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.mutedTextColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const DeveloperSettingsWidget(),
|
||||
],
|
||||
const DeveloperSettingsWidget(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -208,7 +219,9 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
|||
}
|
||||
|
||||
Future<void> _optForOfflineMode() async {
|
||||
bool canCheckBio = await LocalAuthentication().canCheckBiometrics;
|
||||
bool canCheckBio = Platform.isMacOS || Platform.isLinux
|
||||
? true
|
||||
: await LocalAuthentication().canCheckBiometrics;
|
||||
if (!canCheckBio) {
|
||||
showToast(
|
||||
context,
|
||||
|
|
|
@ -9,7 +9,7 @@ import "package:flutter/material.dart";
|
|||
class SetupEnterSecretKeyPage extends StatefulWidget {
|
||||
final Code? code;
|
||||
|
||||
SetupEnterSecretKeyPage({this.code, Key? key}) : super(key: key);
|
||||
SetupEnterSecretKeyPage({this.code, super.key});
|
||||
|
||||
@override
|
||||
State<SetupEnterSecretKeyPage> createState() =>
|
||||
|
@ -32,7 +32,7 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
|
|||
widget.code != null ? safeDecode(widget.code!.account).trim() : null,
|
||||
);
|
||||
_secretController = TextEditingController(
|
||||
text: widget.code != null ? widget.code!.secret : null,
|
||||
text: widget.code?.secret,
|
||||
);
|
||||
_secretKeyObscured = widget.code != null;
|
||||
super.initState();
|
||||
|
@ -45,8 +45,8 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
|
|||
appBar: AppBar(
|
||||
title: Text(l10n.importAccountPageTitle),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
|
||||
child: Column(
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import 'dart:math';
|
||||
|
||||
import "package:ente_auth/l10n/l10n.dart";
|
||||
|
@ -10,7 +9,7 @@ import 'package:qr_flutter/qr_flutter.dart';
|
|||
class ViewQrPage extends StatelessWidget {
|
||||
final Code? code;
|
||||
|
||||
ViewQrPage({this.code, Key? key}) : super(key: key);
|
||||
ViewQrPage({this.code, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -22,15 +21,22 @@ class ViewQrPage extends StatelessWidget {
|
|||
appBar: AppBar(
|
||||
title: Text(l10n.qrCode),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
|
||||
child: Column(
|
||||
children: [
|
||||
QrImage(
|
||||
QrImageView(
|
||||
data: code!.rawData,
|
||||
foregroundColor: Theme.of(context).colorScheme.onBackground,
|
||||
eyeStyle: QrEyeStyle(
|
||||
eyeShape: QrEyeShape.square,
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
dataModuleStyle: QrDataModuleStyle(
|
||||
dataModuleShape: QrDataModuleShape.square,
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
version: QrVersions.auto,
|
||||
size: qrSize,
|
||||
),
|
||||
|
|
|
@ -15,9 +15,8 @@ import 'package:ente_auth/models/authenticator/entity_result.dart';
|
|||
import 'package:ente_auth/models/authenticator/local_auth_entity.dart';
|
||||
import 'package:ente_auth/store/authenticator_db.dart';
|
||||
import 'package:ente_auth/store/offline_authenticator_db.dart';
|
||||
import 'package:ente_auth/utils/crypto_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
|
@ -75,10 +74,10 @@ class AuthenticatorService {
|
|||
final key = await getOrCreateAuthDataKey(mode);
|
||||
for (LocalAuthEntity e in result) {
|
||||
try {
|
||||
final decryptedValue = await CryptoUtil.decryptChaCha(
|
||||
Sodium.base642bin(e.encryptedData),
|
||||
final decryptedValue = await CryptoUtil.decryptData(
|
||||
CryptoUtil.base642bin(e.encryptedData),
|
||||
key,
|
||||
Sodium.base642bin(e.header),
|
||||
CryptoUtil.base642bin(e.header),
|
||||
);
|
||||
final hasSynced = !(e.id == null || e.shouldSync);
|
||||
entities.add(
|
||||
|
@ -101,12 +100,13 @@ class AuthenticatorService {
|
|||
AccountMode accountMode,
|
||||
) async {
|
||||
var key = await getOrCreateAuthDataKey(accountMode);
|
||||
final encryptedKeyData = await CryptoUtil.encryptChaCha(
|
||||
utf8.encode(plainText) as Uint8List,
|
||||
final encryptedKeyData = await CryptoUtil.encryptData(
|
||||
utf8.encode(plainText),
|
||||
key,
|
||||
);
|
||||
String encryptedData = Sodium.bin2base64(encryptedKeyData.encryptedData!);
|
||||
String header = Sodium.bin2base64(encryptedKeyData.header!);
|
||||
String encryptedData =
|
||||
CryptoUtil.bin2base64(encryptedKeyData.encryptedData!);
|
||||
String header = CryptoUtil.bin2base64(encryptedKeyData.header!);
|
||||
final insertedID = accountMode.isOnline
|
||||
? await _db.insert(encryptedData, header)
|
||||
: await _offlineDb.insert(encryptedData, header);
|
||||
|
@ -123,12 +123,13 @@ class AuthenticatorService {
|
|||
AccountMode accountMode,
|
||||
) async {
|
||||
var key = await getOrCreateAuthDataKey(accountMode);
|
||||
final encryptedKeyData = await CryptoUtil.encryptChaCha(
|
||||
utf8.encode(plainText) as Uint8List,
|
||||
final encryptedKeyData = await CryptoUtil.encryptData(
|
||||
utf8.encode(plainText),
|
||||
key,
|
||||
);
|
||||
String encryptedData = Sodium.bin2base64(encryptedKeyData.encryptedData!);
|
||||
String header = Sodium.bin2base64(encryptedKeyData.header!);
|
||||
String encryptedData =
|
||||
CryptoUtil.bin2base64(encryptedKeyData.encryptedData!);
|
||||
String header = CryptoUtil.bin2base64(encryptedKeyData.header!);
|
||||
final int affectedRows = accountMode.isOnline
|
||||
? await _db.updateEntry(generatedID, encryptedData, header)
|
||||
: await _offlineDb.updateEntry(generatedID, encryptedData, header);
|
||||
|
@ -191,25 +192,25 @@ class AuthenticatorService {
|
|||
Future<void> _remoteToLocalSync() async {
|
||||
_logger.info('Initiating remote to local sync');
|
||||
final int lastSyncTime = _prefs.getInt(_lastEntitySyncTime) ?? 0;
|
||||
_logger.info("Current sync is " + lastSyncTime.toString());
|
||||
_logger.info("Current sync is $lastSyncTime");
|
||||
const int fetchLimit = 500;
|
||||
final List<AuthEntity> result =
|
||||
await _gateway.getDiff(lastSyncTime, limit: fetchLimit);
|
||||
_logger.info(result.length.toString() + " entries fetched from remote");
|
||||
_logger.info("${result.length} entries fetched from remote");
|
||||
if (result.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final maxSyncTime = result.map((e) => e.updatedAt).reduce(max);
|
||||
List<String> deletedIDs =
|
||||
result.where((element) => element.isDeleted).map((e) => e.id).toList();
|
||||
_logger.info(deletedIDs.length.toString() + " entries deleted");
|
||||
_logger.info("${deletedIDs.length} entries deleted");
|
||||
result.removeWhere((element) => element.isDeleted);
|
||||
await _db.insertOrReplace(result);
|
||||
if (deletedIDs.isNotEmpty) {
|
||||
await _db.deleteByIDs(ids: deletedIDs);
|
||||
}
|
||||
await _prefs.setInt(_lastEntitySyncTime, maxSyncTime);
|
||||
_logger.info("Setting synctime to " + maxSyncTime.toString());
|
||||
_logger.info("Setting synctime to $maxSyncTime");
|
||||
if (result.length == fetchLimit) {
|
||||
_logger.info("Diff limit reached, pulling again");
|
||||
await _remoteToLocalSync();
|
||||
|
@ -223,7 +224,7 @@ class AuthenticatorService {
|
|||
.where((element) => element.shouldSync || element.id == null)
|
||||
.toList();
|
||||
_logger.info(
|
||||
pendingUpdate.length.toString() + " entries to be updated at remote",
|
||||
"${pendingUpdate.length} entries to be updated at remote",
|
||||
);
|
||||
for (LocalAuthEntity entity in pendingUpdate) {
|
||||
if (entity.id == null) {
|
||||
|
@ -262,21 +263,21 @@ class AuthenticatorService {
|
|||
try {
|
||||
final AuthKey response = await _gateway.getKey();
|
||||
final authKey = CryptoUtil.decryptSync(
|
||||
Sodium.base642bin(response.encryptedKey),
|
||||
CryptoUtil.base642bin(response.encryptedKey),
|
||||
_config.getKey()!,
|
||||
Sodium.base642bin(response.header),
|
||||
CryptoUtil.base642bin(response.header),
|
||||
);
|
||||
await _config.setAuthSecretKey(Sodium.bin2base64(authKey));
|
||||
await _config.setAuthSecretKey(CryptoUtil.bin2base64(authKey));
|
||||
return authKey;
|
||||
} on AuthenticatorKeyNotFound catch (e) {
|
||||
_logger.info("AuthenticatorKeyNotFound generating key ${e.stackTrace}");
|
||||
final key = CryptoUtil.generateKey();
|
||||
final encryptedKeyData = CryptoUtil.encryptSync(key, _config.getKey()!);
|
||||
await _gateway.createKey(
|
||||
Sodium.bin2base64(encryptedKeyData.encryptedData!),
|
||||
Sodium.bin2base64(encryptedKeyData.nonce!),
|
||||
CryptoUtil.bin2base64(encryptedKeyData.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptedKeyData.nonce!),
|
||||
);
|
||||
await _config.setAuthSecretKey(Sodium.bin2base64(key));
|
||||
await _config.setAuthSecretKey(CryptoUtil.bin2base64(key));
|
||||
return key;
|
||||
} catch (e, s) {
|
||||
_logger.severe("Failed to getOrCreateAuthDataKey", e, s);
|
||||
|
|
|
@ -50,7 +50,7 @@ class BillingService {
|
|||
|
||||
Future<Response<dynamic>> _fetchPrivateBillingPlans() {
|
||||
return _dio.get(
|
||||
_config.getHttpEndpoint() + "/billing/user-plans/",
|
||||
"${_config.getHttpEndpoint()}/billing/user-plans/",
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
|
@ -60,7 +60,7 @@ class BillingService {
|
|||
}
|
||||
|
||||
Future<Response<dynamic>> _fetchPublicBillingPlans() {
|
||||
return _dio.get(_config.getHttpEndpoint() + "/billing/plans/v2");
|
||||
return _dio.get("${_config.getHttpEndpoint()}/billing/plans/v2");
|
||||
}
|
||||
|
||||
Future<Subscription> verifySubscription(
|
||||
|
@ -70,7 +70,7 @@ class BillingService {
|
|||
}) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/billing/verify-subscription",
|
||||
"${_config.getHttpEndpoint()}/billing/verify-subscription",
|
||||
data: {
|
||||
"paymentProvider": paymentProvider ??
|
||||
(Platform.isAndroid ? "playstore" : "appstore"),
|
||||
|
@ -84,7 +84,7 @@ class BillingService {
|
|||
),
|
||||
);
|
||||
return Subscription.fromMap(response.data["subscription"]);
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
if (e.response != null && e.response!.statusCode == 409) {
|
||||
throw SubscriptionAlreadyClaimedError();
|
||||
} else {
|
||||
|
@ -100,7 +100,7 @@ class BillingService {
|
|||
if (_cachedSubscription == null) {
|
||||
try {
|
||||
final response = await _dio.get(
|
||||
_config.getHttpEndpoint() + "/billing/subscription",
|
||||
"${_config.getHttpEndpoint()}/billing/subscription",
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
|
@ -109,7 +109,7 @@ class BillingService {
|
|||
);
|
||||
_cachedSubscription =
|
||||
Subscription.fromMap(response.data["subscription"]);
|
||||
} on DioError catch (e, s) {
|
||||
} on DioException catch (e, s) {
|
||||
_logger.severe(e, s);
|
||||
rethrow;
|
||||
}
|
||||
|
@ -120,7 +120,7 @@ class BillingService {
|
|||
Future<Subscription> cancelStripeSubscription() async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/billing/stripe/cancel-subscription",
|
||||
"${_config.getHttpEndpoint()}/billing/stripe/cancel-subscription",
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
|
@ -129,7 +129,7 @@ class BillingService {
|
|||
);
|
||||
final subscription = Subscription.fromMap(response.data["subscription"]);
|
||||
return subscription;
|
||||
} on DioError catch (e, s) {
|
||||
} on DioException catch (e, s) {
|
||||
_logger.severe(e, s);
|
||||
rethrow;
|
||||
}
|
||||
|
@ -138,7 +138,7 @@ class BillingService {
|
|||
Future<Subscription> activateStripeSubscription() async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/billing/stripe/activate-subscription",
|
||||
"${_config.getHttpEndpoint()}/billing/stripe/activate-subscription",
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
|
@ -147,7 +147,7 @@ class BillingService {
|
|||
);
|
||||
final subscription = Subscription.fromMap(response.data["subscription"]);
|
||||
return subscription;
|
||||
} on DioError catch (e, s) {
|
||||
} on DioException catch (e, s) {
|
||||
_logger.severe(e, s);
|
||||
rethrow;
|
||||
}
|
||||
|
@ -158,7 +158,7 @@ class BillingService {
|
|||
}) async {
|
||||
try {
|
||||
final response = await _dio.get(
|
||||
_config.getHttpEndpoint() + "/billing/stripe/customer-portal",
|
||||
"${_config.getHttpEndpoint()}/billing/stripe/customer-portal",
|
||||
queryParameters: {
|
||||
"redirectURL": kWebPaymentRedirectUrl,
|
||||
},
|
||||
|
@ -169,7 +169,7 @@ class BillingService {
|
|||
),
|
||||
);
|
||||
return response.data["url"];
|
||||
} on DioError catch (e, s) {
|
||||
} on DioException catch (e, s) {
|
||||
_logger.severe(e, s);
|
||||
rethrow;
|
||||
}
|
||||
|
|
|
@ -1,15 +1,21 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/ui/tools/app_lock.dart';
|
||||
import 'package:ente_auth/utils/auth_util.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_local_authentication/flutter_local_authentication.dart';
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class LocalAuthenticationService {
|
||||
LocalAuthenticationService._privateConstructor();
|
||||
static final LocalAuthenticationService instance =
|
||||
LocalAuthenticationService._privateConstructor();
|
||||
final logger = Logger((LocalAuthenticationService).toString());
|
||||
|
||||
Future<bool> requestLocalAuthentication(
|
||||
BuildContext context,
|
||||
|
@ -38,7 +44,7 @@ class LocalAuthenticationService {
|
|||
String errorDialogContent, [
|
||||
String errorDialogTitle = "",
|
||||
]) async {
|
||||
if (await LocalAuthentication().isDeviceSupported()) {
|
||||
if (await _isLocalAuthSupportedOnDevice()) {
|
||||
AppLock.of(context)!.disable();
|
||||
final result = await requestAuthentication(
|
||||
context,
|
||||
|
@ -65,6 +71,12 @@ class LocalAuthenticationService {
|
|||
}
|
||||
|
||||
Future<bool> _isLocalAuthSupportedOnDevice() async {
|
||||
return await LocalAuthentication().isDeviceSupported();
|
||||
try {
|
||||
return Platform.isMacOS || Platform.isLinux
|
||||
? await FlutterLocalAuthentication().canAuthenticate()
|
||||
: await LocalAuthentication().isDeviceSupported();
|
||||
} on MissingPluginException {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,8 +27,7 @@ class NotificationService {
|
|||
_flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>();
|
||||
if (implementation != null) {
|
||||
// ignore: unawaited_futures
|
||||
implementation.requestPermission();
|
||||
await implementation.requestNotificationsPermission();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'dart:io';
|
|||
import 'package:ente_auth/core/constants.dart';
|
||||
import 'package:ente_auth/core/network.dart';
|
||||
import 'package:ente_auth/services/notification_service.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
@ -130,7 +131,8 @@ class UpdateService {
|
|||
|
||||
bool isIndependent() {
|
||||
return flavor == "independent" ||
|
||||
_packageInfo.packageName.endsWith("independent");
|
||||
_packageInfo.packageName.endsWith("independent") ||
|
||||
PlatformUtil.isDesktop();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,6 +143,7 @@ class LatestVersionInfo {
|
|||
final bool? shouldForceUpdate;
|
||||
final int lastSupportedVersionCode;
|
||||
final String? url;
|
||||
final String? release;
|
||||
final int? size;
|
||||
final bool? shouldNotify;
|
||||
|
||||
|
@ -151,6 +154,7 @@ class LatestVersionInfo {
|
|||
this.shouldForceUpdate,
|
||||
this.lastSupportedVersionCode,
|
||||
this.url,
|
||||
this.release,
|
||||
this.size,
|
||||
this.shouldNotify,
|
||||
);
|
||||
|
@ -163,6 +167,7 @@ class LatestVersionInfo {
|
|||
map['shouldForceUpdate'],
|
||||
map['lastSupportedVersionCode'] ?? 1,
|
||||
map['url'],
|
||||
map['release'],
|
||||
map['size'],
|
||||
map['shouldNotify'],
|
||||
);
|
||||
|
|
|
@ -96,7 +96,7 @@ class UserRemoteFlagService {
|
|||
queryParams["defaultValue"] = defaultValue;
|
||||
}
|
||||
final response = await _dio.get(
|
||||
_config.getHttpEndpoint() + "/remote-store",
|
||||
"${_config.getHttpEndpoint()}/remote-store",
|
||||
queryParameters: queryParams,
|
||||
options: Options(
|
||||
headers: {
|
||||
|
@ -119,7 +119,7 @@ class UserRemoteFlagService {
|
|||
Future<void> _updateKeyValue(String key, String value) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/remote-store/update",
|
||||
"${_config.getHttpEndpoint()}/remote-store/update",
|
||||
data: {
|
||||
"key": key,
|
||||
"value": value,
|
||||
|
|
|
@ -30,9 +30,9 @@ import 'package:ente_auth/ui/home_page.dart';
|
|||
import 'package:ente_auth/ui/passkey_page.dart';
|
||||
import 'package:ente_auth/ui/two_factor_authentication_page.dart';
|
||||
import 'package:ente_auth/ui/two_factor_recovery_page.dart';
|
||||
import 'package:ente_auth/utils/crypto_util.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import "package:flutter/foundation.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
@ -80,7 +80,7 @@ class UserService {
|
|||
await dialog.show();
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/users/ott",
|
||||
"${_config.getHttpEndpoint()}/users/ott",
|
||||
data: {"email": email, "purpose": isChangeEmail ? "change" : ""},
|
||||
);
|
||||
await dialog.hide();
|
||||
|
@ -102,7 +102,7 @@ class UserService {
|
|||
return;
|
||||
}
|
||||
unawaited(showGenericErrorDialog(context: context));
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
await dialog.hide();
|
||||
_logger.info(e);
|
||||
if (e.response != null && e.response!.statusCode == 403) {
|
||||
|
@ -129,7 +129,7 @@ class UserService {
|
|||
String type = "SubCancellation",
|
||||
}) async {
|
||||
await _dio.post(
|
||||
_config.getHttpEndpoint() + "/anonymous/feedback",
|
||||
"${_config.getHttpEndpoint()}/anonymous/feedback",
|
||||
data: {"feedback": feedback, "type": "type"},
|
||||
);
|
||||
}
|
||||
|
@ -173,7 +173,7 @@ class UserService {
|
|||
try {
|
||||
final response = await _enteDio.get("/users/sessions");
|
||||
return Sessions.fromMap(response.data);
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
_logger.info(e);
|
||||
rethrow;
|
||||
}
|
||||
|
@ -187,7 +187,7 @@ class UserService {
|
|||
"token": token,
|
||||
},
|
||||
);
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
_logger.info(e);
|
||||
rethrow;
|
||||
}
|
||||
|
@ -196,7 +196,7 @@ class UserService {
|
|||
Future<void> leaveFamilyPlan() async {
|
||||
try {
|
||||
await _enteDio.delete("/family/leave");
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
_logger.warning('failed to leave family plan', e);
|
||||
rethrow;
|
||||
}
|
||||
|
@ -306,11 +306,11 @@ class UserService {
|
|||
"ott": ott,
|
||||
};
|
||||
if (!_config.isLoggedIn()) {
|
||||
verifyData["source"] = 'auth:' + _getRefSource();
|
||||
verifyData["source"] = 'auth:${_getRefSource()}';
|
||||
}
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/users/verify-email",
|
||||
"${_config.getHttpEndpoint()}/users/verify-email",
|
||||
data: verifyData,
|
||||
);
|
||||
await dialog.hide();
|
||||
|
@ -346,7 +346,7 @@ class UserService {
|
|||
// should never reach here
|
||||
throw Exception("unexpected response during email verification");
|
||||
}
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
_logger.info(e);
|
||||
await dialog.hide();
|
||||
if (e.response != null && e.response!.statusCode == 410) {
|
||||
|
@ -410,7 +410,7 @@ class UserService {
|
|||
context.l10n.oops,
|
||||
context.l10n.verificationFailedPleaseTryAgain,
|
||||
);
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
await dialog.hide();
|
||||
if (e.response != null && e.response!.statusCode == 403) {
|
||||
// ignore: unawaited_futures
|
||||
|
@ -460,7 +460,7 @@ class UserService {
|
|||
Future<SrpAttributes> getSrpAttributes(String email) async {
|
||||
try {
|
||||
final response = await _dio.get(
|
||||
_config.getHttpEndpoint() + "/users/srp/attributes",
|
||||
"${_config.getHttpEndpoint()}/users/srp/attributes",
|
||||
queryParameters: {
|
||||
"email": email,
|
||||
},
|
||||
|
@ -470,7 +470,7 @@ class UserService {
|
|||
} else {
|
||||
throw Exception("get-srp-attributes action failed");
|
||||
}
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
if (e.response != null && e.response!.statusCode == 404) {
|
||||
throw SrpSetupNotCompleteError();
|
||||
}
|
||||
|
@ -523,7 +523,7 @@ class UserService {
|
|||
// ignore: need to calculate secret to get M1, unused_local_variable
|
||||
final clientS = client.calculateSecret(serverB);
|
||||
final clientM = client.calculateClientEvidenceMessage();
|
||||
// ignore: unused_local_variable
|
||||
|
||||
late Response _;
|
||||
if (setKeysRequest == null) {
|
||||
_ = await _enteDio.post(
|
||||
|
@ -573,7 +573,7 @@ class UserService {
|
|||
late Uint8List keyEncryptionKey;
|
||||
_logger.finest('Start deriving key');
|
||||
keyEncryptionKey = await CryptoUtil.deriveKey(
|
||||
utf8.encode(userPassword) as Uint8List,
|
||||
utf8.encode(userPassword),
|
||||
CryptoUtil.base642bin(srpAttributes.kekSalt),
|
||||
srpAttributes.memLimit,
|
||||
srpAttributes.opsLimit,
|
||||
|
@ -596,7 +596,7 @@ class UserService {
|
|||
|
||||
final A = client.generateClientCredentials(salt, identity, password);
|
||||
final createSessionResponse = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/users/srp/create-session",
|
||||
"${_config.getHttpEndpoint()}/users/srp/create-session",
|
||||
data: {
|
||||
"srpUserID": srpAttributes.srpUserID,
|
||||
"srpA": base64Encode(SRP6Util.encodeBigInt(A!)),
|
||||
|
@ -610,7 +610,7 @@ class UserService {
|
|||
final clientS = client.calculateSecret(serverB);
|
||||
final clientM = client.calculateClientEvidenceMessage();
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/users/srp/verify-session",
|
||||
"${_config.getHttpEndpoint()}/users/srp/verify-session",
|
||||
data: {
|
||||
"sessionID": sessionID,
|
||||
"srpUserID": srpAttributes.srpUserID,
|
||||
|
@ -709,7 +709,7 @@ class UserService {
|
|||
await dialog.show();
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/users/two-factor/verify",
|
||||
"${_config.getHttpEndpoint()}/users/two-factor/verify",
|
||||
data: {
|
||||
"sessionID": sessionID,
|
||||
"code": code,
|
||||
|
@ -729,7 +729,7 @@ class UserService {
|
|||
(route) => route.isFirst,
|
||||
);
|
||||
}
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
await dialog.hide();
|
||||
_logger.severe(e);
|
||||
if (e.response != null && e.response!.statusCode == 404) {
|
||||
|
@ -772,7 +772,7 @@ class UserService {
|
|||
await dialog.show();
|
||||
try {
|
||||
final response = await _dio.get(
|
||||
_config.getHttpEndpoint() + "/users/two-factor/recover",
|
||||
"${_config.getHttpEndpoint()}/users/two-factor/recover",
|
||||
queryParameters: {
|
||||
"sessionID": sessionID,
|
||||
"twoFactorType": twoFactorTypeToString(type),
|
||||
|
@ -794,7 +794,7 @@ class UserService {
|
|||
(route) => route.isFirst,
|
||||
);
|
||||
}
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
await dialog.hide();
|
||||
_logger.severe(e);
|
||||
if (e.response != null && e.response!.statusCode == 404) {
|
||||
|
@ -868,7 +868,7 @@ class UserService {
|
|||
}
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/users/two-factor/remove",
|
||||
"${_config.getHttpEndpoint()}/users/two-factor/remove",
|
||||
data: {
|
||||
"sessionID": sessionID,
|
||||
"secret": secret,
|
||||
|
@ -891,7 +891,7 @@ class UserService {
|
|||
(route) => route.isFirst,
|
||||
);
|
||||
}
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
await dialog.hide();
|
||||
_logger.severe(e);
|
||||
if (e.response != null && e.response!.statusCode == 404) {
|
||||
|
|
36
auth/lib/services/window_listener_service.dart
Normal file
|
@ -0,0 +1,36 @@
|
|||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
class WindowListenerService {
|
||||
late SharedPreferences _preferences;
|
||||
|
||||
WindowListenerService._privateConstructor();
|
||||
|
||||
static final WindowListenerService instance =
|
||||
WindowListenerService._privateConstructor();
|
||||
|
||||
Future<void> init() async {
|
||||
_preferences = await SharedPreferences.getInstance();
|
||||
}
|
||||
|
||||
Size getWindowSize() {
|
||||
final double windowWidth = _preferences.getDouble('windowWidth') ?? 450.0;
|
||||
final double windowHeight = _preferences.getDouble('windowHeight') ?? 800.0;
|
||||
return Size(windowWidth, windowHeight);
|
||||
}
|
||||
|
||||
Future<void> onWindowResize() async {
|
||||
// Save the window size to shared preferences
|
||||
await _preferences.setDouble(
|
||||
'windowWidth',
|
||||
(await windowManager.getSize()).width,
|
||||
);
|
||||
await _preferences.setDouble(
|
||||
'windowHeight',
|
||||
(await windowManager.getSize()).height,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -3,10 +3,12 @@ import 'dart:io';
|
|||
|
||||
import 'package:ente_auth/models/authenticator/auth_entity.dart';
|
||||
import 'package:ente_auth/models/authenticator/local_auth_entity.dart';
|
||||
import 'package:ente_auth/utils/directory_utils.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
|
||||
class AuthenticatorDB {
|
||||
static const _databaseName = "ente.authenticator.db";
|
||||
|
@ -25,6 +27,16 @@ class AuthenticatorDB {
|
|||
}
|
||||
|
||||
Future<Database> _initDatabase() async {
|
||||
if (Platform.isWindows || Platform.isLinux) {
|
||||
var databaseFactory = databaseFactoryFfi;
|
||||
return await databaseFactory.openDatabase(
|
||||
await DirectoryUtils.getDatabasePath(_databaseName),
|
||||
options: OpenDatabaseOptions(
|
||||
version: _databaseVersion,
|
||||
onCreate: _onCreate,
|
||||
),
|
||||
);
|
||||
}
|
||||
final Directory documentsDirectory =
|
||||
await getApplicationDocumentsDirectory();
|
||||
final String path = join(documentsDirectory.path, _databaseName);
|
||||
|
@ -166,7 +178,7 @@ class AuthenticatorDB {
|
|||
batch.delete(entityTable, where: whereID, whereArgs: [id]);
|
||||
}
|
||||
}
|
||||
await batch.commit();
|
||||
final _ = await batch.commit();
|
||||
debugPrint("Done");
|
||||
}
|
||||
|
||||
|
|
|
@ -3,10 +3,11 @@ import 'dart:io';
|
|||
|
||||
import 'package:ente_auth/models/authenticator/auth_entity.dart';
|
||||
import 'package:ente_auth/models/authenticator/local_auth_entity.dart';
|
||||
import 'package:ente_auth/utils/directory_utils.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
|
||||
class OfflineAuthenticatorDB {
|
||||
static const _databaseName = "ente.offline_authenticator.db";
|
||||
|
@ -26,6 +27,16 @@ class OfflineAuthenticatorDB {
|
|||
}
|
||||
|
||||
Future<Database> _initDatabase() async {
|
||||
if (Platform.isWindows || Platform.isLinux) {
|
||||
var databaseFactory = databaseFactoryFfi;
|
||||
return await databaseFactory.openDatabase(
|
||||
await DirectoryUtils.getDatabasePath(_databaseName),
|
||||
options: OpenDatabaseOptions(
|
||||
version: _databaseVersion,
|
||||
onCreate: _onCreate,
|
||||
),
|
||||
);
|
||||
}
|
||||
final Directory documentsDirectory =
|
||||
await getApplicationDocumentsDirectory();
|
||||
final String path = join(documentsDirectory.path, _databaseName);
|
||||
|
@ -152,7 +163,7 @@ class OfflineAuthenticatorDB {
|
|||
batch.delete(entityTable, where: whereID, whereArgs: [id]);
|
||||
}
|
||||
}
|
||||
await batch.commit();
|
||||
final _ = await batch.commit();
|
||||
debugPrint("Done");
|
||||
}
|
||||
|
||||
|
|
|
@ -204,6 +204,7 @@ const Color _warning700 = Color.fromRGBO(234, 63, 63, 1);
|
|||
const Color _warning500 = Color.fromRGBO(255, 101, 101, 1);
|
||||
const Color _warning800 = Color(0xFFF53434);
|
||||
const Color warning500 = Color.fromRGBO(255, 101, 101, 1);
|
||||
// ignore: unused_element
|
||||
const Color _warning400 = Color.fromRGBO(255, 111, 111, 1);
|
||||
|
||||
const Color _caution500 = Color.fromRGBO(255, 194, 71, 1);
|
||||
|
|
|
@ -5,7 +5,7 @@ import 'package:ente_auth/utils/email_util.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class ChangeEmailDialog extends StatefulWidget {
|
||||
const ChangeEmailDialog({Key? key}) : super(key: key);
|
||||
const ChangeEmailDialog({super.key});
|
||||
|
||||
@override
|
||||
State<ChangeEmailDialog> createState() => _ChangeEmailDialogState();
|
||||
|
|
|
@ -7,15 +7,15 @@ import 'package:ente_auth/services/local_authentication_service.dart';
|
|||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/ui/common/dialogs.dart';
|
||||
import 'package:ente_auth/ui/common/gradient_button.dart';
|
||||
import 'package:ente_auth/utils/crypto_util.dart';
|
||||
import 'package:ente_auth/utils/email_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
||||
|
||||
class DeleteAccountPage extends StatelessWidget {
|
||||
const DeleteAccountPage({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -150,6 +150,8 @@ class DeleteAccountPage extends StatelessWidget {
|
|||
l10n.initiateAccountDeleteTitle,
|
||||
);
|
||||
|
||||
await PlatformUtil.refocusWindows();
|
||||
|
||||
if (hasAuthenticated) {
|
||||
final choice = await showChoiceDialogOld(
|
||||
context,
|
||||
|
@ -164,8 +166,10 @@ class DeleteAccountPage extends StatelessWidget {
|
|||
return;
|
||||
}
|
||||
final decryptChallenge = CryptoUtil.openSealSync(
|
||||
Sodium.base642bin(response.encryptedChallenge),
|
||||
Sodium.base642bin(Configuration.instance.getKeyAttributes()!.publicKey),
|
||||
CryptoUtil.base642bin(response.encryptedChallenge),
|
||||
CryptoUtil.base642bin(
|
||||
Configuration.instance.getKeyAttributes()!.publicKey,
|
||||
),
|
||||
Configuration.instance.getSecretKey()!,
|
||||
);
|
||||
final challengeResponseStr = utf8.decode(decryptChallenge);
|
||||
|
|
|
@ -5,7 +5,7 @@ import 'package:ente_auth/l10n/l10n.dart';
|
|||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:ente_auth/ui/common/dynamic_fab.dart';
|
||||
import 'package:ente_auth/ui/common/web_page.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
@ -14,7 +14,7 @@ import 'package:step_progress_indicator/step_progress_indicator.dart';
|
|||
import "package:styled_text/styled_text.dart";
|
||||
|
||||
class EmailEntryPage extends StatefulWidget {
|
||||
const EmailEntryPage({Key? key}) : super(key: key);
|
||||
const EmailEntryPage({super.key});
|
||||
|
||||
@override
|
||||
State<EmailEntryPage> createState() => _EmailEntryPageState();
|
||||
|
@ -190,6 +190,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
|||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
|
||||
child: TextFormField(
|
||||
keyboardType: TextInputType.text,
|
||||
textInputAction: TextInputAction.next,
|
||||
controller: _passwordController1,
|
||||
obscureText: !_password1Visible,
|
||||
enableSuggestions: true,
|
||||
|
@ -427,15 +428,10 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
|||
tags: {
|
||||
'u-terms': StyledTextActionTag(
|
||||
(String? text, Map<String?, String?> attrs) =>
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return WebPage(
|
||||
context.l10n.termsOfServicesTitle,
|
||||
"https://ente.io/terms",
|
||||
);
|
||||
},
|
||||
),
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.l10n.termsOfServicesTitle,
|
||||
"https://ente.io/terms",
|
||||
),
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
|
@ -443,15 +439,10 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
|||
),
|
||||
'u-policy': StyledTextActionTag(
|
||||
(String? text, Map<String?, String?> attrs) =>
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return WebPage(
|
||||
context.l10n.privacyPolicyTitle,
|
||||
"https://ente.io/privacy",
|
||||
);
|
||||
},
|
||||
),
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.l10n.privacyPolicyTitle,
|
||||
"https://ente.io/privacy",
|
||||
),
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
|
@ -494,15 +485,10 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
|||
tags: {
|
||||
'underline': StyledTextActionTag(
|
||||
(String? text, Map<String?, String?> attrs) =>
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return WebPage(
|
||||
context.l10n.encryption,
|
||||
"https://ente.io/architecture",
|
||||
);
|
||||
},
|
||||
),
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.l10n.encryption,
|
||||
"https://ente.io/architecture",
|
||||
),
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
|
|
|
@ -6,13 +6,13 @@ import 'package:ente_auth/models/api/user/srp.dart';
|
|||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/ui/account/login_pwd_verification_page.dart';
|
||||
import 'package:ente_auth/ui/common/dynamic_fab.dart';
|
||||
import 'package:ente_auth/ui/common/web_page.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import "package:styled_text/styled_text.dart";
|
||||
|
||||
class LoginPage extends StatefulWidget {
|
||||
const LoginPage({Key? key}) : super(key: key);
|
||||
const LoginPage({super.key});
|
||||
|
||||
@override
|
||||
State<LoginPage> createState() => _LoginPageState();
|
||||
|
@ -25,6 +25,36 @@ class _LoginPageState extends State<LoginPage> {
|
|||
Color? _emailInputFieldColor;
|
||||
final Logger _logger = Logger('_LoginPageState');
|
||||
|
||||
Future<void> onPressed() async {
|
||||
await UserService.instance.setEmail(_email!);
|
||||
Configuration.instance.resetVolatilePassword();
|
||||
SrpAttributes? attr;
|
||||
bool isEmailVerificationEnabled = true;
|
||||
try {
|
||||
attr = await UserService.instance.getSrpAttributes(_email!);
|
||||
isEmailVerificationEnabled = attr.isEmailMFAEnabled;
|
||||
} catch (e) {
|
||||
if (e is! SrpSetupNotCompleteError) {
|
||||
_logger.severe('Error getting SRP attributes', e);
|
||||
}
|
||||
}
|
||||
if (attr != null && !isEmailVerificationEnabled) {
|
||||
await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return LoginPasswordVerificationPage(
|
||||
srpAttributes: attr!,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await UserService.instance
|
||||
.sendOtt(context, _email!, isCreateAccountScreen: false);
|
||||
}
|
||||
FocusScope.of(context).unfocus();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_email = _config.getEmail();
|
||||
|
@ -60,36 +90,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||
isKeypadOpen: isKeypadOpen,
|
||||
isFormValid: _emailIsValid,
|
||||
buttonText: context.l10n.logInLabel,
|
||||
onPressedFunction: () async {
|
||||
await UserService.instance.setEmail(_email!);
|
||||
Configuration.instance.resetVolatilePassword();
|
||||
SrpAttributes? attr;
|
||||
bool isEmailVerificationEnabled = true;
|
||||
try {
|
||||
attr = await UserService.instance.getSrpAttributes(_email!);
|
||||
isEmailVerificationEnabled = attr.isEmailMFAEnabled;
|
||||
} catch (e) {
|
||||
if (e is! SrpSetupNotCompleteError) {
|
||||
_logger.severe('Error getting SRP attributes', e);
|
||||
}
|
||||
}
|
||||
if (attr != null && !isEmailVerificationEnabled) {
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return LoginPasswordVerificationPage(
|
||||
srpAttributes: attr!,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await UserService.instance
|
||||
.sendOtt(context, _email!, isCreateAccountScreen: false);
|
||||
}
|
||||
FocusScope.of(context).unfocus();
|
||||
},
|
||||
onPressedFunction: onPressed,
|
||||
),
|
||||
floatingActionButtonLocation: fabLocation(),
|
||||
floatingActionButtonAnimator: NoScalingAnimation(),
|
||||
|
@ -116,6 +117,8 @@ class _LoginPageState extends State<LoginPage> {
|
|||
padding: const EdgeInsets.fromLTRB(20, 24, 20, 0),
|
||||
child: TextFormField(
|
||||
autofillHints: const [AutofillHints.email],
|
||||
onFieldSubmitted:
|
||||
_emailIsValid ? (value) => onPressed() : null,
|
||||
decoration: InputDecoration(
|
||||
fillColor: _emailInputFieldColor,
|
||||
filled: true,
|
||||
|
@ -179,15 +182,10 @@ class _LoginPageState extends State<LoginPage> {
|
|||
tags: {
|
||||
'u-terms': StyledTextActionTag(
|
||||
(String? text, Map<String?, String?> attrs) =>
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return WebPage(
|
||||
context.l10n.termsOfServicesTitle,
|
||||
"https://ente.io/terms",
|
||||
);
|
||||
},
|
||||
),
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.l10n.termsOfServicesTitle,
|
||||
"https://ente.io/terms",
|
||||
),
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
|
@ -195,15 +193,10 @@ class _LoginPageState extends State<LoginPage> {
|
|||
),
|
||||
'u-policy': StyledTextActionTag(
|
||||
(String? text, Map<String?, String?> attrs) =>
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return WebPage(
|
||||
context.l10n.privacyPolicyTitle,
|
||||
"https://ente.io/privacy",
|
||||
);
|
||||
},
|
||||
),
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.l10n.privacyPolicyTitle,
|
||||
"https://ente.io/privacy",
|
||||
),
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import "package:dio/dio.dart";
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import "package:ente_auth/core/errors.dart";
|
||||
import "package:ente_auth/l10n/l10n.dart";
|
||||
import "package:ente_auth/models/api/user/srp.dart";
|
||||
import "package:ente_auth/services/user_service.dart";
|
||||
|
@ -9,6 +8,7 @@ import 'package:ente_auth/ui/common/dynamic_fab.dart';
|
|||
import "package:ente_auth/ui/components/buttons/button_widget.dart";
|
||||
import "package:ente_auth/utils/dialog_util.dart";
|
||||
import "package:ente_auth/utils/email_util.dart";
|
||||
import "package:ente_crypto_dart/ente_crypto_dart.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:logging/logging.dart";
|
||||
|
||||
|
@ -19,8 +19,7 @@ import "package:logging/logging.dart";
|
|||
// volatile password.
|
||||
class LoginPasswordVerificationPage extends StatefulWidget {
|
||||
final SrpAttributes srpAttributes;
|
||||
const LoginPasswordVerificationPage({Key? key, required this.srpAttributes})
|
||||
: super(key: key);
|
||||
const LoginPasswordVerificationPage({super.key, required this.srpAttributes});
|
||||
|
||||
@override
|
||||
State<LoginPasswordVerificationPage> createState() =>
|
||||
|
@ -36,6 +35,11 @@ class _LoginPasswordVerificationPageState
|
|||
bool _passwordInFocus = false;
|
||||
bool _passwordVisible = false;
|
||||
|
||||
Future<void> onPressed() async {
|
||||
FocusScope.of(context).unfocus();
|
||||
await verifyPassword(context, _passwordController.text);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
@ -77,10 +81,7 @@ class _LoginPasswordVerificationPageState
|
|||
isKeypadOpen: isKeypadOpen,
|
||||
isFormValid: _passwordController.text.isNotEmpty,
|
||||
buttonText: context.l10n.logInLabel,
|
||||
onPressedFunction: () async {
|
||||
FocusScope.of(context).unfocus();
|
||||
await verifyPassword(context, _passwordController.text);
|
||||
},
|
||||
onPressedFunction: onPressed,
|
||||
),
|
||||
floatingActionButtonLocation: fabLocation(),
|
||||
floatingActionButtonAnimator: NoScalingAnimation(),
|
||||
|
@ -101,7 +102,7 @@ class _LoginPasswordVerificationPageState
|
|||
password,
|
||||
dialog,
|
||||
);
|
||||
} on DioError catch (e, s) {
|
||||
} on DioException catch (e, s) {
|
||||
await dialog.hide();
|
||||
if (e.response != null && e.response!.statusCode == 401) {
|
||||
_logger.severe('server reject, failed verify SRP login', e, s);
|
||||
|
@ -112,7 +113,7 @@ class _LoginPasswordVerificationPageState
|
|||
);
|
||||
} else {
|
||||
_logger.severe('API failure during SRP login', e, s);
|
||||
if (e.type == DioErrorType.other) {
|
||||
if (e.type == DioExceptionType.unknown) {
|
||||
await _showContactSupportDialog(
|
||||
context,
|
||||
context.l10n.noInternetConnection,
|
||||
|
@ -229,6 +230,9 @@ class _LoginPasswordVerificationPageState
|
|||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 24, 20, 0),
|
||||
child: TextFormField(
|
||||
onFieldSubmitted: _passwordController.text.isNotEmpty
|
||||
? (_) => onPressed()
|
||||
: null,
|
||||
key: const ValueKey("passwordInputField"),
|
||||
autofillHints: const [AutofillHints.password],
|
||||
decoration: InputDecoration(
|
||||
|
|
|
@ -17,8 +17,8 @@ class OTTVerificationPage extends StatefulWidget {
|
|||
this.isChangeEmail = false,
|
||||
this.isCreateAccountScreen = false,
|
||||
this.isResetPasswordScreen = false,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<OTTVerificationPage> createState() => _OTTVerificationPageState();
|
||||
|
@ -27,6 +27,23 @@ class OTTVerificationPage extends StatefulWidget {
|
|||
class _OTTVerificationPageState extends State<OTTVerificationPage> {
|
||||
final _verificationCodeController = TextEditingController();
|
||||
|
||||
Future<void> onPressed() async {
|
||||
if (widget.isChangeEmail) {
|
||||
await UserService.instance.changeEmail(
|
||||
context,
|
||||
widget.email,
|
||||
_verificationCodeController.text,
|
||||
);
|
||||
} else {
|
||||
await UserService.instance.verifyEmail(
|
||||
context,
|
||||
_verificationCodeController.text,
|
||||
isResettingPasswordScreen: widget.isResetPasswordScreen,
|
||||
);
|
||||
}
|
||||
FocusScope.of(context).unfocus();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
|
@ -68,22 +85,9 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
|
|||
body: _getBody(),
|
||||
floatingActionButton: DynamicFAB(
|
||||
isKeypadOpen: isKeypadOpen,
|
||||
isFormValid: !(_verificationCodeController.text.isEmpty),
|
||||
isFormValid: _verificationCodeController.text.isNotEmpty,
|
||||
buttonText: l10n.verify,
|
||||
onPressedFunction: () {
|
||||
if (widget.isChangeEmail) {
|
||||
UserService.instance.changeEmail(
|
||||
context,
|
||||
widget.email,
|
||||
_verificationCodeController.text,
|
||||
);
|
||||
} else {
|
||||
UserService.instance
|
||||
.verifyEmail(context, _verificationCodeController.text,
|
||||
isResettingPasswordScreen: widget.isResetPasswordScreen,);
|
||||
}
|
||||
FocusScope.of(context).unfocus();
|
||||
},
|
||||
onPressedFunction: onPressed,
|
||||
),
|
||||
floatingActionButtonLocation: fabLocation(),
|
||||
floatingActionButtonAnimator: NoScalingAnimation(),
|
||||
|
@ -160,6 +164,9 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
|
|||
padding: const EdgeInsets.fromLTRB(20, 16, 20, 16),
|
||||
child: TextFormField(
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
onFieldSubmitted: _verificationCodeController.text.isNotEmpty
|
||||
? (_) => onPressed()
|
||||
: null,
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
hintText: l10n.tapToEnterCode,
|
||||
|
|
|
@ -4,11 +4,11 @@ import 'package:ente_auth/models/key_gen_result.dart';
|
|||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/ui/account/recovery_key_page.dart';
|
||||
import 'package:ente_auth/ui/common/dynamic_fab.dart';
|
||||
import 'package:ente_auth/ui/common/web_page.dart';
|
||||
import 'package:ente_auth/ui/components/models/button_type.dart';
|
||||
import 'package:ente_auth/ui/home_page.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/navigation_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
@ -25,7 +25,7 @@ enum PasswordEntryMode {
|
|||
class PasswordEntryPage extends StatefulWidget {
|
||||
final PasswordEntryMode mode;
|
||||
|
||||
const PasswordEntryPage({required this.mode, Key? key}) : super(key: key);
|
||||
const PasswordEntryPage({required this.mode, super.key});
|
||||
|
||||
@override
|
||||
State<PasswordEntryPage> createState() => _PasswordEntryPageState();
|
||||
|
@ -149,227 +149,239 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
|||
children: [
|
||||
Expanded(
|
||||
child: AutofillGroup(
|
||||
child: ListView(
|
||||
children: [
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
||||
child: Text(
|
||||
buttonTextAndHeading,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
child: FocusTraversalGroup(
|
||||
policy: OrderedTraversalPolicy(),
|
||||
child: ListView(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 30,
|
||||
horizontal: 20,
|
||||
),
|
||||
child: Text(
|
||||
buttonTextAndHeading,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(
|
||||
widget.mode == PasswordEntryMode.set
|
||||
? context.l10n.enterPasswordToEncrypt
|
||||
: context.l10n.enterNewPasswordToEncrypt,
|
||||
textAlign: TextAlign.start,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(fontSize: 14),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(
|
||||
widget.mode == PasswordEntryMode.set
|
||||
? context.l10n.enterPasswordToEncrypt
|
||||
: context.l10n.enterNewPasswordToEncrypt,
|
||||
textAlign: TextAlign.start,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(fontSize: 14),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Padding(padding: EdgeInsets.all(8)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: StyledText(
|
||||
text: context.l10n.passwordWarning,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(fontSize: 14),
|
||||
tags: {
|
||||
'underline': StyledTextTag(
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
const Padding(padding: EdgeInsets.all(8)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: StyledText(
|
||||
text: context.l10n.passwordWarning,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(fontSize: 14),
|
||||
tags: {
|
||||
'underline': StyledTextTag(
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
const Padding(padding: EdgeInsets.all(12)),
|
||||
Visibility(
|
||||
// hidden textForm for suggesting auto-fill service for saving
|
||||
// password
|
||||
visible: false,
|
||||
child: TextFormField(
|
||||
autofillHints: const [
|
||||
AutofillHints.email,
|
||||
],
|
||||
autocorrect: false,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
initialValue: email,
|
||||
textInputAction: TextInputAction.next,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
|
||||
child: TextFormField(
|
||||
autofillHints: const [AutofillHints.newPassword],
|
||||
onFieldSubmitted: (_) {
|
||||
do {
|
||||
FocusScope.of(context).nextFocus();
|
||||
} while (FocusScope.of(context).focusedChild!.context ==
|
||||
null);
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
fillColor:
|
||||
_isPasswordValid ? _validFieldValueColor : null,
|
||||
filled: true,
|
||||
hintText: context.l10n.password,
|
||||
contentPadding: const EdgeInsets.all(20),
|
||||
border: UnderlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
suffixIcon: _password1InFocus
|
||||
? IconButton(
|
||||
icon: Icon(
|
||||
_password1Visible
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off,
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
size: 20,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_password1Visible = !_password1Visible;
|
||||
});
|
||||
},
|
||||
)
|
||||
: _isPasswordValid
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: Theme.of(context)
|
||||
.inputDecorationTheme
|
||||
.focusedBorder!
|
||||
.borderSide
|
||||
.color,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
const Padding(padding: EdgeInsets.all(12)),
|
||||
Visibility(
|
||||
// hidden textForm for suggesting auto-fill service for saving
|
||||
// password
|
||||
visible: false,
|
||||
child: TextFormField(
|
||||
autofillHints: const [
|
||||
AutofillHints.email,
|
||||
],
|
||||
autocorrect: false,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
initialValue: email,
|
||||
textInputAction: TextInputAction.next,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
|
||||
child: TextFormField(
|
||||
autofillHints: const [AutofillHints.newPassword],
|
||||
decoration: InputDecoration(
|
||||
fillColor:
|
||||
_isPasswordValid ? _validFieldValueColor : null,
|
||||
filled: true,
|
||||
hintText: context.l10n.password,
|
||||
contentPadding: const EdgeInsets.all(20),
|
||||
border: UnderlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
suffixIcon: _password1InFocus
|
||||
? IconButton(
|
||||
icon: Icon(
|
||||
_password1Visible
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off,
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
size: 20,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_password1Visible = !_password1Visible;
|
||||
});
|
||||
},
|
||||
)
|
||||
: _isPasswordValid
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: Theme.of(context)
|
||||
.inputDecorationTheme
|
||||
.focusedBorder!
|
||||
.borderSide
|
||||
.color,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
obscureText: !_password1Visible,
|
||||
controller: _passwordController1,
|
||||
autofocus: false,
|
||||
autocorrect: false,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
onChanged: (password) {
|
||||
setState(() {
|
||||
_passwordInInputBox = password;
|
||||
_passwordStrength = estimatePasswordStrength(password);
|
||||
_isPasswordValid =
|
||||
_passwordStrength >= kMildPasswordStrengthThreshold;
|
||||
_passwordsMatch = _passwordInInputBox ==
|
||||
_passwordInInputConfirmationBox;
|
||||
});
|
||||
},
|
||||
textInputAction: TextInputAction.next,
|
||||
focusNode: _password1FocusNode,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
|
||||
child: TextFormField(
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
controller: _passwordController2,
|
||||
obscureText: !_password2Visible,
|
||||
autofillHints: const [AutofillHints.newPassword],
|
||||
onEditingComplete: () => TextInput.finishAutofillContext(),
|
||||
decoration: InputDecoration(
|
||||
fillColor: _passwordsMatch ? _validFieldValueColor : null,
|
||||
filled: true,
|
||||
hintText: context.l10n.confirmPassword,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 20,
|
||||
),
|
||||
suffixIcon: _password2InFocus
|
||||
? IconButton(
|
||||
icon: Icon(
|
||||
_password2Visible
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off,
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
size: 20,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_password2Visible = !_password2Visible;
|
||||
});
|
||||
},
|
||||
)
|
||||
: _passwordsMatch
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: Theme.of(context)
|
||||
.inputDecorationTheme
|
||||
.focusedBorder!
|
||||
.borderSide
|
||||
.color,
|
||||
)
|
||||
: null,
|
||||
border: UnderlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
),
|
||||
focusNode: _password2FocusNode,
|
||||
onChanged: (cnfPassword) {
|
||||
setState(() {
|
||||
_passwordInInputConfirmationBox = cnfPassword;
|
||||
if (_passwordInInputBox != '') {
|
||||
obscureText: !_password1Visible,
|
||||
controller: _passwordController1,
|
||||
autofocus: false,
|
||||
autocorrect: false,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
onChanged: (password) {
|
||||
setState(() {
|
||||
_passwordInInputBox = password;
|
||||
_passwordStrength =
|
||||
estimatePasswordStrength(password);
|
||||
_isPasswordValid = _passwordStrength >=
|
||||
kMildPasswordStrengthThreshold;
|
||||
_passwordsMatch = _passwordInInputBox ==
|
||||
_passwordInInputConfirmationBox;
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
Opacity(
|
||||
opacity:
|
||||
(_passwordInInputBox != '') && _password1InFocus ? 1 : 0,
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
|
||||
child: Text(
|
||||
context.l10n.passwordStrength(passwordStrengthText),
|
||||
style: TextStyle(
|
||||
color: passwordStrengthColor,
|
||||
),
|
||||
});
|
||||
},
|
||||
textInputAction: TextInputAction.next,
|
||||
focusNode: _password1FocusNode,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return WebPage(
|
||||
context.l10n.howItWorks,
|
||||
"https://ente.io/architecture",
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: context.l10n.howItWorks,
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
|
||||
child: TextFormField(
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
controller: _passwordController2,
|
||||
obscureText: !_password2Visible,
|
||||
autofillHints: const [AutofillHints.newPassword],
|
||||
onEditingComplete: () =>
|
||||
TextInput.finishAutofillContext(),
|
||||
decoration: InputDecoration(
|
||||
fillColor:
|
||||
_passwordsMatch ? _validFieldValueColor : null,
|
||||
filled: true,
|
||||
hintText: context.l10n.confirmPassword,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 20,
|
||||
),
|
||||
suffixIcon: _password2InFocus
|
||||
? IconButton(
|
||||
icon: Icon(
|
||||
_password2Visible
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off,
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
size: 20,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_password2Visible = !_password2Visible;
|
||||
});
|
||||
},
|
||||
)
|
||||
: _passwordsMatch
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: Theme.of(context)
|
||||
.inputDecorationTheme
|
||||
.focusedBorder!
|
||||
.borderSide
|
||||
.color,
|
||||
)
|
||||
: null,
|
||||
border: UnderlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
),
|
||||
focusNode: _password2FocusNode,
|
||||
onChanged: (cnfPassword) {
|
||||
setState(() {
|
||||
_passwordInInputConfirmationBox = cnfPassword;
|
||||
if (_passwordInInputBox != '') {
|
||||
_passwordsMatch = _passwordInInputBox ==
|
||||
_passwordInInputConfirmationBox;
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
Opacity(
|
||||
opacity: (_passwordInInputBox != '') && _password1InFocus
|
||||
? 1
|
||||
: 0,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 8,
|
||||
),
|
||||
child: Text(
|
||||
context.l10n.passwordStrength(passwordStrengthText),
|
||||
style: TextStyle(
|
||||
color: passwordStrengthColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Padding(padding: EdgeInsets.all(20)),
|
||||
],
|
||||
const SizedBox(height: 8),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.l10n.howItWorks,
|
||||
"https://ente.io/architecture",
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: context.l10n.howItWorks,
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Padding(padding: EdgeInsets.all(20)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -463,6 +475,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
|||
showGenericErrorDialog(context: context);
|
||||
}
|
||||
}
|
||||
|
||||
// ignore: unawaited_futures
|
||||
routeToPage(
|
||||
context,
|
||||
|
|
|
@ -9,14 +9,14 @@ import 'package:ente_auth/ui/account/recovery_page.dart';
|
|||
import 'package:ente_auth/ui/common/dynamic_fab.dart';
|
||||
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
|
||||
import 'package:ente_auth/ui/home_page.dart';
|
||||
import 'package:ente_auth/utils/crypto_util.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/email_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class PasswordReentryPage extends StatefulWidget {
|
||||
const PasswordReentryPage({Key? key}) : super(key: key);
|
||||
const PasswordReentryPage({super.key});
|
||||
|
||||
@override
|
||||
State<PasswordReentryPage> createState() => _PasswordReentryPageState();
|
||||
|
@ -261,8 +261,8 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
|
|||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.spaceBetween,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
|
@ -275,13 +275,17 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
|
|||
),
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
context.l10n.forgotPassword,
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
context.l10n.forgotPassword,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
|
@ -297,13 +301,17 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
|
|||
Navigator.of(context)
|
||||
.popUntil((route) => route.isFirst);
|
||||
},
|
||||
child: Text(
|
||||
context.l10n.changeEmail,
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
context.l10n.changeEmail,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io' as io;
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
|
@ -7,7 +8,10 @@ import 'package:ente_auth/core/constants.dart';
|
|||
import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/ui/common/gradient_button.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_auth/utils/share_utils.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:file_saver/file_saver.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
@ -27,7 +31,7 @@ class RecoveryKeyPage extends StatefulWidget {
|
|||
const RecoveryKeyPage(
|
||||
this.recoveryKey,
|
||||
this.doneText, {
|
||||
Key? key,
|
||||
super.key,
|
||||
this.showAppBar,
|
||||
this.onDone,
|
||||
this.isDismissible,
|
||||
|
@ -35,7 +39,7 @@ class RecoveryKeyPage extends StatefulWidget {
|
|||
this.text,
|
||||
this.subText,
|
||||
this.showProgressBar = false,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
State<RecoveryKeyPage> createState() => _RecoveryKeyPageState();
|
||||
|
@ -44,7 +48,7 @@ class RecoveryKeyPage extends StatefulWidget {
|
|||
class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
bool _hasTriedToSave = false;
|
||||
final _recoveryKeyFile = io.File(
|
||||
Configuration.instance.getTempDirectory() + "ente-recovery-key.txt",
|
||||
"${Configuration.instance.getTempDirectory()}ente-recovery-key.txt",
|
||||
);
|
||||
|
||||
@override
|
||||
|
@ -61,6 +65,21 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
|||
? 32
|
||||
: 120;
|
||||
|
||||
Future<void> copy() async {
|
||||
await Clipboard.setData(
|
||||
ClipboardData(
|
||||
text: recoveryKey,
|
||||
),
|
||||
);
|
||||
showShortToast(
|
||||
context,
|
||||
context.l10n.recoveryKeyCopiedToClipboard,
|
||||
);
|
||||
setState(() {
|
||||
_hasTriedToSave = true;
|
||||
});
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: widget.showProgressBar
|
||||
? AppBar(
|
||||
|
@ -113,62 +132,73 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
|||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const Padding(padding: EdgeInsets.only(top: 24)),
|
||||
DottedBorder(
|
||||
color: const Color.fromARGB(255, 105, 17, 127),
|
||||
//color of dotted/dash line
|
||||
strokeWidth: 1,
|
||||
//thickness of dash/dots
|
||||
dashPattern: const [6, 6],
|
||||
radius: const Radius.circular(8),
|
||||
//dash patterns, 10 is dash width, 6 is space width
|
||||
child: SizedBox(
|
||||
//inner container
|
||||
// height: 120, //height of inner container
|
||||
width: double
|
||||
.infinity, //width to 100% match to parent container.
|
||||
// ignore: prefer_const_literals_to_create_immutables
|
||||
child: Column(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
await Clipboard.setData(
|
||||
ClipboardData(text: recoveryKey),
|
||||
);
|
||||
showShortToast(
|
||||
context,
|
||||
context.l10n.recoveryKeyCopiedToClipboard,
|
||||
);
|
||||
setState(() {
|
||||
_hasTriedToSave = true;
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: const Color.fromRGBO(
|
||||
49,
|
||||
155,
|
||||
86,
|
||||
.2,
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(1),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Color(0x8E9610D6),
|
||||
Color(0x8E9F4FC6),
|
||||
],
|
||||
stops: [0.0, 0.9753],
|
||||
),
|
||||
),
|
||||
child: DottedBorder(
|
||||
padding: EdgeInsets.zero,
|
||||
borderType: BorderType.RRect,
|
||||
strokeWidth: 1,
|
||||
color: const Color(0xFF6B6B6B),
|
||||
dashPattern: const [6, 6],
|
||||
radius: const Radius.circular(8),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: Stack(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Builder(
|
||||
builder: (context) {
|
||||
final content = Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
width: double.infinity,
|
||||
child: Text(
|
||||
recoveryKey,
|
||||
textAlign: TextAlign.justify,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge,
|
||||
),
|
||||
);
|
||||
|
||||
if (PlatformUtil.isMobile()) {
|
||||
return GestureDetector(
|
||||
onTap: () async => await copy(),
|
||||
child: content,
|
||||
);
|
||||
} else {
|
||||
return SelectableRegion(
|
||||
focusNode: FocusNode(),
|
||||
selectionControls:
|
||||
PlatformUtil.selectionControls,
|
||||
child: content,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(2),
|
||||
),
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.recoveryKeyBoxColor,
|
||||
),
|
||||
padding: const EdgeInsets.all(20),
|
||||
width: double.infinity,
|
||||
child: Text(
|
||||
recoveryKey,
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyLarge,
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
right: 0,
|
||||
top: 0,
|
||||
child: PlatformCopy(
|
||||
onPressed: copy,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -193,7 +223,7 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
|||
),
|
||||
),
|
||||
],
|
||||
), // columnEnds
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -207,12 +237,15 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
|||
final List<Widget> childrens = [];
|
||||
if (!_hasTriedToSave) {
|
||||
childrens.add(
|
||||
ElevatedButton(
|
||||
style: Theme.of(context).colorScheme.optionalActionButtonStyle,
|
||||
onPressed: () async {
|
||||
await _saveKeys();
|
||||
},
|
||||
child: Text(context.l10n.doThisLater),
|
||||
SizedBox(
|
||||
height: 56,
|
||||
child: ElevatedButton(
|
||||
style: Theme.of(context).colorScheme.optionalActionButtonStyle,
|
||||
onPressed: () async {
|
||||
await _saveKeys();
|
||||
},
|
||||
child: Text(context.l10n.doThisLater),
|
||||
),
|
||||
),
|
||||
);
|
||||
childrens.add(const SizedBox(height: 10));
|
||||
|
@ -221,19 +254,32 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
|||
childrens.add(
|
||||
GradientButton(
|
||||
onTap: () async {
|
||||
await _shareRecoveryKey(recoveryKey);
|
||||
await shareDialog(
|
||||
context,
|
||||
context.l10n.recoveryKey,
|
||||
saveAction: () async {
|
||||
await _saveRecoveryKey(recoveryKey);
|
||||
},
|
||||
sendAction: () async {
|
||||
await _shareRecoveryKey(recoveryKey);
|
||||
},
|
||||
);
|
||||
},
|
||||
text: context.l10n.saveKey,
|
||||
),
|
||||
);
|
||||
|
||||
if (_hasTriedToSave) {
|
||||
childrens.add(const SizedBox(height: 10));
|
||||
childrens.add(
|
||||
ElevatedButton(
|
||||
child: Text(widget.doneText),
|
||||
onPressed: () async {
|
||||
await _saveKeys();
|
||||
},
|
||||
SizedBox(
|
||||
height: 56,
|
||||
child: ElevatedButton(
|
||||
child: Text(widget.doneText),
|
||||
onPressed: () async {
|
||||
await _saveKeys();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -241,11 +287,34 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
|||
return childrens;
|
||||
}
|
||||
|
||||
Future _saveRecoveryKey(String recoveryKey) async {
|
||||
final bytes = utf8.encode(recoveryKey);
|
||||
final time = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
await PlatformUtil.shareFile(
|
||||
"ente_recovery_key_$time",
|
||||
"txt",
|
||||
bytes,
|
||||
MimeType.text,
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
showToast(
|
||||
context,
|
||||
context.l10n.recoveryKeySaved,
|
||||
);
|
||||
setState(() {
|
||||
_hasTriedToSave = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future _shareRecoveryKey(String recoveryKey) async {
|
||||
if (_recoveryKeyFile.existsSync()) {
|
||||
await _recoveryKeyFile.delete();
|
||||
}
|
||||
_recoveryKeyFile.writeAsStringSync(recoveryKey);
|
||||
// ignore: deprecated_member_use
|
||||
await Share.shareFiles([_recoveryKeyFile.path]);
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
if (mounted) {
|
||||
|
@ -264,3 +333,24 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
|||
widget.onDone!();
|
||||
}
|
||||
}
|
||||
|
||||
class PlatformCopy extends StatelessWidget {
|
||||
const PlatformCopy({
|
||||
super.key,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
final void Function() onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconButton(
|
||||
onPressed: () => onPressed(),
|
||||
visualDensity: VisualDensity.compact,
|
||||
icon: const Icon(
|
||||
Icons.copy,
|
||||
size: 16,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/ui/account/password_entry_page.dart';
|
||||
import 'package:ente_auth/ui/common/dynamic_fab.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
|
@ -10,7 +7,7 @@ import 'package:ente_auth/utils/toast_util.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class RecoveryPage extends StatefulWidget {
|
||||
const RecoveryPage({Key? key}) : super(key: key);
|
||||
const RecoveryPage({super.key});
|
||||
|
||||
@override
|
||||
State<RecoveryPage> createState() => _RecoveryPageState();
|
||||
|
@ -19,6 +16,36 @@ class RecoveryPage extends StatefulWidget {
|
|||
class _RecoveryPageState extends State<RecoveryPage> {
|
||||
final _recoveryKey = TextEditingController();
|
||||
|
||||
Future<void> onPressed() async {
|
||||
FocusScope.of(context).unfocus();
|
||||
final dialog = createProgressDialog(context, "Decrypting...");
|
||||
await dialog.show();
|
||||
try {
|
||||
await Configuration.instance.recover(_recoveryKey.text.trim());
|
||||
await dialog.hide();
|
||||
showToast(context, "Recovery successful!");
|
||||
await Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return const PopScope(
|
||||
canPop: false,
|
||||
child: PasswordEntryPage(
|
||||
mode: PasswordEntryMode.reset,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
await dialog.hide();
|
||||
String errMessage = 'The recovery key you entered is incorrect';
|
||||
if (e is AssertionError) {
|
||||
errMessage = '$errMessage : ${e.message}';
|
||||
}
|
||||
await showErrorDialog(context, "Incorrect recovery key", errMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100;
|
||||
|
@ -46,37 +73,7 @@ class _RecoveryPageState extends State<RecoveryPage> {
|
|||
isKeypadOpen: isKeypadOpen,
|
||||
isFormValid: _recoveryKey.text.isNotEmpty,
|
||||
buttonText: 'Recover',
|
||||
onPressedFunction: () async {
|
||||
FocusScope.of(context).unfocus();
|
||||
final dialog = createProgressDialog(context, "Decrypting...");
|
||||
await dialog.show();
|
||||
try {
|
||||
await Configuration.instance.recover(_recoveryKey.text.trim());
|
||||
await dialog.hide();
|
||||
showToast(context, "Recovery successful!");
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async => false,
|
||||
child: const PasswordEntryPage(
|
||||
mode: PasswordEntryMode.reset,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
await dialog.hide();
|
||||
String errMessage = 'The recovery key you entered is incorrect';
|
||||
if (e is AssertionError) {
|
||||
errMessage = '$errMessage : ${e.message}';
|
||||
}
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(context, "Incorrect recovery key", errMessage);
|
||||
}
|
||||
},
|
||||
onPressedFunction: onPressed,
|
||||
),
|
||||
floatingActionButtonLocation: fabLocation(),
|
||||
floatingActionButtonAnimator: NoScalingAnimation(),
|
||||
|
@ -89,7 +86,7 @@ class _RecoveryPageState extends State<RecoveryPage> {
|
|||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
||||
child: Text(
|
||||
'Forgot password',
|
||||
context.l10n.forgotPassword,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
),
|
||||
|
@ -140,12 +137,14 @@ class _RecoveryPageState extends State<RecoveryPage> {
|
|||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Center(
|
||||
child: Text(
|
||||
"No recovery key?",
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
context.l10n.noRecoveryKeyTitle,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -5,10 +5,9 @@ import 'package:ente_auth/core/configuration.dart';
|
|||
import "package:ente_auth/l10n/l10n.dart";
|
||||
import "package:ente_auth/theme/ente_theme.dart";
|
||||
import 'package:ente_auth/ui/common/dynamic_fab.dart';
|
||||
import "package:ente_auth/utils/crypto_util.dart";
|
||||
import "package:ente_auth/utils/dialog_util.dart";
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:flutter_sodium/flutter_sodium.dart";
|
||||
import "package:logging/logging.dart";
|
||||
|
||||
typedef OnPasswordVerifiedFn = Future<void> Function(Uint8List bytes);
|
||||
|
@ -17,8 +16,11 @@ class RequestPasswordVerificationPage extends StatefulWidget {
|
|||
final OnPasswordVerifiedFn onPasswordVerified;
|
||||
final Function? onPasswordError;
|
||||
|
||||
const RequestPasswordVerificationPage(
|
||||
{super.key, required this.onPasswordVerified, this.onPasswordError,});
|
||||
const RequestPasswordVerificationPage({
|
||||
super.key,
|
||||
required this.onPasswordVerified,
|
||||
this.onPasswordError,
|
||||
});
|
||||
|
||||
@override
|
||||
State<RequestPasswordVerificationPage> createState() =>
|
||||
|
@ -82,15 +84,15 @@ class _RequestPasswordVerificationPageState
|
|||
try {
|
||||
final attributes = Configuration.instance.getKeyAttributes()!;
|
||||
final Uint8List keyEncryptionKey = await CryptoUtil.deriveKey(
|
||||
utf8.encode(_passwordController.text) as Uint8List,
|
||||
Sodium.base642bin(attributes.kekSalt),
|
||||
utf8.encode(_passwordController.text),
|
||||
CryptoUtil.base642bin(attributes.kekSalt),
|
||||
attributes.memLimit,
|
||||
attributes.opsLimit,
|
||||
);
|
||||
CryptoUtil.decryptSync(
|
||||
Sodium.base642bin(attributes.encryptedKey),
|
||||
CryptoUtil.base642bin(attributes.encryptedKey),
|
||||
keyEncryptionKey,
|
||||
Sodium.base642bin(attributes.keyDecryptionNonce),
|
||||
CryptoUtil.base642bin(attributes.keyDecryptionNonce),
|
||||
);
|
||||
await dialog.show();
|
||||
// pop
|
||||
|
|
|
@ -11,7 +11,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:logging/logging.dart';
|
||||
|
||||
class SessionsPage extends StatefulWidget {
|
||||
const SessionsPage({Key? key}) : super(key: key);
|
||||
const SessionsPage({super.key});
|
||||
|
||||
@override
|
||||
State<SessionsPage> createState() => _SessionsPageState();
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import 'dart:ui';
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
|
@ -12,12 +10,13 @@ import 'package:ente_auth/ui/common/gradient_button.dart';
|
|||
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/navigation_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class VerifyRecoveryPage extends StatefulWidget {
|
||||
const VerifyRecoveryPage({Key? key}) : super(key: key);
|
||||
const VerifyRecoveryPage({super.key});
|
||||
|
||||
@override
|
||||
State<VerifyRecoveryPage> createState() => _VerifyRecoveryPageState();
|
||||
|
@ -34,14 +33,14 @@ class _VerifyRecoveryPageState extends State<VerifyRecoveryPage> {
|
|||
try {
|
||||
final String inputKey = _recoveryKey.text.trim();
|
||||
final String recoveryKey =
|
||||
Sodium.bin2hex(Configuration.instance.getRecoveryKey());
|
||||
CryptoUtil.bin2hex(Configuration.instance.getRecoveryKey());
|
||||
final String recoveryKeyWords = bip39.entropyToMnemonic(recoveryKey);
|
||||
if (inputKey == recoveryKey || inputKey == recoveryKeyWords) {
|
||||
try {
|
||||
await UserRemoteFlagService.instance.markRecoveryVerificationAsDone();
|
||||
} catch (e) {
|
||||
await dialog.hide();
|
||||
if (e is DioError && e.type == DioErrorType.other) {
|
||||
if (e is DioException && e.type == DioExceptionType.unknown) {
|
||||
await showErrorDialog(
|
||||
context,
|
||||
"No internet connection",
|
||||
|
@ -88,12 +87,14 @@ class _VerifyRecoveryPageState extends State<VerifyRecoveryPage> {
|
|||
context,
|
||||
"Please authenticate to view your recovery key",
|
||||
);
|
||||
await PlatformUtil.refocusWindows();
|
||||
|
||||
if (hasAuthenticated) {
|
||||
String recoveryKey;
|
||||
try {
|
||||
recoveryKey = Sodium.bin2hex(Configuration.instance.getRecoveryKey());
|
||||
// ignore: unawaited_futures
|
||||
routeToPage(
|
||||
recoveryKey =
|
||||
CryptoUtil.bin2hex(Configuration.instance.getRecoveryKey());
|
||||
await routeToPage(
|
||||
context,
|
||||
RecoveryKeyPage(
|
||||
recoveryKey,
|
||||
|
|
|
@ -6,12 +6,12 @@ class CodeTimerProgress extends StatefulWidget {
|
|||
final int period;
|
||||
|
||||
CodeTimerProgress({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.period,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
_CodeTimerProgressState createState() => _CodeTimerProgressState();
|
||||
State createState() => _CodeTimerProgressState();
|
||||
}
|
||||
|
||||
class _CodeTimerProgressState extends State<CodeTimerProgress>
|
||||
|
|
|
@ -14,9 +14,11 @@ import 'package:ente_auth/store/code_store.dart';
|
|||
import 'package:ente_auth/ui/code_timer_progress.dart';
|
||||
import 'package:ente_auth/ui/utils/icon_utils.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:ente_auth/utils/totp_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_context_menu/flutter_context_menu.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:move_to_background/move_to_background.dart';
|
||||
|
@ -24,7 +26,7 @@ import 'package:move_to_background/move_to_background.dart';
|
|||
class CodeWidget extends StatefulWidget {
|
||||
final Code code;
|
||||
|
||||
const CodeWidget(this.code, {Key? key}) : super(key: key);
|
||||
const CodeWidget(this.code, {super.key});
|
||||
|
||||
@override
|
||||
State<CodeWidget> createState() => _CodeWidgetState();
|
||||
|
@ -84,83 +86,121 @@ class _CodeWidgetState extends State<CodeWidget> {
|
|||
final l10n = context.l10n;
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(left: 16, right: 16, bottom: 8, top: 8),
|
||||
child: Slidable(
|
||||
key: ValueKey(widget.code.hashCode),
|
||||
endActionPane: ActionPane(
|
||||
extentRatio: 0.60,
|
||||
motion: const ScrollMotion(),
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
SlidableAction(
|
||||
onPressed: _onShowQrPressed,
|
||||
backgroundColor: Colors.grey.withOpacity(0.1),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12.0)),
|
||||
foregroundColor:
|
||||
Theme.of(context).colorScheme.inverseBackgroundColor,
|
||||
icon: Icons.qr_code_2_outlined,
|
||||
label: "QR",
|
||||
padding: const EdgeInsets.only(left: 4, right: 0),
|
||||
spacing: 8,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
SlidableAction(
|
||||
onPressed: _onEditPressed,
|
||||
backgroundColor: Colors.grey.withOpacity(0.1),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12.0)),
|
||||
foregroundColor:
|
||||
Theme.of(context).colorScheme.inverseBackgroundColor,
|
||||
icon: Icons.edit_outlined,
|
||||
label: l10n.edit,
|
||||
padding: const EdgeInsets.only(left: 4, right: 0),
|
||||
spacing: 8,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
SlidableAction(
|
||||
onPressed: _onDeletePressed,
|
||||
backgroundColor: Colors.grey.withOpacity(0.1),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12.0)),
|
||||
foregroundColor: const Color(0xFFFE4A49),
|
||||
icon: Icons.delete,
|
||||
label: l10n.delete,
|
||||
padding: const EdgeInsets.only(left: 0, right: 0),
|
||||
spacing: 8,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.codeCardBackgroundColor,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
customBorder: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
onTap: () {
|
||||
_copyCurrentOTPToClipboard();
|
||||
},
|
||||
onDoubleTap: isMaskingEnabled
|
||||
? () {
|
||||
setState(
|
||||
() {
|
||||
_hideCode = !_hideCode;
|
||||
},
|
||||
);
|
||||
}
|
||||
: null,
|
||||
onLongPress: () {
|
||||
_copyCurrentOTPToClipboard();
|
||||
},
|
||||
child: _getCardContents(l10n),
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
if (PlatformUtil.isDesktop()) {
|
||||
return ContextMenuRegion(
|
||||
contextMenu: ContextMenu(
|
||||
entries: <ContextMenuEntry>[
|
||||
MenuItem(
|
||||
label: 'QR',
|
||||
icon: Icons.qr_code_2_outlined,
|
||||
onSelected: () => _onShowQrPressed(null),
|
||||
),
|
||||
MenuItem(
|
||||
label: l10n.edit,
|
||||
icon: Icons.edit,
|
||||
onSelected: () => _onEditPressed(null),
|
||||
),
|
||||
const MenuDivider(),
|
||||
MenuItem(
|
||||
label: l10n.delete,
|
||||
value: "Delete",
|
||||
icon: Icons.delete,
|
||||
onSelected: () => _onDeletePressed(null),
|
||||
),
|
||||
],
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
),
|
||||
child: _clippedCard(l10n),
|
||||
);
|
||||
}
|
||||
|
||||
return Slidable(
|
||||
key: ValueKey(widget.code.hashCode),
|
||||
endActionPane: ActionPane(
|
||||
extentRatio: 0.60,
|
||||
motion: const ScrollMotion(),
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
SlidableAction(
|
||||
onPressed: _onShowQrPressed,
|
||||
backgroundColor: Colors.grey.withOpacity(0.1),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12.0)),
|
||||
foregroundColor:
|
||||
Theme.of(context).colorScheme.inverseBackgroundColor,
|
||||
icon: Icons.qr_code_2_outlined,
|
||||
label: "QR",
|
||||
padding: const EdgeInsets.only(left: 4, right: 0),
|
||||
spacing: 8,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
SlidableAction(
|
||||
onPressed: _onEditPressed,
|
||||
backgroundColor: Colors.grey.withOpacity(0.1),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12.0)),
|
||||
foregroundColor:
|
||||
Theme.of(context).colorScheme.inverseBackgroundColor,
|
||||
icon: Icons.edit_outlined,
|
||||
label: l10n.edit,
|
||||
padding: const EdgeInsets.only(left: 4, right: 0),
|
||||
spacing: 8,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
SlidableAction(
|
||||
onPressed: _onDeletePressed,
|
||||
backgroundColor: Colors.grey.withOpacity(0.1),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12.0)),
|
||||
foregroundColor: const Color(0xFFFE4A49),
|
||||
icon: Icons.delete,
|
||||
label: l10n.delete,
|
||||
padding: const EdgeInsets.only(left: 0, right: 0),
|
||||
spacing: 8,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Builder(
|
||||
builder: (context) => _clippedCard(l10n),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _clippedCard(AppLocalizations l10n) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.codeCardBackgroundColor,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
customBorder: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
onTap: () {
|
||||
_copyCurrentOTPToClipboard();
|
||||
},
|
||||
onDoubleTap: isMaskingEnabled
|
||||
? () {
|
||||
setState(
|
||||
() {
|
||||
_hideCode = !_hideCode;
|
||||
},
|
||||
);
|
||||
}
|
||||
: null,
|
||||
onLongPress: () {
|
||||
_copyCurrentOTPToClipboard();
|
||||
},
|
||||
child: _getCardContents(l10n),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -373,9 +413,10 @@ class _CodeWidgetState extends State<CodeWidget> {
|
|||
}
|
||||
|
||||
Future<void> _onEditPressed(_) async {
|
||||
bool _isAuthSuccessful = await LocalAuthenticationService.instance
|
||||
bool isAuthSuccessful = await LocalAuthenticationService.instance
|
||||
.requestLocalAuthentication(context, context.l10n.editCodeAuthMessage);
|
||||
if (!_isAuthSuccessful) {
|
||||
await PlatformUtil.refocusWindows();
|
||||
if (!isAuthSuccessful) {
|
||||
return;
|
||||
}
|
||||
final Code? code = await Navigator.of(context).push(
|
||||
|
@ -391,9 +432,10 @@ class _CodeWidgetState extends State<CodeWidget> {
|
|||
}
|
||||
|
||||
Future<void> _onShowQrPressed(_) async {
|
||||
bool _isAuthSuccessful = await LocalAuthenticationService.instance
|
||||
bool isAuthSuccessful = await LocalAuthenticationService.instance
|
||||
.requestLocalAuthentication(context, context.l10n.showQRAuthMessage);
|
||||
if (!_isAuthSuccessful) {
|
||||
await PlatformUtil.refocusWindows();
|
||||
if (!isAuthSuccessful) {
|
||||
return;
|
||||
}
|
||||
// ignore: unused_local_variable
|
||||
|
@ -407,14 +449,15 @@ class _CodeWidgetState extends State<CodeWidget> {
|
|||
}
|
||||
|
||||
void _onDeletePressed(_) async {
|
||||
bool _isAuthSuccessful =
|
||||
bool isAuthSuccessful =
|
||||
await LocalAuthenticationService.instance.requestLocalAuthentication(
|
||||
context,
|
||||
context.l10n.deleteCodeAuthMessage,
|
||||
);
|
||||
if (!_isAuthSuccessful) {
|
||||
if (!isAuthSuccessful) {
|
||||
return;
|
||||
}
|
||||
FocusScope.of(context).requestFocus();
|
||||
final l10n = context.l10n;
|
||||
await showChoiceActionSheet(
|
||||
context,
|
||||
|
@ -451,7 +494,7 @@ class _CodeWidgetState extends State<CodeWidget> {
|
|||
code = code.replaceAll(RegExp(r'\d'), '•');
|
||||
}
|
||||
if (code.length == 6) {
|
||||
return code.substring(0, 3) + " " + code.substring(3, 6);
|
||||
return "${code.substring(0, 3)} ${code.substring(3, 6)}";
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
|
|
@ -5,8 +5,7 @@ import 'package:flutter/material.dart';
|
|||
class BottomShadowWidget extends StatelessWidget {
|
||||
final double offsetDy;
|
||||
final Color? shadowColor;
|
||||
const BottomShadowWidget({this.offsetDy = 28, this.shadowColor, Key? key})
|
||||
: super(key: key);
|
||||
const BottomShadowWidget({this.offsetDy = 28, this.shadowColor, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DividerWithPadding extends StatelessWidget {
|
||||
final double left, top, right, bottom, thinckness;
|
||||
const DividerWithPadding({
|
||||
Key? key,
|
||||
super.key,
|
||||
this.left = 0,
|
||||
this.top = 0,
|
||||
this.right = 0,
|
||||
this.bottom = 0,
|
||||
this.thinckness = 0.5,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|