Compare commits
359 Commits
auth-v2.0.
...
update_doc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6146350aae | ||
|
|
f8d8550b10 | ||
|
|
bc4fa44edd | ||
|
|
92de88e778 | ||
|
|
7814cbcc91 | ||
|
|
518b947808 | ||
|
|
077ba04664 | ||
|
|
e42422407c | ||
|
|
2711a227fc | ||
|
|
4325de6fde | ||
|
|
c7d7d436c3 | ||
|
|
f7077c2b11 | ||
|
|
8f525cb88d | ||
|
|
be3b4dc7ba | ||
|
|
0c1c0ad400 | ||
|
|
773f4cdca2 | ||
|
|
96bb79b9e9 | ||
|
|
d5164693ff | ||
|
|
26b162c8dc | ||
|
|
297148dc67 | ||
|
|
46522c329c | ||
|
|
8358eef34e | ||
|
|
4326409046 | ||
|
|
687d575bf4 | ||
|
|
0678e3129a | ||
|
|
b164b0df21 | ||
|
|
0d38346722 | ||
|
|
51d3238a52 | ||
|
|
ddd89aa1d1 | ||
|
|
f21a627a71 | ||
|
|
063e980280 | ||
|
|
d7d42b6854 | ||
|
|
260a7fbcaa | ||
|
|
55e0ec39ed | ||
|
|
9c04a7102b | ||
|
|
a5e6f0cc30 | ||
|
|
2322b41f67 | ||
|
|
685e75d97d | ||
|
|
cde87716a1 | ||
|
|
dff0af3397 | ||
|
|
ca771993ee | ||
|
|
c8b9b4cd8f | ||
|
|
d7cd2cecbc | ||
|
|
e219197e2f | ||
|
|
3eb84ceba8 | ||
|
|
e358738c35 | ||
|
|
b15901df67 | ||
|
|
ee7d90e55b | ||
|
|
22b744aa96 | ||
|
|
5d01931402 | ||
|
|
206ad46950 | ||
|
|
9b6e47d291 | ||
|
|
70cddfdf0b | ||
|
|
c0a2347b80 | ||
|
|
dd556f8f72 | ||
|
|
2fc1a96c8b | ||
|
|
8a7f64b889 | ||
|
|
c153b28ed6 | ||
|
|
a9557df240 | ||
|
|
8c23090abd | ||
|
|
6efe2cd5fd | ||
|
|
4feea01879 | ||
|
|
51f19cf2fd | ||
|
|
dbc50760af | ||
|
|
638de0a769 | ||
|
|
0ab1c0ee89 | ||
|
|
cb8738287a | ||
|
|
ddedaf2d0e | ||
|
|
cfa4077b5c | ||
|
|
1d2de8d9b8 | ||
|
|
e843ea6669 | ||
|
|
9f2a66e0ef | ||
|
|
944ef2e564 | ||
|
|
00f45ef39d | ||
|
|
84926cbee1 | ||
|
|
27c1b66c08 | ||
|
|
027ae1cfb9 | ||
|
|
621f81355b | ||
|
|
849b61c5cf | ||
|
|
267ad0d11f | ||
|
|
e272722d6e | ||
|
|
a73f3cc52b | ||
|
|
748d18cc2a | ||
|
|
88741083fe | ||
|
|
829406fa62 | ||
|
|
df13eac6ef | ||
|
|
25dda3598c | ||
|
|
9a8e76b494 | ||
|
|
c6120f33de | ||
|
|
200504dc01 | ||
|
|
2a33707db2 | ||
|
|
3fd5af8134 | ||
|
|
bb68b22adb | ||
|
|
5accf4c6a8 | ||
|
|
28335700e3 | ||
|
|
e3826695c5 | ||
|
|
6fdfa24e89 | ||
|
|
fb2abd8afc | ||
|
|
bd84b54c5a | ||
|
|
af4eaac158 | ||
|
|
de166645b0 | ||
|
|
ae67f0d67b | ||
|
|
fab16a7947 | ||
|
|
9711e0e29e | ||
|
|
d7292dc670 | ||
|
|
6930aaf220 | ||
|
|
12903a3748 | ||
|
|
412c872266 | ||
|
|
8fab6b5e48 | ||
|
|
4af3030c81 | ||
|
|
c32f0a28f1 | ||
|
|
38e8f7c8d7 | ||
|
|
72aa597f85 | ||
|
|
f759ce07ae | ||
|
|
976a76ae23 | ||
|
|
89d761a096 | ||
|
|
e667eef951 | ||
|
|
192caedeb9 | ||
|
|
fc482609b6 | ||
|
|
e1e0c45d88 | ||
|
|
8f384247ba | ||
|
|
b0d396a5bd | ||
|
|
2354f5bc7e | ||
|
|
73b4f54d42 | ||
|
|
ccd9e2ecaf | ||
|
|
4f3d9d0798 | ||
|
|
5ffa8ffe2b | ||
|
|
50cb7f7aaf | ||
|
|
8daa7d8a8e | ||
|
|
5dbc300056 | ||
|
|
c7a4507f96 | ||
|
|
b812827480 | ||
|
|
f9051c94da | ||
|
|
4c3642526e | ||
|
|
7c86e8f903 | ||
|
|
b4cf5761fa | ||
|
|
881ece525f | ||
|
|
5acef45118 | ||
|
|
96d1b09147 | ||
|
|
fc390d69c7 | ||
|
|
c49cee8be6 | ||
|
|
8c9a11fc62 | ||
|
|
50b50409b1 | ||
|
|
b52cf1605d | ||
|
|
c14f2ddbd7 | ||
|
|
a9c6196142 | ||
|
|
2ed8429df8 | ||
|
|
c899984f76 | ||
|
|
ed3b165b4b | ||
|
|
20940293d3 | ||
|
|
f040ffad13 | ||
|
|
33f12ffd9d | ||
|
|
b0f8e331e6 | ||
|
|
e9d46a8093 | ||
|
|
ca90727c78 | ||
|
|
440b6b2833 | ||
|
|
3e7862fe4e | ||
|
|
f0f81d2ec2 | ||
|
|
eb28279de0 | ||
|
|
c75e45897c | ||
|
|
62b05513a2 | ||
|
|
353eb67cab | ||
|
|
290196ee9e | ||
|
|
ea63ea1c55 | ||
|
|
2d14cc5899 | ||
|
|
f612e0b69f | ||
|
|
929e1bbac1 | ||
|
|
1dd183c4bd | ||
|
|
bcb0e2fcc3 | ||
|
|
25a04fbc5f | ||
|
|
0b585ce3a5 | ||
|
|
8058d2bfd4 | ||
|
|
5a04030766 | ||
|
|
04e508561b | ||
|
|
57d5647a39 | ||
|
|
9f28e5ef79 | ||
|
|
42d9ad4206 | ||
|
|
907a0bd456 | ||
|
|
8f37af3985 | ||
|
|
6e160dca43 | ||
|
|
1f7d9dbb86 | ||
|
|
a780598607 | ||
|
|
7f66714d96 | ||
|
|
980ab6c49c | ||
|
|
fe181fecbe | ||
|
|
50c3a7a8e5 | ||
|
|
f766484b2e | ||
|
|
42e4364fda | ||
|
|
09a7d557d2 | ||
|
|
13bae268ec | ||
|
|
23fcce245d | ||
|
|
69c5d4f645 | ||
|
|
d7854fa6c0 | ||
|
|
cf8e684cb3 | ||
|
|
5d4486fce3 | ||
|
|
a9385f2132 | ||
|
|
37913ffbc7 | ||
|
|
215e89427d | ||
|
|
2eb95ab215 | ||
|
|
4b05dd49f6 | ||
|
|
00f3a0ce90 | ||
|
|
9e4f4c4670 | ||
|
|
236d7e2f49 | ||
|
|
205dd302da | ||
|
|
0ca29d2f83 | ||
|
|
232dbde8a3 | ||
|
|
70435cfccf | ||
|
|
bd455825e6 | ||
|
|
b15d05050a | ||
|
|
05d13979db | ||
|
|
454f5cdead | ||
|
|
3f45345aad | ||
|
|
8da57bd575 | ||
|
|
e47bcf2774 | ||
|
|
f14b499ffe | ||
|
|
4da96a3b76 | ||
|
|
58958cd705 | ||
|
|
e3118ee7b0 | ||
|
|
e3f2a77d2c | ||
|
|
10b5771445 | ||
|
|
835c6dea73 | ||
|
|
457b1c1abd | ||
|
|
1955508a90 | ||
|
|
a9cb6f3077 | ||
|
|
b66cdb8942 | ||
|
|
a8922203bf | ||
|
|
7e2410a0ed | ||
|
|
3e614e66f5 | ||
|
|
3b498f1be6 | ||
|
|
cf75ac58b9 | ||
|
|
90bbc54bb5 | ||
|
|
fe0697fccb | ||
|
|
b9078eadc0 | ||
|
|
06c4f5791b | ||
|
|
e3655c4513 | ||
|
|
293246ce92 | ||
|
|
8186246690 | ||
|
|
239d7f33ed | ||
|
|
85af7f920c | ||
|
|
7bb65af482 | ||
|
|
2101d06d32 | ||
|
|
b70ca6bec2 | ||
|
|
1a6644e28c | ||
|
|
97792f064b | ||
|
|
a9631c09c8 | ||
|
|
c4ec818fb8 | ||
|
|
dd323316f7 | ||
|
|
846ecadc59 | ||
|
|
cd328687e2 | ||
|
|
981e3866d3 | ||
|
|
7ca217f753 | ||
|
|
07b496be4c | ||
|
|
a45129b75b | ||
|
|
3593ee4931 | ||
|
|
690f90d296 | ||
|
|
d6fc57fc3f | ||
|
|
b893affbfa | ||
|
|
9feb5397a8 | ||
|
|
d3f3adc1f2 | ||
|
|
758d6a53e0 | ||
|
|
45406d3486 | ||
|
|
07e48ce318 | ||
|
|
53d70387a4 | ||
|
|
d7d127cb55 | ||
|
|
cd60f4e590 | ||
|
|
2eaa741f1a | ||
|
|
ce94a17b8a | ||
|
|
b53eb2e0bb | ||
|
|
f9b5cdb1e7 | ||
|
|
a053dbf872 | ||
|
|
6a6cc6b2ba | ||
|
|
45416d5ba0 | ||
|
|
0de6ba722a | ||
|
|
8101ddf4bc | ||
|
|
bf923007e8 | ||
|
|
30d3c738f5 | ||
|
|
4f89bf9eb5 | ||
|
|
4744434a62 | ||
|
|
b36c136662 | ||
|
|
d57e59493b | ||
|
|
af2ccf7449 | ||
|
|
82573f20d7 | ||
|
|
a857a86608 | ||
|
|
3c53381691 | ||
|
|
47f6b9b690 | ||
|
|
c175973ff0 | ||
|
|
d411efe57c | ||
|
|
951ace6183 | ||
|
|
d26d193a41 | ||
|
|
636454faa2 | ||
|
|
c469e68f7a | ||
|
|
5530287197 | ||
|
|
d316a3049c | ||
|
|
024c45ec5c | ||
|
|
942da28b53 | ||
|
|
69a900aa56 | ||
|
|
518a2a0632 | ||
|
|
7057b53d2c | ||
|
|
92b5411cb2 | ||
|
|
f199ce0e83 | ||
|
|
9d55b9936d | ||
|
|
698abe0bb8 | ||
|
|
7dc5ccb154 | ||
|
|
0204fce22a | ||
|
|
f02b99b40e | ||
|
|
a4618faa55 | ||
|
|
41dc0984cc | ||
|
|
b4ac991986 | ||
|
|
3a0dc05e3d | ||
|
|
946a78ae47 | ||
|
|
8943484aa8 | ||
|
|
9d0aee87d1 | ||
|
|
36d5aa9f01 | ||
|
|
8da4dbeb12 | ||
|
|
2a525da8ad | ||
|
|
795c2baf0f | ||
|
|
9127d9540c | ||
|
|
3cad79dfa9 | ||
|
|
0b37f74636 | ||
|
|
00253a923a | ||
|
|
e97468180f | ||
|
|
748e715ae8 | ||
|
|
50a9b38727 | ||
|
|
e29704c381 | ||
|
|
5bc43aa0c5 | ||
|
|
ff0e5dd29c | ||
|
|
1a4631db03 | ||
|
|
52d1540ad6 | ||
|
|
34743171f9 | ||
|
|
197ea28f76 | ||
|
|
a80c2b4d83 | ||
|
|
d5f4c8d358 | ||
|
|
79f4c9ebd9 | ||
|
|
3513d7a297 | ||
|
|
cec1d2cf4e | ||
|
|
e376decec1 | ||
|
|
d6ac40b047 | ||
|
|
d627fdc0fd | ||
|
|
0e5a0120b2 | ||
|
|
fb6dbde198 | ||
|
|
76968d915e | ||
|
|
4d33fb63d0 | ||
|
|
4c1bb65620 | ||
|
|
1b9af538fd | ||
|
|
ef553d9401 | ||
|
|
cbf0223f07 | ||
|
|
235393235b | ||
|
|
85cc8f0173 | ||
|
|
d36be6efbd | ||
|
|
59560cc870 | ||
|
|
a20e1bc0d4 | ||
|
|
b7503897c5 | ||
|
|
4a579a93bb | ||
|
|
9a3c450d34 | ||
|
|
b29addccbd | ||
|
|
ad542429a4 | ||
|
|
e13edc7486 | ||
|
|
b35d942eac | ||
|
|
939e76d696 |
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Set line endings of shell scripts to LF, even on Windows, otherwise execution
|
||||
# within Docker fails.
|
||||
*.sh text eol=lf
|
||||
BIN
.github/assets/github-badge.png
vendored
Normal file
BIN
.github/assets/github-badge.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
3
.github/assets/mastodon.svg
vendored
Normal file
3
.github/assets/mastodon.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="#6364ff" viewBox="0 0 16 16">
|
||||
<path d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a4 4 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522q0-1.288.66-2.046c.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764q.662.757.661 2.046z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 925 B |
3
.github/assets/twitter.svg
vendored
Normal file
3
.github/assets/twitter.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#1e9bf0" viewBox="0 0 16 16">
|
||||
<path d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334q.002-.211-.006-.422A6.7 6.7 0 0 0 16 3.542a6.7 6.7 0 0 1-1.889.518 3.3 3.3 0 0 0 1.447-1.817 6.5 6.5 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.32 9.32 0 0 1-6.767-3.429 3.29 3.29 0 0 0 1.018 4.382A3.3 3.3 0 0 1 .64 6.575v.045a3.29 3.29 0 0 0 2.632 3.218 3.2 3.2 0 0 1-.865.115 3 3 0 0 1-.614-.057 3.28 3.28 0 0 0 3.067 2.277A6.6 6.6 0 0 1 .78 13.58a6 6 0 0 1-.78-.045A9.34 9.34 0 0 0 5.026 15"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 560 B |
13
.github/workflows/auth-crowdin.yml
vendored
13
.github/workflows/auth-crowdin.yml
vendored
@@ -3,15 +3,16 @@ name: "Sync Crowdin translations (auth)"
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
# Run action when auth's intl_en.arb is changed
|
||||
# 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:
|
||||
# Run every 24 hours - https://crontab.guru/#0_*/24_*_*_*
|
||||
- cron: "0 */24 * * *"
|
||||
workflow_dispatch: # Allow manually running the action
|
||||
# See: [Note: Run every 24 hours]
|
||||
- cron: "50 1 * * *"
|
||||
# Also allow manually running the workflow
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
synchronize-with-crowdin:
|
||||
@@ -32,8 +33,8 @@ jobs:
|
||||
localization_branch_name: crowdin-translations-auth
|
||||
create_pull_request: true
|
||||
skip_untranslated_strings: true
|
||||
pull_request_title: "New translations (auth)"
|
||||
pull_request_body: "New translations via [Crowdin GH Action](https://github.com/crowdin/github-action)"
|
||||
pull_request_title: "[auth] New translations"
|
||||
pull_request_body: "New translations from [Crowdin](https://crowdin.com/project/ente-authenticator-app)"
|
||||
pull_request_base_branch_name: "main"
|
||||
project_id: 575169
|
||||
env:
|
||||
|
||||
23
.github/workflows/auth-lint.yml
vendored
23
.github/workflows/auth-lint.yml
vendored
@@ -1,14 +1,16 @@
|
||||
name: "Lint (auth)"
|
||||
|
||||
on:
|
||||
# Run on every push (this also covers pull requests)
|
||||
# Run on every push to a branch other than main that changes auth/
|
||||
push:
|
||||
branches-ignore: [main]
|
||||
paths:
|
||||
# - But only if something changes inside auth
|
||||
- "auth/**"
|
||||
# - Or if the there is some change in workflow itself
|
||||
- ".github/workflows/auth-lint.yml"
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.16.9"
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -16,20 +18,21 @@ jobs:
|
||||
run:
|
||||
working-directory: auth
|
||||
steps:
|
||||
# Checkout our code, including submodules
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout code and submodules
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
# Install Flutter
|
||||
- uses: subosito/flutter-action@v2
|
||||
- name: Install Flutter ${{ env.FLUTTER_VERSION }}
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: "3.13.4"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
# Install dependencies
|
||||
- run: flutter pub get
|
||||
|
||||
# Lint
|
||||
- run: flutter analyze --no-fatal-infos
|
||||
|
||||
- name: Verify custom icon JSON
|
||||
run: cat assets/custom-icons/_data/custom-icons.json | jq empty
|
||||
|
||||
39
.github/workflows/auth-release.yml
vendored
39
.github/workflows/auth-release.yml
vendored
@@ -1,14 +1,13 @@
|
||||
name: "Release (auth)"
|
||||
|
||||
# [Note: Testing release workflows that are triggered by tags]
|
||||
#
|
||||
# To test this out, push a tag with a pre-release version. The version number
|
||||
# should be the version number of the next actual release.
|
||||
#
|
||||
# > When major, minor, and patch are equal, a pre-release version has lower
|
||||
# > precedence than a normal version:
|
||||
# >
|
||||
# > Example: 1.0.0-alpha < 1.0.0.
|
||||
# >
|
||||
# > - https://semver.org
|
||||
# > precedence than a normal version. Example: 1.0.0-alpha < 1.0.0.
|
||||
# > https://semver.org
|
||||
#
|
||||
# So if the next release we intend to put out is 1.2.3, you can:
|
||||
#
|
||||
@@ -30,7 +29,7 @@ on:
|
||||
- "auth-v*"
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.16.9"
|
||||
FLUTTER_VERSION: "3.13.4"
|
||||
|
||||
jobs:
|
||||
build-ubuntu:
|
||||
@@ -38,7 +37,6 @@ jobs:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
# Run all the "run" steps inside the auth directory
|
||||
working-directory: auth
|
||||
|
||||
steps:
|
||||
@@ -63,7 +61,7 @@ jobs:
|
||||
- name: Create artifacts directory
|
||||
run: mkdir artifacts
|
||||
|
||||
- name: Build Android APK
|
||||
- name: Build independent APK
|
||||
run: |
|
||||
flutter build apk --release --flavor independent --dart-define=app.flavor=independent
|
||||
mv build/app/outputs/flutter-apk/app-independent-release.apk artifacts/ente-${{ github.ref_name }}.apk
|
||||
@@ -115,19 +113,16 @@ jobs:
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "auth/artifacts/*"
|
||||
prerelease: true
|
||||
draft: true
|
||||
allowUpdates: true
|
||||
updateOnlyUnreleased: true
|
||||
|
||||
- name: Upload AAB to PlayStore
|
||||
# Temporarily disable GP upload
|
||||
if: false
|
||||
uses: r0adkll/upload-google-play@v1
|
||||
with:
|
||||
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
|
||||
packageName: io.ente.auth
|
||||
releaseFiles: build/app/outputs/bundle/playstoreRelease/app-playstore-release.aab
|
||||
releaseFiles: auth/build/app/outputs/bundle/playstoreRelease/app-playstore-release.aab
|
||||
track: internal
|
||||
|
||||
build-windows:
|
||||
@@ -135,7 +130,6 @@ jobs:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
# Run all the "run" steps inside the auth directory
|
||||
working-directory: auth
|
||||
|
||||
steps:
|
||||
@@ -185,19 +179,10 @@ jobs:
|
||||
if: false
|
||||
run: tar.exe -a -c -f auth/artifacts/ente-${{ github.ref_name }}-windows.zip auth/ente-${{ github.ref_name }}-windows
|
||||
|
||||
- name: Temporary action
|
||||
# TODO: Remove me when desktop builds are enabled
|
||||
if: true
|
||||
run: echo test > artifacts/ente-example.txt
|
||||
|
||||
- name: Generate checksums
|
||||
run: sha256sum artifacts/ente-* > artifacts/sha256sum-win
|
||||
|
||||
- name: Create a draft GitHub release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "auth/artifacts/*"
|
||||
prerelease: true
|
||||
draft: true
|
||||
allowUpdates: true
|
||||
updateOnlyUnreleased: true
|
||||
@@ -207,7 +192,6 @@ jobs:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
# Run all the "run" steps inside the auth directory
|
||||
working-directory: auth
|
||||
|
||||
steps:
|
||||
@@ -295,19 +279,10 @@ jobs:
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
|
||||
- name: Temporary action
|
||||
# TODO: Remove me when desktop builds are enabled
|
||||
if: true
|
||||
run: echo test > artifacts/ente-example.txt
|
||||
|
||||
- name: Generate checksums
|
||||
run: sha256sum artifacts/ente-* > artifacts/sha256sum-mac
|
||||
|
||||
- name: Create a draft GitHub release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "auth/artifacts/*"
|
||||
prerelease: true
|
||||
draft: true
|
||||
allowUpdates: true
|
||||
updateOnlyUnreleased: true
|
||||
|
||||
54
.github/workflows/cli-release.yml
vendored
Normal file
54
.github/workflows/cli-release.yml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
name: "Release (cli)"
|
||||
|
||||
on:
|
||||
push:
|
||||
# Run when a tag matching the pattern "cli-v*"" is pushed
|
||||
#
|
||||
# Tip: to test this workflow, push at tag with a pre-release version,
|
||||
# e.g. `cli-v1.2.3-test`, where 1.2.3 is the expected version number of
|
||||
# the next release that'll go out.
|
||||
#
|
||||
# See: [Note: Testing release workflows that are triggered by tags]
|
||||
tags:
|
||||
- "cli-v*"
|
||||
|
||||
jobs:
|
||||
draft-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Create a draft GitHub release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
draft: true
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
needs: draft-release
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
goos: [linux, windows, darwin]
|
||||
goarch: ["386", amd64, arm64]
|
||||
exclude:
|
||||
- goarch: "386"
|
||||
goos: darwin
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build binaries and add to the release
|
||||
uses: wangyoucao577/go-release-action@v1
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
goos: ${{ matrix.goos }}
|
||||
goarch: ${{ matrix.goarch }}
|
||||
asset_name: ente-${{ github.ref_name }}-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
release_name: ${{ github.ref_name }}
|
||||
goversion: "1.20"
|
||||
project_path: "./cli"
|
||||
pre_command: export CGO_ENABLED=0
|
||||
build_flags: "-trimpath"
|
||||
ldflags: "-X main.AppVersion=${{ github.ref_name }} -s -w"
|
||||
md5sum: false
|
||||
sha256sum: true
|
||||
47
.github/workflows/docs-deploy.yml
vendored
Normal file
47
.github/workflows/docs-deploy.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
name: "Deploy (docs)"
|
||||
|
||||
on:
|
||||
# Run on every push to main that changes docs/
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "docs/**"
|
||||
- ".github/workflows/docs-deploy.yml"
|
||||
# Also allow manually running the workflow
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: docs
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- 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 production site
|
||||
# Will create docs/.vitepress/dist
|
||||
run: yarn build
|
||||
|
||||
- name: Publish
|
||||
uses: cloudflare/pages-action@1
|
||||
with:
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
projectName: ente
|
||||
branch: help
|
||||
directory: docs/docs/.vitepress/dist
|
||||
wranglerVersion: "3"
|
||||
37
.github/workflows/docs-verify-build.yml
vendored
Normal file
37
.github/workflows/docs-verify-build.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
name: "Verify build (docs)"
|
||||
|
||||
# Preflight build of docs. This allows us to ensure that yarn build is
|
||||
# succeeding before we merge the PR into main.
|
||||
|
||||
on:
|
||||
# Run on every push to a branch other than main that changes docs/
|
||||
push:
|
||||
branches-ignore: [main]
|
||||
paths:
|
||||
- "docs/**"
|
||||
- ".github/workflows/docs-verify-build.yml"
|
||||
|
||||
jobs:
|
||||
verify-build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: docs
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- 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 production site
|
||||
run: yarn build
|
||||
13
.github/workflows/mobile-crowdin.yml
vendored
13
.github/workflows/mobile-crowdin.yml
vendored
@@ -3,15 +3,16 @@ name: "Sync Crowdin translations (mobile)"
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
# Run action when mobiles's intl_en.arb is changed
|
||||
# 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:
|
||||
# Run every 24 hours - https://crontab.guru/#0_*/24_*_*_*
|
||||
- cron: "0 */24 * * *"
|
||||
workflow_dispatch: # Allow manually running the action
|
||||
# See: [Note: Run every 24 hours]
|
||||
- cron: "40 1 * * *"
|
||||
# Also allow manually running the workflow
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
synchronize-with-crowdin:
|
||||
@@ -32,8 +33,8 @@ jobs:
|
||||
localization_branch_name: crowdin-translations-mobile
|
||||
create_pull_request: true
|
||||
skip_untranslated_strings: true
|
||||
pull_request_title: "New translations (mobile)"
|
||||
pull_request_body: "New translations via [Crowdin GH Action](https://github.com/crowdin/github-action)"
|
||||
pull_request_title: "[mobile] New translations"
|
||||
pull_request_body: "New translations from [Crowdin](https://crowdin.com/project/ente-photos-app)"
|
||||
pull_request_base_branch_name: "main"
|
||||
project_id: 574741
|
||||
env:
|
||||
|
||||
20
.github/workflows/mobile-lint.yml
vendored
20
.github/workflows/mobile-lint.yml
vendored
@@ -1,14 +1,16 @@
|
||||
name: "Lint (mobile)"
|
||||
|
||||
on:
|
||||
# Run on every push (this also covers pull requests)
|
||||
# Run on every push to a branch other than main that changes mobile/
|
||||
push:
|
||||
branches-ignore: [main]
|
||||
paths:
|
||||
# - But only if something changes inside mobile
|
||||
- "mobile/**"
|
||||
# - Or if the there is some change in workflow itself
|
||||
- ".github/workflows/mobile-lint.yml"
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.13.4"
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -16,20 +18,18 @@ jobs:
|
||||
run:
|
||||
working-directory: mobile
|
||||
steps:
|
||||
# Checkout our code, including submodules
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout code and submodules
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
# Install Flutter
|
||||
- uses: subosito/flutter-action@v2
|
||||
- name: Install Flutter ${{ env.FLUTTER_VERSION }}
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: "3.13.4"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
# Install dependencies
|
||||
- run: flutter pub get
|
||||
|
||||
# Lint
|
||||
- run: flutter analyze --no-fatal-infos
|
||||
|
||||
58
.github/workflows/mobile-release.yml
vendored
Normal file
58
.github/workflows/mobile-release.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
name: "Release (photos independent)"
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Allow manually running the action
|
||||
push:
|
||||
# Run when a tag matching the pattern "photos-v*"" is pushed
|
||||
# See: [Note: Testing release workflows that are triggered by tags]
|
||||
tags:
|
||||
- "photos-v*"
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.13.4"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: mobile
|
||||
|
||||
steps:
|
||||
- name: Checkout code and submodules
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install Flutter ${{ env.FLUTTER_VERSION }}
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
- name: Setup keys
|
||||
uses: timheuer/base64-to-file@v1
|
||||
with:
|
||||
fileName: "keystore/ente_photos_key.jks"
|
||||
encodedString: ${{ secrets.SIGNING_KEY_PHOTOS }}
|
||||
|
||||
- name: Build independent APK
|
||||
run: |
|
||||
flutter build apk --release --flavor independent
|
||||
mv build/app/outputs/flutter-apk/app-independent-release.apk build/app/outputs/flutter-apk/ente-${{ github.ref_name }}.apk
|
||||
env:
|
||||
SIGNING_KEY_PATH: "/home/runner/work/_temp/keystore/ente_photos_key.jks"
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS_PHOTOS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD_PHOTOS }}
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD_PHOTOS }}
|
||||
|
||||
- name: Checksum
|
||||
run: sha256sum build/app/outputs/flutter-apk/ente.apk > build/app/outputs/flutter-apk/sha256sum
|
||||
|
||||
- name: Create a draft GitHub release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "mobile/build/app/outputs/flutter-apk/ente-${{ github.ref_name }}.apk,mobile/build/app/outputs/flutter-apk/sha256sum"
|
||||
draft: true
|
||||
31
.github/workflows/server-lint.yml
vendored
Normal file
31
.github/workflows/server-lint.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
name: "Lint (server)"
|
||||
|
||||
on:
|
||||
# Run on every push to a branch other than main that changes server/
|
||||
push:
|
||||
branches-ignore: [main]
|
||||
paths:
|
||||
- "server/**"
|
||||
- ".github/workflows/server-lint.yml"
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: server
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: "server/go.mod"
|
||||
cache: true
|
||||
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get update && sudo apt-get install libsodium-dev
|
||||
|
||||
- name: Lint
|
||||
run: "./scripts/lint.sh"
|
||||
@@ -1,16 +1,10 @@
|
||||
name: Prod CI
|
||||
name: "Release (server)"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# Enable manual run
|
||||
push:
|
||||
# Sequence of patterns matched against refs/tags
|
||||
tags:
|
||||
- "v*" # Push events to matching v*, i.e. v4.2.0
|
||||
workflow_dispatch: # Run manually
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# This job will run on ubuntu virtual machine
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -19,6 +13,8 @@ jobs:
|
||||
- uses: mr-smithers-excellent/docker-build-push@v6
|
||||
name: Build & Push
|
||||
with:
|
||||
dockerfile: server/Dockerfile
|
||||
directory: server
|
||||
image: ente/museum-prod
|
||||
registry: rg.fr-par.scw.cloud
|
||||
enableBuildKit: true
|
||||
13
.github/workflows/web-crowdin.yml
vendored
13
.github/workflows/web-crowdin.yml
vendored
@@ -3,15 +3,16 @@ name: "Sync Crowdin translations (web)"
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
# Run action when web's en-US/translation.json is changed
|
||||
# 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:
|
||||
# Run every 24 hours - https://crontab.guru/#0_*/24_*_*_*
|
||||
- cron: "0 */24 * * *"
|
||||
workflow_dispatch: # Allow manually running the action
|
||||
# See: [Note: Run every 24 hours]
|
||||
- cron: "20 1 * * *"
|
||||
# Also allow manually running the workflow
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
synchronize-with-crowdin:
|
||||
@@ -32,8 +33,8 @@ jobs:
|
||||
localization_branch_name: crowdin-translations-web
|
||||
create_pull_request: true
|
||||
skip_untranslated_strings: true
|
||||
pull_request_title: "New translations (web)"
|
||||
pull_request_body: "New translations via [Crowdin GH Action](https://github.com/crowdin/github-action)"
|
||||
pull_request_title: "[web] New translations"
|
||||
pull_request_body: "New translations from [Crowdin](https://crowdin.com/project/ente-photos-web)"
|
||||
pull_request_base_branch_name: "main"
|
||||
project_id: 569613
|
||||
env:
|
||||
|
||||
43
.github/workflows/web-deploy-accounts.yml
vendored
Normal file
43
.github/workflows/web-deploy-accounts.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: "Deploy (accounts)"
|
||||
|
||||
on:
|
||||
push:
|
||||
# Run workflow on pushes to the deploy/accounts
|
||||
branches: [deploy/accounts]
|
||||
|
||||
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 accounts
|
||||
run: yarn build:accounts
|
||||
|
||||
- name: Publish accounts
|
||||
uses: cloudflare/pages-action@1
|
||||
with:
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
projectName: ente
|
||||
branch: deploy/accounts
|
||||
directory: web/apps/accounts/out
|
||||
wranglerVersion: "3"
|
||||
43
.github/workflows/web-deploy-auth.yml
vendored
Normal file
43
.github/workflows/web-deploy-auth.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: "Deploy (auth)"
|
||||
|
||||
on:
|
||||
push:
|
||||
# Run workflow on pushes to the deploy/auth
|
||||
branches: [deploy/auth]
|
||||
|
||||
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 auth
|
||||
run: yarn build:auth
|
||||
|
||||
- name: Publish auth
|
||||
uses: cloudflare/pages-action@1
|
||||
with:
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
projectName: ente
|
||||
branch: deploy/auth
|
||||
directory: web/apps/auth/out
|
||||
wranglerVersion: "3"
|
||||
43
.github/workflows/web-deploy-cast.yml
vendored
Normal file
43
.github/workflows/web-deploy-cast.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: "Deploy (cast)"
|
||||
|
||||
on:
|
||||
push:
|
||||
# Run workflow on pushes to the deploy/cast
|
||||
branches: [deploy/cast]
|
||||
|
||||
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 cast
|
||||
run: yarn build:cast
|
||||
|
||||
- name: Publish cast
|
||||
uses: cloudflare/pages-action@1
|
||||
with:
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
projectName: ente
|
||||
branch: deploy/cast
|
||||
directory: web/apps/cast/out
|
||||
wranglerVersion: "3"
|
||||
43
.github/workflows/web-deploy-photos.yml
vendored
Normal file
43
.github/workflows/web-deploy-photos.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: "Deploy (photos)"
|
||||
|
||||
on:
|
||||
push:
|
||||
# Run workflow on pushes to the deploy/photos
|
||||
branches: [deploy/photos]
|
||||
|
||||
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 photos
|
||||
run: yarn build:photos
|
||||
|
||||
- name: Publish photos
|
||||
uses: cloudflare/pages-action@1
|
||||
with:
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
projectName: ente
|
||||
branch: deploy/photos
|
||||
directory: web/apps/photos/out
|
||||
wranglerVersion: "3"
|
||||
14
.github/workflows/web-lint.yml
vendored
14
.github/workflows/web-lint.yml
vendored
@@ -1,12 +1,11 @@
|
||||
name: "Lint (web)"
|
||||
|
||||
on:
|
||||
# Run on every push (this also covers pull requests)
|
||||
# Run on every push to a branch other than main that changes web/
|
||||
push:
|
||||
branches-ignore: [main]
|
||||
paths:
|
||||
# - But only if something changes inside web
|
||||
- "web/**"
|
||||
# - Or if the there is some change in workflow itself
|
||||
- ".github/workflows/web-lint.yml"
|
||||
|
||||
jobs:
|
||||
@@ -16,11 +15,16 @@ jobs:
|
||||
run:
|
||||
working-directory: web
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup node and enable yarn caching
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "yarn"
|
||||
cache-dependency-path: "web/yarn.lock"
|
||||
|
||||
- run: yarn install
|
||||
|
||||
- run: yarn lint
|
||||
|
||||
94
.github/workflows/web-nightly.yml
vendored
Normal file
94
.github/workflows/web-nightly.yml
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
name: "Nightly (web)"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# [Note: Run 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
|
||||
# avoid scheduling it on the exact hour, as suggested by GitHub.
|
||||
#
|
||||
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule
|
||||
# https://crontab.guru/
|
||||
#
|
||||
- cron: "15 23 * * *"
|
||||
# Also allow manually running the workflow
|
||||
workflow_dispatch:
|
||||
|
||||
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 accounts
|
||||
run: yarn build:accounts
|
||||
|
||||
- name: Publish accounts
|
||||
uses: cloudflare/pages-action@1
|
||||
with:
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
projectName: ente
|
||||
branch: n-accounts
|
||||
directory: web/apps/accounts/out
|
||||
wranglerVersion: "3"
|
||||
|
||||
- name: Build auth
|
||||
run: yarn build:auth
|
||||
|
||||
- name: Publish auth
|
||||
uses: cloudflare/pages-action@1
|
||||
with:
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
projectName: ente
|
||||
branch: n-auth
|
||||
directory: web/apps/auth/out
|
||||
wranglerVersion: "3"
|
||||
|
||||
- name: Build cast
|
||||
run: yarn build:cast
|
||||
|
||||
- name: Publish cast
|
||||
uses: cloudflare/pages-action@1
|
||||
with:
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
projectName: ente
|
||||
branch: n-cast
|
||||
directory: web/apps/cast/out
|
||||
wranglerVersion: "3"
|
||||
|
||||
- name: Build photos
|
||||
run: yarn build:photos
|
||||
env:
|
||||
NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT: https://albums.ente.sh
|
||||
|
||||
- name: Publish photos
|
||||
uses: cloudflare/pages-action@1
|
||||
with:
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
projectName: ente
|
||||
branch: n-photos
|
||||
directory: web/apps/photos/out
|
||||
wranglerVersion: "3"
|
||||
52
.github/workflows/web-preview.yml
vendored
Normal file
52
.github/workflows/web-preview.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
name: "Preview (web)"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
app:
|
||||
description: "App to build and deploy"
|
||||
type: choice
|
||||
required: true
|
||||
default: "photos"
|
||||
options:
|
||||
- "accounts"
|
||||
- "auth"
|
||||
- "cast"
|
||||
- "photos"
|
||||
|
||||
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 ${{ inputs.app }}
|
||||
run: yarn build:${{ inputs.app }}
|
||||
|
||||
- name: Publish ${{ inputs.app }} to preview
|
||||
uses: cloudflare/pages-action@1
|
||||
with:
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
projectName: ente
|
||||
branch: preview
|
||||
directory: web/apps/${{ inputs.app }}/out
|
||||
wranglerVersion: "3"
|
||||
4
.gitmodules
vendored
4
.gitmodules
vendored
@@ -9,10 +9,6 @@
|
||||
[submodule "auth/assets/simple-icons"]
|
||||
path = auth/assets/simple-icons
|
||||
url = https://github.com/simple-icons/simple-icons.git
|
||||
[submodule "desktop/thirdparty/next-electron-server"]
|
||||
path = desktop/thirdparty/next-electron-server
|
||||
url = https://github.com/ente-io/next-electron-server.git
|
||||
branch = desktop
|
||||
[submodule "mobile/thirdparty/flutter"]
|
||||
path = mobile/thirdparty/flutter
|
||||
url = https://github.com/flutter/flutter.git
|
||||
|
||||
@@ -42,7 +42,7 @@ projects to get started:
|
||||
|
||||
|
||||
If your language is not listed for translation, please [create a GitHub
|
||||
issue](https://github.com/ente-io/ente/issues/new?title=Request+for+New+Language+Translation&body=Language+name%3A)
|
||||
issue](https://github.com/ente-io/ente/issues/new?title=Request+for+New+Language+Translation&body=Language+name%3A+%0AProject%3A+auth%2Fphotos%2Fboth)
|
||||
to have it added. It is okay to have partial translations. Once ~90% of the
|
||||
strings in a language get translated, we will start surfacing it in the apps.
|
||||
|
||||
@@ -50,21 +50,22 @@ Thank you for your support.
|
||||
|
||||
## Document
|
||||
|
||||
_Coming soon!_
|
||||
|
||||
The help guides and FAQs for users of Ente products are also open source, and
|
||||
can be edited in a wiki-esque manner by our community members. More than the
|
||||
quantity, we feel this helps improve the quality and approachability of the
|
||||
documentation by bringing in more diverse viewpoints and familiarity levels.
|
||||
|
||||
See [docs/](docs/README.md) for how to edit these documents.
|
||||
|
||||
## Code contributions
|
||||
|
||||
If you'd like to contribute code, it is best to start small.
|
||||
|
||||
Each of the individual product/platform specific directories in this repository
|
||||
have instructions on setting up a dev environment and making changes. The issues
|
||||
labelled "good first issues" should be good starting points. Once you have a
|
||||
bearing, you can head on to issues labelled "help wanted".
|
||||
and discussions (feature requests) labelled "good first issues" should be good
|
||||
starting points. Once you have a bearing, you can head on to issues or
|
||||
discussions labelled "help wanted".
|
||||
|
||||
If you're planning on adding a new feature or making any other substantial
|
||||
change, please [discuss it with
|
||||
|
||||
@@ -70,6 +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/web-badge.svg">](https://auth.ente.io)
|
||||
|
||||
</div>
|
||||
@@ -98,6 +99,8 @@ connect with the community.
|
||||
[](https://discord.gg/z2YVKkycX3)
|
||||
[](https://ente.io/blog/rss.xml)
|
||||
|
||||
[](https://twitter.com/enteio) [](https://mstdn.social/@ente)
|
||||
|
||||
---
|
||||
|
||||
## Security
|
||||
|
||||
@@ -7,7 +7,7 @@ details as possible about whatever it is that you need help with, and we will
|
||||
get back to you as soon as possible.
|
||||
|
||||
In some cases, your query might already have been answered in our help
|
||||
documentation (_Coming soon!_).
|
||||
documentation at [help.ente.io](https://help.ente.io).
|
||||
|
||||
Other ways to get in touch are:
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ multi-device sync.
|
||||
### Android
|
||||
|
||||
This repository's [GitHub
|
||||
releases](https://github.com/ente-io/ente/releases/latest/download/ente-auth.apk)
|
||||
releases](https://github.com/ente-io/ente/releases?q=tag%3Aauth-v2)
|
||||
contains APKs, built straight from source. These builds keep themselves updated,
|
||||
without relying on third party stores.
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ linter:
|
||||
- use_rethrow_when_possible
|
||||
- directives_ordering
|
||||
- always_use_package_imports
|
||||
- unawaited_futures
|
||||
|
||||
analyzer:
|
||||
errors:
|
||||
@@ -45,6 +46,8 @@ analyzer:
|
||||
prefer_const_declarations: warning
|
||||
prefer_const_constructors_in_immutables: ignore # too many warnings
|
||||
|
||||
unawaited_futures: warning # convert to warning after fixing existing issues
|
||||
|
||||
avoid_renaming_method_parameters: ignore # incorrect warnings for `equals` overrides
|
||||
|
||||
exclude:
|
||||
|
||||
@@ -56,11 +56,11 @@ android {
|
||||
|
||||
signingConfigs {
|
||||
release {
|
||||
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : file(System.getenv("SIGNING_KEY_PATH"))
|
||||
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"
|
||||
|
||||
@@ -35,6 +35,13 @@
|
||||
<data android:scheme="otpauth" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="enteauth" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<!-- Don't delete the meta-data below.
|
||||
|
||||
@@ -30,11 +30,14 @@
|
||||
{
|
||||
"title": "Bitwarden"
|
||||
},
|
||||
{
|
||||
"title": "Bloom Host",
|
||||
"slug": "bloom_host"
|
||||
},
|
||||
{
|
||||
"title": "BorgBase",
|
||||
"altNames": ["borg"],
|
||||
"slug": "BorgBase",
|
||||
"hex": "222C31"
|
||||
"slug": "BorgBase"
|
||||
},
|
||||
{
|
||||
"title": "Brave Creators",
|
||||
@@ -105,12 +108,11 @@
|
||||
{
|
||||
"title": "Gosuslugi",
|
||||
"altNames": ["Госуслуги"],
|
||||
"slug": "Gosuslugi",
|
||||
"hex": "EE2F53"
|
||||
"slug": "Gosuslugi"
|
||||
},
|
||||
{
|
||||
"title": "Healthchecks.io",
|
||||
"slug": "healthchecks",
|
||||
"slug": "healthchecks"
|
||||
},
|
||||
{
|
||||
"title": "ING"
|
||||
@@ -123,13 +125,11 @@
|
||||
},
|
||||
{
|
||||
"title": "IVPN",
|
||||
"slug": "IVPN",
|
||||
"hex": "FA3243"
|
||||
"slug": "IVPN"
|
||||
},
|
||||
{
|
||||
"title": "IceDrive",
|
||||
"slug": "Icedrive",
|
||||
"hex": "1F4FD0"
|
||||
"slug": "Icedrive"
|
||||
},
|
||||
{
|
||||
"title": "Jagex",
|
||||
@@ -150,8 +150,7 @@
|
||||
"title": "Kite"
|
||||
},
|
||||
{
|
||||
"title": "Koofr",
|
||||
"hex": "71BA05"
|
||||
"title": "Koofr"
|
||||
},
|
||||
{
|
||||
"title": "Kraken",
|
||||
@@ -180,8 +179,7 @@
|
||||
{
|
||||
"title": "Murena",
|
||||
"altNames": ["eCloud"],
|
||||
"slug": "ecloud",
|
||||
"hex": "EC6A55"
|
||||
"slug": "ecloud"
|
||||
},
|
||||
{
|
||||
"title": "Microsoft"
|
||||
@@ -226,8 +224,7 @@
|
||||
},
|
||||
{
|
||||
"title": "pCloud",
|
||||
"slug": "pCloud",
|
||||
"hex": "1EBCC5"
|
||||
"slug": "pCloud"
|
||||
},
|
||||
{
|
||||
"title": "Peerberry",
|
||||
@@ -298,7 +295,7 @@
|
||||
},
|
||||
{
|
||||
"title": "Synology DSM",
|
||||
"slug": "synology_dsm",
|
||||
"slug": "synology_dsm"
|
||||
},
|
||||
{
|
||||
"title": "TCPShield",
|
||||
@@ -367,8 +364,7 @@
|
||||
{
|
||||
"title": "Yandex",
|
||||
"altNames": ["Ya", "Яндекс"],
|
||||
"slug": "Yandex",
|
||||
"hex": "FC3F1D"
|
||||
"slug": "Yandex"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
8
auth/assets/custom-icons/icons/bloom_host.svg
Normal file
8
auth/assets/custom-icons/icons/bloom_host.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 30 KiB |
@@ -1,3 +1,12 @@
|
||||
## Developer docs
|
||||
|
||||
Documentation and notes about more advanced or infrequently needed details.
|
||||
|
||||
### Running
|
||||
|
||||
If you're using VSCode, you can setup the launch configuration by copying the
|
||||
template into your `.vscode` folder at the root of the project.
|
||||
|
||||
```bash
|
||||
cp auth/docs/vscode/launch.json .vscode/
|
||||
```
|
||||
@@ -1,26 +1,22 @@
|
||||
# Releases
|
||||
|
||||
Create a PR to bump up the version number in `pubspec.yaml`.
|
||||
Create a PR to bump up the version in `pubspec.yaml`. Once that is merged, tag
|
||||
main, and push the tag.
|
||||
|
||||
Once that is merged, tag main (using the `auth-v1.2.3` format), and push the
|
||||
tag. This'll trigger a GitHub workflow that:
|
||||
```sh
|
||||
git tag auth-v1.2.3
|
||||
git push origin auth-v1.2.3
|
||||
```
|
||||
|
||||
This'll trigger a GitHub workflow that:
|
||||
|
||||
* Creates a new draft GitHub release and attaches all the build artifacts to it
|
||||
(mobile APKs and various desktop packages),
|
||||
|
||||
* Creates a new release in the internal track on Play Store.
|
||||
|
||||
(TODO(MR): Fix this after the monorepo move) Xcode Cloud has already been
|
||||
configured and will automatically build and release to TestFlight when step 1
|
||||
was merged to main (you can see logs under the PR checks).
|
||||
|
||||
If you want to make changes to the workflow itself, or test it out, you can push
|
||||
a tag like `auth-v1.2.3-test` (where v1.2.3 is the next expected version that'll
|
||||
go out). For more details, see the comments on top of the [auth-release
|
||||
workflow](.github/workflows/auth-release.yml).
|
||||
|
||||
Once the workflow completes, go to the draft GitHub release it that was created.
|
||||
Use the "Generate release notes" button after setting the "Previous tag" for the
|
||||
last release of auth. 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 only the things that relate to auth.
|
||||
Once the workflow completes, go to the draft GitHub release that was created.
|
||||
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
|
||||
only the things that relate to the auth.
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "debug",
|
||||
"program": "lib/main.dart",
|
||||
"program": "auth/lib/main.dart",
|
||||
"args": ["--dart-define", "endpoint=http://localhost:8080"]
|
||||
},
|
||||
{
|
||||
"name": "Auth Android Dev",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"program": "lib/main.dart",
|
||||
"program": "auth/lib/main.dart",
|
||||
"args": [
|
||||
"--dart-define",
|
||||
"endpoint=http://192.168.1.3:8080",
|
||||
@@ -25,21 +25,21 @@
|
||||
"name": "Auth iOS Dev",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"program": "lib/main.dart",
|
||||
"program": "auth/lib/main.dart",
|
||||
"args": ["--dart-define", "endpoint=http://192.168.1.30:8080"]
|
||||
},
|
||||
{
|
||||
"name": "Auth iOS Prod",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"program": "lib/main.dart",
|
||||
"program": "auth/lib/main.dart",
|
||||
"args": ["--target", "lib/main.dart"]
|
||||
},
|
||||
{
|
||||
"name": "Auth Android Prod",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"program": "lib/main.dart",
|
||||
"program": "auth/lib/main.dart",
|
||||
"args": ["--target", "lib/main.dart", "--flavor", "independent"]
|
||||
}
|
||||
]
|
||||
|
||||
Submodule auth/flutter updated: 367f9ea16b...41456452f2
@@ -71,8 +71,6 @@ PODS:
|
||||
- move_to_background (0.0.1):
|
||||
- Flutter
|
||||
- MTBBarcodeScanner (5.0.11)
|
||||
- open_filex (0.0.2):
|
||||
- Flutter
|
||||
- OrderedSet (5.0.0)
|
||||
- package_info_plus (0.4.5):
|
||||
- Flutter
|
||||
@@ -126,7 +124,6 @@ DEPENDENCIES:
|
||||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||
- local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`)
|
||||
- move_to_background (from `.symlinks/plugins/move_to_background/ios`)
|
||||
- open_filex (from `.symlinks/plugins/open_filex/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- privacy_screen (from `.symlinks/plugins/privacy_screen/ios`)
|
||||
@@ -183,8 +180,6 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/local_auth_ios/ios"
|
||||
move_to_background:
|
||||
:path: ".symlinks/plugins/move_to_background/ios"
|
||||
open_filex:
|
||||
:path: ".symlinks/plugins/open_filex/ios"
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
path_provider_foundation:
|
||||
@@ -226,7 +221,6 @@ SPEC CHECKSUMS:
|
||||
local_auth_ios: c6cf091ded637a88f24f86a8875d8b0f526e2605
|
||||
move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d
|
||||
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
|
||||
open_filex: 6e26e659846ec990262224a12ef1c528bb4edbe4
|
||||
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
|
||||
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
|
||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>otpauth</string>
|
||||
<string>enteauth</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'dart:typed_data';
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:ente_auth/core/constants.dart';
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/events/endpoint_updated_event.dart';
|
||||
import 'package:ente_auth/events/signed_in_event.dart';
|
||||
import 'package:ente_auth/events/signed_out_event.dart';
|
||||
import 'package:ente_auth/models/key_attributes.dart';
|
||||
@@ -42,6 +43,7 @@ class Configuration {
|
||||
static const userIDKey = "user_id";
|
||||
static const hasMigratedSecureStorageKey = "has_migrated_secure_storage";
|
||||
static const hasOptedForOfflineModeKey = "has_opted_for_offline_mode";
|
||||
static const endPointKey = "endpoint";
|
||||
final List<String> onlineSecureKeys = [
|
||||
keyKey,
|
||||
secretKeyKey,
|
||||
@@ -317,7 +319,12 @@ class Configuration {
|
||||
}
|
||||
|
||||
String getHttpEndpoint() {
|
||||
return endpoint;
|
||||
return _preferences.getString(endPointKey) ?? endpoint;
|
||||
}
|
||||
|
||||
Future<void> setHttpEndpoint(String endpoint) async {
|
||||
await _preferences.setString(endPointKey, endpoint);
|
||||
Bus.instance.fire(EndpointUpdatedEvent());
|
||||
}
|
||||
|
||||
String? getToken() {
|
||||
|
||||
@@ -167,7 +167,7 @@ class SuperLogging {
|
||||
await setupLogDir();
|
||||
}
|
||||
if (sentryIsEnabled) {
|
||||
setupSentry();
|
||||
setupSentry().ignore();
|
||||
}
|
||||
|
||||
Logger.root.level = Level.ALL;
|
||||
@@ -250,7 +250,7 @@ class SuperLogging {
|
||||
|
||||
// add error to sentry queue
|
||||
if (sentryIsEnabled && rec.error != null) {
|
||||
_sendErrorToSentry(rec.error!, null);
|
||||
_sendErrorToSentry(rec.error!, null).ignore();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,7 +289,7 @@ class SuperLogging {
|
||||
SuperLogging.setUserID(await _getOrCreateAnonymousUserID());
|
||||
await for (final error in sentryQueueControl.stream.asBroadcastStream()) {
|
||||
try {
|
||||
Sentry.captureException(
|
||||
await Sentry.captureException(
|
||||
error,
|
||||
);
|
||||
} catch (e) {
|
||||
|
||||
@@ -2,28 +2,24 @@ import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/constants.dart';
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/events/endpoint_updated_event.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:shared_preferences/shared_preferences.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
int kConnectTimeout = 15000;
|
||||
|
||||
class Network {
|
||||
// apiEndpoint points to the Ente server's API endpoint
|
||||
static const apiEndpoint = String.fromEnvironment(
|
||||
"endpoint",
|
||||
defaultValue: kDefaultProductionEndpoint,
|
||||
);
|
||||
late Dio _dio;
|
||||
late Dio _enteDio;
|
||||
|
||||
Future<void> init() async {
|
||||
await FkUserAgent.init();
|
||||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
final preferences = await SharedPreferences.getInstance();
|
||||
final endpoint = Configuration.instance.getHttpEndpoint();
|
||||
|
||||
_dio = Dio(
|
||||
BaseOptions(
|
||||
connectTimeout: kConnectTimeout,
|
||||
@@ -34,10 +30,10 @@ class Network {
|
||||
},
|
||||
),
|
||||
);
|
||||
_dio.interceptors.add(RequestIdInterceptor());
|
||||
|
||||
_enteDio = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: apiEndpoint,
|
||||
baseUrl: endpoint,
|
||||
connectTimeout: kConnectTimeout,
|
||||
headers: {
|
||||
HttpHeaders.userAgentHeader: FkUserAgent.userAgent,
|
||||
@@ -46,7 +42,13 @@ class Network {
|
||||
},
|
||||
),
|
||||
);
|
||||
_enteDio.interceptors.add(EnteRequestInterceptor(preferences, apiEndpoint));
|
||||
_setupInterceptors(endpoint);
|
||||
|
||||
Bus.instance.on<EndpointUpdatedEvent>().listen((event) {
|
||||
final endpoint = Configuration.instance.getHttpEndpoint();
|
||||
_enteDio.options.baseUrl = endpoint;
|
||||
_setupInterceptors(endpoint);
|
||||
});
|
||||
}
|
||||
|
||||
Network._privateConstructor();
|
||||
@@ -55,34 +57,41 @@ class Network {
|
||||
|
||||
Dio getDio() => _dio;
|
||||
Dio get enteDio => _enteDio;
|
||||
|
||||
void _setupInterceptors(String endpoint) {
|
||||
_dio.interceptors.clear();
|
||||
_dio.interceptors.add(RequestIdInterceptor());
|
||||
|
||||
_enteDio.interceptors.clear();
|
||||
_enteDio.interceptors.add(EnteRequestInterceptor(endpoint));
|
||||
}
|
||||
}
|
||||
|
||||
class RequestIdInterceptor extends Interceptor {
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
// ignore: prefer_const_constructors
|
||||
options.headers.putIfAbsent("x-request-id", () => Uuid().v4().toString());
|
||||
options.headers
|
||||
.putIfAbsent("x-request-id", () => const Uuid().v4().toString());
|
||||
return super.onRequest(options, handler);
|
||||
}
|
||||
}
|
||||
|
||||
class EnteRequestInterceptor extends Interceptor {
|
||||
final SharedPreferences _preferences;
|
||||
final String enteEndpoint;
|
||||
final String endpoint;
|
||||
|
||||
EnteRequestInterceptor(this._preferences, this.enteEndpoint);
|
||||
EnteRequestInterceptor(this.endpoint);
|
||||
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
if (kDebugMode) {
|
||||
assert(
|
||||
options.baseUrl == enteEndpoint,
|
||||
"interceptor should only be used for API endpoint",
|
||||
options.baseUrl == endpoint,
|
||||
"interceptor should only be used for API endpoint",
|
||||
);
|
||||
}
|
||||
// ignore: prefer_const_constructors
|
||||
options.headers.putIfAbsent("x-request-id", () => Uuid().v4().toString());
|
||||
final String? tokenValue = _preferences.getString(Configuration.tokenKey);
|
||||
options.headers
|
||||
.putIfAbsent("x-request-id", () => const Uuid().v4().toString());
|
||||
final String? tokenValue = Configuration.instance.getToken();
|
||||
if (tokenValue != null) {
|
||||
options.headers.putIfAbsent("X-Auth-Token", () => tokenValue);
|
||||
}
|
||||
|
||||
3
auth/lib/events/endpoint_updated_event.dart
Normal file
3
auth/lib/events/endpoint_updated_event.dart
Normal file
@@ -0,0 +1,3 @@
|
||||
import 'package:ente_auth/events/event.dart';
|
||||
|
||||
class EndpointUpdatedEvent extends Event {}
|
||||
@@ -1,43 +1,29 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/errors.dart';
|
||||
import 'package:ente_auth/core/network.dart';
|
||||
import 'package:ente_auth/models/authenticator/auth_entity.dart';
|
||||
import 'package:ente_auth/models/authenticator/auth_key.dart';
|
||||
|
||||
class AuthenticatorGateway {
|
||||
final Dio _dio;
|
||||
final Configuration _config;
|
||||
late String _basedEndpoint;
|
||||
late Dio _enteDio;
|
||||
|
||||
AuthenticatorGateway(this._dio, this._config) {
|
||||
_basedEndpoint = _config.getHttpEndpoint() + "/authenticator";
|
||||
AuthenticatorGateway() {
|
||||
_enteDio = Network.instance.enteDio;
|
||||
}
|
||||
|
||||
Future<void> createKey(String encKey, String header) async {
|
||||
await _dio.post(
|
||||
_basedEndpoint + "/key",
|
||||
await _enteDio.post(
|
||||
"/authenticator/key",
|
||||
data: {
|
||||
"encryptedKey": encKey,
|
||||
"header": header,
|
||||
},
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<AuthKey> getKey() async {
|
||||
try {
|
||||
final response = await _dio.get(
|
||||
_basedEndpoint + "/key",
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
},
|
||||
),
|
||||
);
|
||||
final response = await _enteDio.get("/authenticator/key");
|
||||
return AuthKey.fromMap(response.data);
|
||||
} on DioError catch (e) {
|
||||
if (e.response != null && (e.response!.statusCode ?? 0) == 404) {
|
||||
@@ -51,17 +37,12 @@ class AuthenticatorGateway {
|
||||
}
|
||||
|
||||
Future<AuthEntity> createEntity(String encryptedData, String header) async {
|
||||
final response = await _dio.post(
|
||||
_basedEndpoint + "/entity",
|
||||
final response = await _enteDio.post(
|
||||
"/authenticator/entity",
|
||||
data: {
|
||||
"encryptedData": encryptedData,
|
||||
"header": header,
|
||||
},
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
},
|
||||
),
|
||||
);
|
||||
return AuthEntity.fromMap(response.data);
|
||||
}
|
||||
@@ -71,50 +52,35 @@ class AuthenticatorGateway {
|
||||
String encryptedData,
|
||||
String header,
|
||||
) async {
|
||||
await _dio.put(
|
||||
_basedEndpoint + "/entity",
|
||||
await _enteDio.put(
|
||||
"/authenticator/entity",
|
||||
data: {
|
||||
"id": id,
|
||||
"encryptedData": encryptedData,
|
||||
"header": header,
|
||||
},
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> deleteEntity(
|
||||
String id,
|
||||
) async {
|
||||
await _dio.delete(
|
||||
_basedEndpoint + "/entity",
|
||||
await _enteDio.delete(
|
||||
"/authenticator/entity",
|
||||
queryParameters: {
|
||||
"id": id,
|
||||
},
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<AuthEntity>> getDiff(int sinceTime, {int limit = 500}) async {
|
||||
try {
|
||||
final response = await _dio.get(
|
||||
_basedEndpoint + "/entity/diff",
|
||||
final response = await _enteDio.get(
|
||||
"/authenticator/entity/diff",
|
||||
queryParameters: {
|
||||
"sinceTime": sinceTime,
|
||||
"limit": limit,
|
||||
},
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
},
|
||||
),
|
||||
);
|
||||
final List<AuthEntity> authEntities = <AuthEntity>[];
|
||||
final diff = response.data["diff"] as List;
|
||||
|
||||
@@ -1 +1,408 @@
|
||||
{}
|
||||
{
|
||||
"account": "حسابي",
|
||||
"unlock": "فتح القفل",
|
||||
"recoveryKey": "مفتاح الاسترداد",
|
||||
"counterAppBarTitle": "العداد",
|
||||
"@counterAppBarTitle": {
|
||||
"description": "Text shown in the AppBar of the Counter Page"
|
||||
},
|
||||
"onBoardingBody": "النسخ الاحتياطي لأوامر 2FA",
|
||||
"onBoardingGetStarted": "إبدأ الآن",
|
||||
"setupFirstAccount": "إعداد الحساب الأول الخاص بك",
|
||||
"importScanQrCode": "مسح رمز QR",
|
||||
"qrCode": "رمز QR",
|
||||
"importEnterSetupKey": "أدخِل مفتاح الإعداد",
|
||||
"importAccountPageTitle": "أدخل تفاصيل الحساب",
|
||||
"secretCanNotBeEmpty": "لا يمكن أن يكون رمز السر فارغ",
|
||||
"bothIssuerAndAccountCanNotBeEmpty": "لا يمكن أن يكون المُصدر والحساب فارغًا",
|
||||
"incorrectDetails": "بيانات غير صحيحة",
|
||||
"pleaseVerifyDetails": "من فضلك تأكد من بياناتك وحاول مرة أخرى",
|
||||
"codeIssuerHint": "المصدِّر",
|
||||
"codeSecretKeyHint": "الرمز السري",
|
||||
"codeAccountHint": "الحساب (you@domain.com)",
|
||||
"accountKeyType": "نوع المفتاح",
|
||||
"sessionExpired": "انتهت صلاحية الجلسة",
|
||||
"@sessionExpired": {
|
||||
"description": "Title of the dialog when the users current session is invalid/expired"
|
||||
},
|
||||
"pleaseLoginAgain": "الرجاء تسجيل الدخول مرة أخرى",
|
||||
"loggingOut": "جاري تسجيل الخروج...",
|
||||
"timeBasedKeyType": "على أساس الوقت (TOTP)",
|
||||
"counterBasedKeyType": "القائم على العداد (HOTP)",
|
||||
"saveAction": "حفظ",
|
||||
"nextTotpTitle": "التالي",
|
||||
"deleteCodeTitle": "حذف الرمز؟",
|
||||
"deleteCodeMessage": "هل أنت متأكد من أنك تريد حذف هذا الرمز ؟ هذا الإجراء لا رجعة فيه.",
|
||||
"viewLogsAction": "عرض السجل",
|
||||
"sendLogsDescription": "هذا سوف يرسل عبر السجلات لمساعدتنا على تصحيح مشكلتك. وبينما نتخذ الاحتياطات لضمان عدم تسجيل المعلومات الحساسة، نشجعك على رؤية هذه السجلات قبل تقاسمها.",
|
||||
"preparingLogsTitle": "جاري إعداد السجلات...",
|
||||
"emailLogsTitle": "سجلات البريد الإلكتروني",
|
||||
"emailLogsMessage": "الرجاء إرسال السجلات إلى {email}",
|
||||
"@emailLogsMessage": {
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"copyEmailAction": "نسخ البريد الإلكتروني",
|
||||
"exportLogsAction": "تصدير السجلات",
|
||||
"reportABug": "الابلاغ عن خلل تقني",
|
||||
"crashAndErrorReporting": "الإبلاغ عن الأعطال والأخطاء",
|
||||
"reportBug": "الإبلاغ عن خلل",
|
||||
"emailUsMessage": "الرجاء مراسلتنا على {email}",
|
||||
"@emailUsMessage": {
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"contactSupport": "الاتصال بالدعم",
|
||||
"rateUsOnStore": "قم بتقييمنا على {storeName}",
|
||||
"blog": "المدونة",
|
||||
"merchandise": "إدارة المنتجات",
|
||||
"verifyPassword": "التحقق من كلمة المرور",
|
||||
"pleaseWait": "الرجاء الإنتظار...",
|
||||
"generatingEncryptionKeysTitle": "توليد مفاتيح التشفير...",
|
||||
"recreatePassword": "إعادة كتابة كلمة المرور",
|
||||
"recreatePasswordMessage": "الجهاز الحالي ليس قويًا بما يكفي للتحقق من كلمة المرور الخاصة بك، لذا نحتاج إلى إعادة إنشائها مرة واحدة بطريقة تعمل مع جميع الأجهزة.\n\nالرجاء تسجيل الدخول باستخدام مفتاح الاسترداد وإعادة إنشاء كلمة المرور الخاصة بك (يمكنك استخدام نفس كلمة المرور مرة أخرى إذا كنت ترغب في ذلك).",
|
||||
"useRecoveryKey": "استخدم مفتاح الاسترداد",
|
||||
"incorrectPasswordTitle": "كلمة المرور غير صحيحة",
|
||||
"welcomeBack": "مرحبًا مجددًا!",
|
||||
"madeWithLoveAtPrefix": "مصنوعة مع ❤️ في ",
|
||||
"supportDevs": "اشترك في <bold-green>ente</bold-green> لدعمنا",
|
||||
"supportDiscount": "استخدم رمز القسيمة \"AUTH\" للحصول على 10% خصم من السنة الأولى",
|
||||
"changeEmail": "تغيير البريد الإلكتروني",
|
||||
"changePassword": "تغيير كلمة المرور",
|
||||
"data": "البيانات",
|
||||
"importCodes": "رمزالاستيراد",
|
||||
"importTypePlainText": "نص عادي",
|
||||
"importTypeEnteEncrypted": "تصدير مشفر ente",
|
||||
"passwordForDecryptingExport": "كلمة المرور لفك تشفير التصدير",
|
||||
"passwordEmptyError": "لا يمكن أن تكون كلمة المرور فارغة",
|
||||
"importFromApp": "استيراد الرموز من {appName}",
|
||||
"importGoogleAuthGuide": "قم بتصدير حساباتك من Google Authenticator إلى رمز QR code باستخدام خيار \"Transfer Accounts\" ثم استخدم جهازًا آخر لمسح رمز الاستجابة السريعة ضوئيًا.\n\nنصيحة: يمكنك استخدام كاميرا الويب الخاصة بالكمبيوتر المحمول لالتقاط صورة لرمز الاستجابة السريعة.",
|
||||
"importSelectJsonFile": "حدد ملف JSON",
|
||||
"importSelectAppExport": "حدد ملف التصدير {appName}",
|
||||
"importEnteEncGuide": "حدد ملف JSON المشفر الذي تم تصديره من ente",
|
||||
"importRaivoGuide": "استخدم خيار تصدير OTP إلى أرشيف Zip في إعدادات Raivo.\n\nاستخرج ملف zip واسترد ملف JSON.",
|
||||
"importBitwardenGuide": "استخدم خيار \"تصدير خزانة\" داخل أدوات Bitwarden واستيراد ملف JSON غير مشفر.",
|
||||
"importAegisGuide": "استخدم خيار \"Export the vault\" في إعدادات Aegis.\n\nإذا كان المخزن الخاص بك مشفرًا، فستحتاج إلى إدخال كلمة مرور المخزن لفك تشفير المخزن.",
|
||||
"import2FasGuide": "استخدم خيار \"الإعدادات -> النسخ الاحتياطي - التصدير\" في 2FAS.\n\nإذا تم تشفير النسخة الاحتياطية، سوف تحتاج إلى إدخال كلمة المرور لفك تشفير النسخة الاحتياطية",
|
||||
"importLastpassGuide": "استخدم خيار \"حسابات النقل\" ضمن إعدادات مصادقة Lastpass، واضغط على \"تصدير الحسابات إلى الملف\". استيراد JSON الذي تم تنزيله.",
|
||||
"exportCodes": "تصدير الرموز",
|
||||
"importLabel": "استيراد",
|
||||
"importInstruction": "الرجاء تحديد ملف يحتوي على قائمة بالرموز الخاصة بك بالشكل التالي",
|
||||
"importCodeDelimiterInfo": "يمكن فصل الرموز بفاصلة أو سطر جديد",
|
||||
"selectFile": "اختيار الملف",
|
||||
"emailVerificationToggle": "تأكيد عنوان البريد الإلكتروني",
|
||||
"emailVerificationEnableWarning": "لتجنب إقفال حسابك، تأكد من تخزين نسخة من بريدك الإلكتروني 2FA خارج Ente Auth قبل تمكين التحقق من البريد الإلكتروني.",
|
||||
"authToChangeEmailVerificationSetting": "الرجاء المصادقة لتغيير التحقق من البريد الإلكتروني",
|
||||
"authToViewYourRecoveryKey": "الرجاء المصادقة لعرض مفتاح الاسترداد الخاص بك",
|
||||
"authToChangeYourEmail": "الرجاء المصادقة لتغيير بريدك الإلكتروني",
|
||||
"authToChangeYourPassword": "الرجاء المصادقة لتغيير كلمة المرور الخاصة بك",
|
||||
"authToViewSecrets": "الرجاء المصادقة لعرض مفتاح الاسترداد الخاص بك",
|
||||
"authToInitiateSignIn": "الرجاء المصادقة لبدء تسجيل الدخول للنسخ الاحتياطي.",
|
||||
"ok": "حسناً",
|
||||
"cancel": "إلغاء",
|
||||
"yes": "نعم",
|
||||
"no": "لا",
|
||||
"email": "البريد الإلكتروني",
|
||||
"support": "الدعم",
|
||||
"general": "العامة",
|
||||
"settings": "الإعدادات",
|
||||
"copied": "تم النسخ",
|
||||
"pleaseTryAgain": "حاول مرة اخرى",
|
||||
"existingUser": "المستخدم موجود",
|
||||
"newUser": "جديد إلى Ente",
|
||||
"delete": "حذف",
|
||||
"enterYourPasswordHint": "أدخل كلمة المرور الخاصة بك",
|
||||
"forgotPassword": "هل نسيت كلمة المرور",
|
||||
"oops": "عذرًا",
|
||||
"suggestFeatures": "اقتراح ميزة",
|
||||
"faq": "الأسئلة الأكثر شيوعاً",
|
||||
"faq_q_1": "ما مدى أمان المصادقة؟",
|
||||
"faq_a_1": "يتم تشفير جميع الرموز التي تقوم بنسخها احتياطا عبر Ente. وهذا يعني أنه يمكنك فقط الوصول إلى الرموز الخاصة بك. تطبيقاتنا مفتوحة المصدر وقد تم مراجعة التشفير خارجيا.",
|
||||
"faq_q_2": "هل يمكنني الوصول إلى رموزي على سطح المكتب؟",
|
||||
"faq_a_2": "يمكنك الوصول إلى رموزك على الويب @ auth.ente.io.",
|
||||
"faq_q_3": "كيف يمكنني حذف الرموز؟",
|
||||
"faq_a_3": "يمكنك حذف الرمز عن طريق السحب لليسار على هذا العنصر.",
|
||||
"faq_q_4": "كيف يمكنني دعم هذا المشروع؟",
|
||||
"faq_a_4": "يمكنك دعم تطوير هذا المشروع عن طريق الاشتراك في تطبيق الصور @ ente.io.",
|
||||
"faq_q_5": "كيف يمكنني تمكين قفل FaceID في المصادقة Ente",
|
||||
"faq_a_5": "يمكنك تمكين قفل FaceID تحت الإعدادات => الحماية => قفل الشاشة.",
|
||||
"somethingWentWrongMessage": "حدث خطأ ما، يرجى المحاولة مرة أخرى",
|
||||
"leaveFamily": "مغادرة خطة العائلة",
|
||||
"leaveFamilyMessage": "هل أنت متأكد من الخروج من خطة العائلة؟",
|
||||
"inFamilyPlanMessage": "أنت مندرج ضمن خطة عائلية!",
|
||||
"swipeHint": "اسحب لليسار لتحرير أو إزالة الرموز",
|
||||
"scan": "مسح",
|
||||
"scanACode": "فحص رمز Qr",
|
||||
"verify": "التحقق",
|
||||
"verifyEmail": "تأكيد البريد الإلكتروني",
|
||||
"enterCodeHint": "أدخل الرمز المكون من 6 أرقام من\nتطبيق المصادقة",
|
||||
"lostDeviceTitle": "جهاز مفقود ؟",
|
||||
"twoFactorAuthTitle": "المصادقة الثنائية",
|
||||
"recoverAccount": "إسترجاع الحساب",
|
||||
"enterRecoveryKeyHint": "أدخل رمز الاسترداد",
|
||||
"recover": "استرداد",
|
||||
"contactSupportViaEmailMessage": "الرجاء إسقاط بريد إلكتروني إلى {email} من عنوان بريدك الإلكتروني المسجل",
|
||||
"@contactSupportViaEmailMessage": {
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"noRecoveryKeyTitle": "لا يوجد مفتاح استرجاع؟",
|
||||
"enterEmailHint": "أدخل عنوان البريد الإلكتروني الخاص بك",
|
||||
"invalidEmailTitle": "عنوان البريد الإلكتروني غير صالح",
|
||||
"invalidEmailMessage": "الرجاء إدخال بريد إلكتروني صالح.",
|
||||
"deleteAccount": "إزالة الحساب",
|
||||
"deleteAccountQuery": "سوف نأسف لرؤيتك تذهب. هل تواجه بعض المشاكل؟",
|
||||
"yesSendFeedbackAction": "نعم، ارسل الملاحظات",
|
||||
"noDeleteAccountAction": "لا، حذف الحساب",
|
||||
"initiateAccountDeleteTitle": "الرجاء المصادقة لبدء حذف الحساب",
|
||||
"sendEmail": "ارسل بريد الكتروني",
|
||||
"createNewAccount": "إنشاء حساب جديد",
|
||||
"weakStrength": "ضعيف",
|
||||
"strongStrength": "قوي",
|
||||
"moderateStrength": "متوسط",
|
||||
"confirmPassword": "تأكيد كلمة المرور",
|
||||
"close": "إغلاق",
|
||||
"oopsSomethingWentWrong": "المعذرة! حدث خطأ ما.",
|
||||
"selectLanguage": "اختر اللغة",
|
||||
"language": "اللغة",
|
||||
"social": "وسائل التواصل",
|
||||
"security": "الأمان",
|
||||
"lockscreen": "شاشة القفل",
|
||||
"authToChangeLockscreenSetting": "الرجاء المصادقة لتغيير إعدادات شاشة القفل",
|
||||
"lockScreenEnablePreSteps": "لتمكين شاشة القفل، الرجاء إعداد رمز مرور الجهاز أو قفل الشاشة في إعدادات النظام الخاص بك.",
|
||||
"viewActiveSessions": "عرض الجلسات النشطة",
|
||||
"authToViewYourActiveSessions": "الرجاء المصادقة لعرض جلساتك النشطة",
|
||||
"searchHint": "بحث...",
|
||||
"search": "بحث",
|
||||
"sorryUnableToGenCode": "عذراً، غير قادر على إنشاء رمز ل {issuerName}",
|
||||
"noResult": "لا توجد نتيجة",
|
||||
"addCode": "أضف رمز",
|
||||
"scanAQrCode": "مسح رمز QR",
|
||||
"enterDetailsManually": "أدخل التفاصيل يدوياً",
|
||||
"edit": "تعديل",
|
||||
"copiedToClipboard": "تم النسخ إلى الحافظة",
|
||||
"copiedNextToClipboard": "تم نسخ الرموز التالية إلى الحافظة",
|
||||
"error": "خطأ",
|
||||
"recoveryKeyCopiedToClipboard": "تم نسخ عبارة الاسترداد للحافظة",
|
||||
"recoveryKeyOnForgotPassword": "إذا نسيت كلمة المرور الخاصة بك، فالطريقة الوحيدة التي يمكنك بها استرداد بياناتك هي بهذا المفتاح.",
|
||||
"recoveryKeySaveDescription": "نحن لا نخزن هذا المفتاح، يرجى حفظ مفتاح الـ 24 كلمة هذا في مكان آمن.",
|
||||
"doThisLater": "قم بهذا لاحقاً",
|
||||
"saveKey": "حفظ المفتاح",
|
||||
"back": "الرجوع",
|
||||
"createAccount": "إنشاء حساب",
|
||||
"passwordStrength": "قوة كلمة المرور: {passwordStrengthValue}",
|
||||
"@passwordStrength": {
|
||||
"description": "Text to indicate the password strength",
|
||||
"placeholders": {
|
||||
"passwordStrengthValue": {
|
||||
"description": "The strength of the password as a string",
|
||||
"type": "String",
|
||||
"example": "Weak or Moderate or Strong"
|
||||
}
|
||||
},
|
||||
"message": "Password Strength: {passwordStrengthText}"
|
||||
},
|
||||
"password": "كلمة المرور",
|
||||
"signUpTerms": "أوافق على <u-terms>شروط الخدمة</u-terms> و<u-policy>سياسة الخصوصية</u-policy>",
|
||||
"privacyPolicyTitle": "سياسة الخصوصية",
|
||||
"termsOfServicesTitle": "الشروط",
|
||||
"encryption": "التشفير",
|
||||
"setPasswordTitle": "تعيين كلمة المرور",
|
||||
"changePasswordTitle": "تغيير كلمة المرور",
|
||||
"resetPasswordTitle": "إعادة تعيين كلمة المرور",
|
||||
"encryptionKeys": "مفاتيح التشفير",
|
||||
"passwordWarning": "نحن لا نقوم بتخزين كلمة المرور هذه، لذا إذا نسيتها، <underline>لا يمكننا فك تشفير بياناتك</underline>",
|
||||
"enterPasswordToEncrypt": "أدخل كلمة المرور التي يمكننا استخدامها لتشفير بياناتك",
|
||||
"enterNewPasswordToEncrypt": "أدخل كلمة مرور جديدة يمكننا استخدامها لتشفير بياناتك",
|
||||
"passwordChangedSuccessfully": "تم تغيير كلمة المرور بنجاح",
|
||||
"generatingEncryptionKeys": "توليد مفاتيح التشفير...",
|
||||
"continueLabel": "المتابعة",
|
||||
"insecureDevice": "جهاز غير آمن",
|
||||
"sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "عذرًا، لم نتمكن من إنشاء مفاتيح آمنة على هذا الجهاز.\n\nيرجى التسجيل من جهاز مختلف.",
|
||||
"howItWorks": "كيف يعمل",
|
||||
"ackPasswordLostWarning": "أنا أفهم أنه إذا فقدت كلمة المرور الخاصة بي، قد أفقد بياناتي لأن بياناتي هي <underline>مشفرة من الند للند</underline>.",
|
||||
"loginTerms": "بالنقر على تسجيل الدخول، أوافق على شروط الخدمة <u-terms></u-terms> و <u-policy>سياسة الخصوصية</u-policy>",
|
||||
"logInLabel": "تسجيل الدخول",
|
||||
"logout": "تسجيل الخروج",
|
||||
"areYouSureYouWantToLogout": "هل أنت متأكد من أنك تريد تسجيل الخروج؟",
|
||||
"yesLogout": "نعم، تسجيل الخروج",
|
||||
"exit": "خروج",
|
||||
"verifyingRecoveryKey": "التحقق من مفتاح الاسترداد...",
|
||||
"recoveryKeyVerified": "تم التحقق من مفتاح الاسترداد",
|
||||
"recoveryKeySuccessBody": "رائع! مفتاح الاسترداد الخاص بك صالح. شكرا لك على التحقق.\n\nيرجى تذكر الاحتفاظ بنسخة احتياطية من مفتاح الاسترداد بشكل آمن.",
|
||||
"invalidRecoveryKey": "مفتاح الاسترداد الذي أدخلته غير صالح. الرجاء التأكد من أنه يحتوي على 24 كلمة، والتحقق من تهجئة كل منها.\n\nإذا قمت بإدخال رمز الاسترداد القديم، تأكد من أن طوله 64 حرفاً، وتحقق من كل منها.",
|
||||
"recreatePasswordTitle": "إعادة كتابة كلمة المرور",
|
||||
"recreatePasswordBody": "الجهاز الحالي ليس قويًا بما يكفي للتحقق من كلمة المرور الخاصة بك، لذا نحتاج إلى إعادة إنشائها مرة واحدة بطريقة تعمل مع جميع الأجهزة.\n\nالرجاء تسجيل الدخول باستخدام مفتاح الاسترداد وإعادة إنشاء كلمة المرور الخاصة بك (يمكنك استخدام نفس كلمة المرور مرة أخرى إذا كنت ترغب في ذلك).",
|
||||
"invalidKey": "المفتاح غير صالح",
|
||||
"tryAgain": "حاول مرة أخرى",
|
||||
"viewRecoveryKey": "عرض مفتاح الاسترداد",
|
||||
"confirmRecoveryKey": "تأكيد مفتاح الاسترداد",
|
||||
"recoveryKeyVerifyReason": "مفتاح الاسترداد الخاص بك هو الطريقة الوحيدة لاسترداد صورك إذا نسيت كلمة المرور الخاصة بك. يمكنك العثور على مفتاح الاسترداد الخاص بك في الإعدادات > الحساب.\n\nالرجاء إدخال مفتاح الاسترداد الخاص بك هنا للتحقق من أنك قمت بحفظه بشكل صحيح.",
|
||||
"confirmYourRecoveryKey": "تأكيد مفتاح الاسترداد",
|
||||
"confirm": "تأكيد",
|
||||
"emailYourLogs": "إرسال السجلات عبر البريد الإلكتروني",
|
||||
"pleaseSendTheLogsTo": "الرجاء إرسال السجلات إلى {toEmail}",
|
||||
"copyEmailAddress": "نسخ عنوان البريد الإلكتروني",
|
||||
"exportLogs": "تصدير السجلات",
|
||||
"enterYourRecoveryKey": "أدخل رمز الاسترداد",
|
||||
"tempErrorContactSupportIfPersists": "يبدو أنه حدث خطأ ما. الرجاء إعادة المحاولة لاحقا. إذا استمر الخطأ، يرجى الاتصال بفريق الدعم.",
|
||||
"itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "يبدو أنه حدث خطأ ما. الرجاء إعادة المحاولة لاحقا. إذا استمر الخطأ، يرجى الاتصال بفريق الدعم.",
|
||||
"about": "حول",
|
||||
"weAreOpenSource": "الخدمة مفتوحة المصدر!",
|
||||
"privacy": "الخصوصية",
|
||||
"terms": "الشروط",
|
||||
"checkForUpdates": "بحث عن تحديثات",
|
||||
"downloadUpdate": "تحميل",
|
||||
"criticalUpdateAvailable": "تحديث حاسم متوفر",
|
||||
"updateAvailable": "التحديث متاح",
|
||||
"update": "تحديث",
|
||||
"checking": "جارٍ التحقق...",
|
||||
"youAreOnTheLatestVersion": "أنت في الإصدار الأخير",
|
||||
"warning": "تحذير",
|
||||
"exportWarningDesc": "الملف الذي تم تصديره يحتوي على معلومات حساسة. الرجاء تخزين هذا بشكل آمن.",
|
||||
"iUnderStand": "فهمت",
|
||||
"@iUnderStand": {
|
||||
"description": "Text for the button to confirm the user understands the warning"
|
||||
},
|
||||
"authToExportCodes": "الرجاء المصادقة لتصدير الرموز الخاصة بك",
|
||||
"importSuccessTitle": "مرحى!",
|
||||
"importSuccessDesc": "لقد استوردت {count} رمز!",
|
||||
"@importSuccessDesc": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"description": "The number of codes imported",
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sorry": "المعذرة",
|
||||
"importFailureDesc": "تعذر تحليل الملف المحدد.\nالرجاء الكتابة إلى support@ente.io إذا كنت بحاجة إلى مساعدة!",
|
||||
"pendingSyncs": "تحذير",
|
||||
"pendingSyncsWarningBody": "لم يتم نسخ بعض رموزك احتياطيًا.\n\nيرجى التأكد من أن لديك نسخة احتياطية لهذه الرموز قبل تسجيل الخروج.",
|
||||
"checkInboxAndSpamFolder": "الرجاء التحقق من صندوق الوارد (والرسائل غير المرغوب فيها) لإكمال التحقق",
|
||||
"tapToEnterCode": "انقر لإدخال الرمز",
|
||||
"resendEmail": "إعادة إرسال البريد الإلكتروني",
|
||||
"weHaveSendEmailTo": "لقد أرسلنا رسالة إلى <green>{email}</green>",
|
||||
"@weHaveSendEmailTo": {
|
||||
"description": "Text to indicate that we have sent a mail to the user",
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"description": "The email address of the user",
|
||||
"type": "String",
|
||||
"example": "example@ente.io"
|
||||
}
|
||||
}
|
||||
},
|
||||
"activeSessions": "الجلسات النشطة",
|
||||
"somethingWentWrongPleaseTryAgain": "حدث خطأ ما، يرجى المحاولة مرة أخرى",
|
||||
"thisWillLogYouOutOfThisDevice": "سيؤدي هذا إلى تسجيل خروجك من هذا الجهاز!",
|
||||
"thisWillLogYouOutOfTheFollowingDevice": "سيؤدي هذا إلى تسجيل خروجك من هذا الجهاز:",
|
||||
"terminateSession": "إنهاء الجلسة؟",
|
||||
"terminate": "إنهاء",
|
||||
"thisDevice": "هذا الجهاز",
|
||||
"toResetVerifyEmail": "لإعادة تعيين كلمة المرور الخاصة بك، يرجى التحقق من بريدك الإلكتروني أولاً.",
|
||||
"thisEmailIsAlreadyInUse": "هذا البريد مستخدم مسبقاً",
|
||||
"verificationFailedPleaseTryAgain": "فشل في المصادقة ، يرجى المحاولة مرة أخرى في وقت لاحق",
|
||||
"yourVerificationCodeHasExpired": "انتهت صلاحية رمز التحقق",
|
||||
"incorrectCode": "رمز غير صحيح",
|
||||
"sorryTheCodeYouveEnteredIsIncorrect": "عذراً، الرمز الذي أدخلته غير صحيح",
|
||||
"emailChangedTo": "تم تغيير البريد الإلكتروني إلى {newEmail}",
|
||||
"authenticationFailedPleaseTryAgain": "فشلت المصادقة. الرجاء المحاولة مرة أخرى",
|
||||
"authenticationSuccessful": "تمت المصادقة بنجاح!",
|
||||
"twofactorAuthenticationSuccessfullyReset": "تم تحديث المصادقة الثنائية بنجاح",
|
||||
"incorrectRecoveryKey": "مفتاح الاسترداد غير صحيح",
|
||||
"theRecoveryKeyYouEnteredIsIncorrect": "مفتاح الاسترداد الذي أدخلته غير صحيح",
|
||||
"enterPassword": "أدخل كلمة المرور",
|
||||
"selectExportFormat": "اختر صيغة التصدير",
|
||||
"exportDialogDesc": "سيتم حماية الصادرات المشفرة بكلمة مرور من اختيارك.",
|
||||
"encrypted": "مشفَّرة",
|
||||
"plainText": "نص عادي",
|
||||
"passwordToEncryptExport": "كلمة المرور لتشفير التصدير",
|
||||
"export": "تصدير",
|
||||
"useOffline": "استخدام بدون نسخ إحتياطية",
|
||||
"signInToBackup": "قم بتسجيل الدخول للنسخ الاحتياطي للرموز الخاصة بك",
|
||||
"singIn": "تسجل الدخول",
|
||||
"sigInBackupReminder": "يرجى تصدير الرموز الخاصة بك للتأكد من أن لديك نسخة احتياطية يمكنك استعادتها منها.",
|
||||
"offlineModeWarning": "لقد اخترت المضي قدما بدون نسخ احتياطية. يرجى أخذ نسخ احتياطية يدوية للتأكد من سلامة الرموز الخاصة بك.",
|
||||
"showLargeIcons": "إظهار أيقونات كبيرة",
|
||||
"shouldHideCode": "إخفاء الرموز",
|
||||
"doubleTapToViewHiddenCode": "يمكنك النقر مرتين على أي عنصر لعرض الرمز",
|
||||
"focusOnSearchBar": "التركيز على البحث عند بدء التطبيق",
|
||||
"confirmUpdatingkey": "هل أنت متأكد من أنك تريد تحديث المفتاح السري؟",
|
||||
"minimizeAppOnCopy": "تصغير التطبيق عند النسخ",
|
||||
"editCodeAuthMessage": "المصادقة لتعديل الرمز",
|
||||
"deleteCodeAuthMessage": "المصادقة لحذف الرمز",
|
||||
"showQRAuthMessage": "المصادقة لإظهار رمز QR",
|
||||
"confirmAccountDeleteTitle": "تأكيد حذف الحساب",
|
||||
"confirmAccountDeleteMessage": "هذا الحساب مرتبط بتطبيقات Ente أخرى، إذا كنت تستخدم أي منها.\n\nبياناتك التي تم تحميلها، عبر جميع تطبيقات Ente سيتم جدولتها للحذف، وسيتم حذف حسابك بشكل دائم.",
|
||||
"androidBiometricHint": "التحقق من الهوية",
|
||||
"@androidBiometricHint": {
|
||||
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricNotRecognized": "لم يتم التعرف عليه. حاول مرة أخرى.",
|
||||
"@androidBiometricNotRecognized": {
|
||||
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricSuccess": "تم بنجاح",
|
||||
"@androidBiometricSuccess": {
|
||||
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidCancelButton": "إلغاء",
|
||||
"@androidCancelButton": {
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
|
||||
},
|
||||
"androidSignInTitle": "المصادقة مطلوبة",
|
||||
"@androidSignInTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricRequiredTitle": "البيومترية مطلوبة",
|
||||
"@androidBiometricRequiredTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidDeviceCredentialsRequiredTitle": "بيانات اعتماد الجهاز مطلوبة",
|
||||
"@androidDeviceCredentialsRequiredTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidDeviceCredentialsSetupDescription": "بيانات اعتماد الجهاز مطلوبة",
|
||||
"@androidDeviceCredentialsSetupDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side."
|
||||
},
|
||||
"goToSettings": "الانتقال إلى الإعدادات",
|
||||
"@goToSettings": {
|
||||
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
|
||||
},
|
||||
"androidGoToSettingsDescription": "لم يتم إعداد المصادقة الحيوية على جهازك. انتقل إلى 'الإعدادات > الأمن' لإضافة المصادقة البيومترية.",
|
||||
"@androidGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side."
|
||||
},
|
||||
"iOSLockOut": "المصادقة البيومترية معطلة. الرجاء قفل الشاشة وفتح القفل لتفعيلها.",
|
||||
"@iOSLockOut": {
|
||||
"description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side."
|
||||
},
|
||||
"iOSGoToSettingsDescription": "لم يتم إعداد المصادقة البيومترية على جهازك. الرجاء تمكين معرف اللمس أو معرف الوجه على هاتفك.",
|
||||
"@iOSGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side."
|
||||
},
|
||||
"iOSOkButton": "حسناً",
|
||||
"@iOSOkButton": {
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters."
|
||||
},
|
||||
"noInternetConnection": "لا يوجد اتصال بالإنترنت",
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain": "يرجى التحقق من اتصالك بالإنترنت ثم المحاولة من جديد.",
|
||||
"signOutFromOtherDevices": "تسجيل الخروج من الأجهزة الأخرى",
|
||||
"signOutOtherBody": "إذا كنت تعتقد أن شخصا ما يعرف كلمة المرور الخاصة بك، يمكنك إجبار جميع الأجهزة الأخرى الستخدمة حاليا لحسابك على تسجيل الخروج.",
|
||||
"signOutOtherDevices": "تسجيل الخروج من الأجهزة الأخرى",
|
||||
"doNotSignOut": "لا تقم بتسجيل الخروج",
|
||||
"hearUsWhereTitle": "كيف سمعت عن Ente؟ (اختياري)",
|
||||
"hearUsExplanation": "نحن لا نتتبع تثبيت التطبيق. سيكون من المفيد إذا أخبرتنا أين وجدتنا!"
|
||||
}
|
||||
@@ -59,7 +59,7 @@
|
||||
}
|
||||
},
|
||||
"contactSupport": "Contact support",
|
||||
"rateUsOnStore" : "Rate us on {storeName}",
|
||||
"rateUsOnStore": "Rate us on {storeName}",
|
||||
"blog": "Blog",
|
||||
"merchandise": "Merchandise",
|
||||
"verifyPassword": "Verify password",
|
||||
@@ -133,7 +133,6 @@
|
||||
"faq_q_5": "How can I enable FaceID lock in ente Auth",
|
||||
"faq_a_5": "You can enable FaceID lock under Settings → Security → Lockscreen.",
|
||||
"somethingWentWrongMessage": "Something went wrong, please try again",
|
||||
|
||||
"leaveFamily": "Leave family",
|
||||
"leaveFamilyMessage": "Are you sure that you want to leave the family plan?",
|
||||
"inFamilyPlanMessage": "You are on a family plan!",
|
||||
@@ -145,6 +144,8 @@
|
||||
"enterCodeHint": "Enter the 6-digit code from\nyour authenticator app",
|
||||
"lostDeviceTitle": "Lost device?",
|
||||
"twoFactorAuthTitle": "Two-factor authentication",
|
||||
"passkeyAuthTitle": "Passkey verification",
|
||||
"verifyPasskey": "Verify passkey",
|
||||
"recoverAccount": "Recover account",
|
||||
"enterRecoveryKeyHint": "Enter your recovery key",
|
||||
"recover": "Recover",
|
||||
@@ -337,10 +338,10 @@
|
||||
"offlineModeWarning": "You have chosen to proceed without backups. Please take manual backups to make sure your codes are safe.",
|
||||
"showLargeIcons": "Show large icons",
|
||||
"shouldHideCode": "Hide codes",
|
||||
"doubleTapToViewHiddenCode" : "You can double tap on an entry to view code",
|
||||
"doubleTapToViewHiddenCode": "You can double tap on an entry to view code",
|
||||
"focusOnSearchBar": "Focus search on app start",
|
||||
"confirmUpdatingkey": "Are you sure you want to update the secret key?",
|
||||
"minimizeAppOnCopy": "Minimize app on copy",
|
||||
"minimizeAppOnCopy": "Minimize app on copy",
|
||||
"editCodeAuthMessage": "Authenticate to edit code",
|
||||
"deleteCodeAuthMessage": "Authenticate to delete code",
|
||||
"showQRAuthMessage": "Authenticate to show QR code",
|
||||
@@ -405,5 +406,15 @@
|
||||
"signOutOtherDevices": "Sign out other devices",
|
||||
"doNotSignOut": "Do not sign out",
|
||||
"hearUsWhereTitle": "How did you hear about Ente? (optional)",
|
||||
"hearUsExplanation": "We don't track app installs. It'd help if you told us where you found us!"
|
||||
}
|
||||
"hearUsExplanation": "We don't track app installs. It'd help if you told us where you found us!",
|
||||
"waitingForBrowserRequest": "Waiting for browser request...",
|
||||
"waitingForVerification": "Waiting for verification...",
|
||||
"passkey": "Passkey",
|
||||
"developerSettingsWarning":"Are you sure that you want to modify Developer settings?",
|
||||
"developerSettings": "Developer settings",
|
||||
"serverEndpoint": "Server endpoint",
|
||||
"invalidEndpoint": "Invalid endpoint",
|
||||
"invalidEndpointMessage": "Sorry, the endpoint you entered is invalid. Please enter a valid endpoint and try again.",
|
||||
"endpointUpdatedMessage": "Endpoint updated successfully",
|
||||
"customEndpoint": "Connected to {endpoint}"
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"account": "Cuenta",
|
||||
"unlock": "Desbloquear",
|
||||
"recoveryKey": "Clave de recuperación",
|
||||
"counterAppBarTitle": "Contador",
|
||||
"@counterAppBarTitle": {
|
||||
@@ -83,9 +84,13 @@
|
||||
"importFromApp": "Importar códigos de {appName}",
|
||||
"importGoogleAuthGuide": "Exportar tus cuentas desde Google Authenticator a un código QR usando la opción \"Transferir Cuentas\". A continuación, usando otro dispositivo, escanee el código QR.\n\nConsejo: Puede usar la webcam de su portátil para tomar una foto del código QR.",
|
||||
"importSelectJsonFile": "Seleccione el archivo JSON",
|
||||
"importSelectAppExport": "Seleccione el archivo de exportación de {appName}",
|
||||
"importEnteEncGuide": "Seleccione el archivo JSON cifrado exportado desde ente",
|
||||
"importRaivoGuide": "Utilice la opción \"Exportar códigos a un archivo de Zip\" en la configuración de Raivo.\n\nExtraiga el archivo zip e importe el archivo JSON.",
|
||||
"importBitwardenGuide": "Use la opción \"Exportar caja fuerte\" dentro del menú Herramientas de Bitwarden e importe el fichero JSON no crifrado.",
|
||||
"importAegisGuide": "Utilice la opción \"Exportar la bóveda\" en ajustes de Aegis.\n\nSi tu bóveda es cifrada, necesitara entrar contraseña de bóveda para descifrar la bóveda.",
|
||||
"import2FasGuide": "Use la opción \"Configuración→Copia de seguridad→Exportar\" en 2FAS\n\nSi su copia de seguridad está cifrada, necesitará introducir la contraseña para descifrarla",
|
||||
"importLastpassGuide": "Utilice la opción \"Transferir cuentas\" en la configuración del autenticador de Lastpass y pulse \"Exportar cuentas al archivo\". Importe el archivo JSON descargado.",
|
||||
"exportCodes": "Exportar códigos",
|
||||
"importLabel": "Importar",
|
||||
"importInstruction": "Por favor, seleccione un archivo que contenga una lista de sus códigos en el siguiente formato",
|
||||
@@ -97,6 +102,8 @@
|
||||
"authToViewYourRecoveryKey": "Por favor, autentifíquese para ver su clave de recuperación",
|
||||
"authToChangeYourEmail": "Por favor, autentifíquese para cambiar su correo electrónico",
|
||||
"authToChangeYourPassword": "Por favor, autentifíquese para cambiar su contraseña",
|
||||
"authToViewSecrets": "Por favor, autentifíquese para ver sus secretos",
|
||||
"authToInitiateSignIn": "Por favor, autentifíquese para iniciar la sesión para realizar la copia de seguridad.",
|
||||
"ok": "Ok",
|
||||
"cancel": "Cancelar",
|
||||
"yes": "Si",
|
||||
@@ -329,6 +336,7 @@
|
||||
"offlineModeWarning": "Ha elegido proceder sin copia de seguridad. Por favor, tome copias de seguridad manuales para asegurarse de que sus códigos están seguros.",
|
||||
"showLargeIcons": "Mostrar iconos grandes",
|
||||
"shouldHideCode": "Ocultar códigos",
|
||||
"doubleTapToViewHiddenCode": "Puedes tocar dos veces en una entrada para ver el código",
|
||||
"focusOnSearchBar": "Enfocar búsqueda al iniciar la aplicación",
|
||||
"confirmUpdatingkey": "¿Estás seguro de que deseas actualizar la clave secreto?",
|
||||
"minimizeAppOnCopy": "Minimizar aplicación al copiar",
|
||||
@@ -336,5 +344,65 @@
|
||||
"deleteCodeAuthMessage": "Autenticar para borrar código",
|
||||
"showQRAuthMessage": "Autenticar para mostrar código QR",
|
||||
"confirmAccountDeleteTitle": "Confirmar eliminación de la cuenta",
|
||||
"confirmAccountDeleteMessage": "Esta cuenta está vinculada a otras aplicaciones de ente, si utiliza alguna.\n\nSe programará la eliminación de los datos que cargue en todas las aplicaciones de ente y su cuenta se eliminará permanentemente."
|
||||
"confirmAccountDeleteMessage": "Esta cuenta está vinculada a otras aplicaciones de ente, si utiliza alguna.\n\nSe programará la eliminación de los datos que cargue en todas las aplicaciones de ente y su cuenta se eliminará permanentemente.",
|
||||
"androidBiometricHint": "Verificar identidad",
|
||||
"@androidBiometricHint": {
|
||||
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricNotRecognized": "No reconocido. Inténtelo de nuevo.",
|
||||
"@androidBiometricNotRecognized": {
|
||||
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricSuccess": "Realizado correctamente",
|
||||
"@androidBiometricSuccess": {
|
||||
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidCancelButton": "Cancelar",
|
||||
"@androidCancelButton": {
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
|
||||
},
|
||||
"androidSignInTitle": "Se requiere autenticación",
|
||||
"@androidSignInTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricRequiredTitle": "Biométrica necesaria",
|
||||
"@androidBiometricRequiredTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidDeviceCredentialsRequiredTitle": "Se necesitan credenciales de dispositivo",
|
||||
"@androidDeviceCredentialsRequiredTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidDeviceCredentialsSetupDescription": "Se necesitan credenciales de dispositivo",
|
||||
"@androidDeviceCredentialsSetupDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side."
|
||||
},
|
||||
"goToSettings": "Ir a Ajustes",
|
||||
"@goToSettings": {
|
||||
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
|
||||
},
|
||||
"androidGoToSettingsDescription": "La autenticación biométrica no está configurada en su dispositivo. Vaya a 'Ajustes > Seguridad' para añadir autenticación biométrica.",
|
||||
"@androidGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side."
|
||||
},
|
||||
"iOSLockOut": "La autenticación biométrica está deshabilitada. Por favor bloquee y desbloquee la pantalla para habilitarla.",
|
||||
"@iOSLockOut": {
|
||||
"description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side."
|
||||
},
|
||||
"iOSGoToSettingsDescription": "La autenticación biométrica no está configurada en tu dispositivo. Por favor, activa Touch ID o Face ID en tu teléfono.",
|
||||
"@iOSGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side."
|
||||
},
|
||||
"iOSOkButton": "Aceptar",
|
||||
"@iOSOkButton": {
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters."
|
||||
},
|
||||
"noInternetConnection": "No hay conexión a Internet",
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain": "Compruebe su conexión a Internet e inténtelo de nuevo.",
|
||||
"signOutFromOtherDevices": "Cerrar sesión desde otros dispositivos",
|
||||
"signOutOtherBody": "Si cree que alguien puede conocer su contraseña, puede forzar a todos los demás dispositivos que usen su cuenta a cerrar la sesión.",
|
||||
"signOutOtherDevices": "Cerrar la sesión de otros dispositivos",
|
||||
"doNotSignOut": "No cerrar la sesión",
|
||||
"hearUsWhereTitle": "¿Cómo conoció Ente? (opcional)",
|
||||
"hearUsExplanation": "No rastreamos las aplicaciones instaladas. ¡Nos ayudaría si nos dijera dónde nos encontró!"
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"onBoardingBody": "Храните ваши коды двухфакторной аутентификации в безопасности",
|
||||
"onBoardingGetStarted": "Начать",
|
||||
"setupFirstAccount": "Настройте свою первую учетную запись",
|
||||
"setupFirstAccount": "Настройте свой первый аккаунт",
|
||||
"importScanQrCode": "Сканировать QR-код",
|
||||
"qrCode": "QR-код",
|
||||
"importEnterSetupKey": "Ввести ключ настройки",
|
||||
@@ -71,8 +71,8 @@
|
||||
"incorrectPasswordTitle": "Неправильный пароль",
|
||||
"welcomeBack": "С возвращением!",
|
||||
"madeWithLoveAtPrefix": "сделана с ❤️ в ",
|
||||
"supportDevs": "Подпишитесь на <bold-green>ente</bold-green> для поддержки этого проекта.",
|
||||
"supportDiscount": "Используйте код скидки \"AUTH\", чтобы получить скидку 10% в первый год",
|
||||
"supportDevs": "Подпишитесь на <bold-green>ente</bold-green> для поддержки нашего проекта",
|
||||
"supportDiscount": "Используйте код скидки \"AUTH\", чтобы получить скидку 10% на первый год",
|
||||
"changeEmail": "Изменить почту",
|
||||
"changePassword": "Изменить пароль",
|
||||
"data": "Данные",
|
||||
@@ -84,10 +84,12 @@
|
||||
"importFromApp": "Импорт кодов из {appName}",
|
||||
"importGoogleAuthGuide": "Экспортируйте учетные записи из Google Authenticator в QR-код, используя опцию «Перенести учетные записи». Затем с помощью другого устройства отсканируйте QR-код.\n\nСовет: Чтобы сфотографировать QR-код, можно воспользоваться веб-камерой ноутбука.",
|
||||
"importSelectJsonFile": "Выбрать JSON-файл",
|
||||
"importSelectAppExport": "Выбрать файл экспорта {appName}",
|
||||
"importEnteEncGuide": "Выберите зашифрованный JSON-файл, экспортированный из ente",
|
||||
"importRaivoGuide": "Используйте опцию «Export OTPs to Zip archive» в настройках Raivo.\n\nРаспакуйте zip-архив и импортируйте JSON-файл.",
|
||||
"importBitwardenGuide": "Используйте опцию \"Экспортировать хранилище\" в Bitwarden Tools и импортируйте незашифрованный JSON файл.",
|
||||
"importAegisGuide": "Используйте опцию «Экспортировать хранилище» в настройках Aegis.\n\nЕсли ваше хранилище зашифровано, то для его расшифровки потребуется ввести пароль хранилища.",
|
||||
"import2FasGuide": "Используйте опцию \"Settings->Backup -Export\" в 2FAS.\n\nЕсли ваша резервная копия зашифрована, то для расшифровки резервной копии необходимо ввести пароль",
|
||||
"exportCodes": "Экспортировать коды",
|
||||
"importLabel": "Импорт",
|
||||
"importInstruction": "Пожалуйста, выберите файл, содержащий список ваших кодов в следующем формате",
|
||||
|
||||
147
auth/lib/l10n/arb/app_sv.arb
Normal file
147
auth/lib/l10n/arb/app_sv.arb
Normal file
@@ -0,0 +1,147 @@
|
||||
{
|
||||
"account": "Konto",
|
||||
"unlock": "Lås upp",
|
||||
"recoveryKey": "Återställningsnyckel",
|
||||
"onBoardingBody": "Säkerhetskopiera dina 2FA-koder",
|
||||
"onBoardingGetStarted": "Kom igång",
|
||||
"setupFirstAccount": "Konfigurera ditt första konto",
|
||||
"importScanQrCode": "Skanna en QR-kod",
|
||||
"qrCode": "QR-kod",
|
||||
"importEnterSetupKey": "Ange en konfigurationskod",
|
||||
"importAccountPageTitle": "Ange kontodetaljer",
|
||||
"secretCanNotBeEmpty": "Secret kan inte vara tomt",
|
||||
"bothIssuerAndAccountCanNotBeEmpty": "Både utgivare och konto kan inte vara tomma",
|
||||
"incorrectDetails": "Felaktiga uppgifter",
|
||||
"pleaseVerifyDetails": "Kontrollera dina detaljer och försök igen",
|
||||
"codeIssuerHint": "Utfärdare",
|
||||
"codeSecretKeyHint": "Secret Key",
|
||||
"codeAccountHint": "Konto (du@domän.com)",
|
||||
"accountKeyType": "Typ av nyckel",
|
||||
"sessionExpired": "Sessionen har gått ut",
|
||||
"@sessionExpired": {
|
||||
"description": "Title of the dialog when the users current session is invalid/expired"
|
||||
},
|
||||
"pleaseLoginAgain": "Vänligen logga in igen",
|
||||
"loggingOut": "Loggar ut...",
|
||||
"saveAction": "Spara",
|
||||
"nextTotpTitle": "nästa",
|
||||
"deleteCodeMessage": "Vill du ta bort den här koden? Det går inte att ångra den här åtgärden.",
|
||||
"viewLogsAction": "Visa loggar",
|
||||
"emailLogsTitle": "E-posta loggar",
|
||||
"emailLogsMessage": "Skicka loggarna till {email}",
|
||||
"@emailLogsMessage": {
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"copyEmailAction": "Kopiera e-post",
|
||||
"exportLogsAction": "Exportera loggar",
|
||||
"reportABug": "Rapportera en bugg",
|
||||
"crashAndErrorReporting": "Krasch och felrapportering",
|
||||
"reportBug": "Rapportera bugg",
|
||||
"emailUsMessage": "Skicka e-mail till {email}",
|
||||
"@emailUsMessage": {
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"contactSupport": "Kontakta support",
|
||||
"rateUsOnStore": "Betygsätt på {storeName}",
|
||||
"blog": "Blogg",
|
||||
"merchandise": "Merchandise",
|
||||
"verifyPassword": "Bekräfta lösenord",
|
||||
"pleaseWait": "Vänligen vänta...",
|
||||
"generatingEncryptionKeysTitle": "Skapar krypteringsnycklar...",
|
||||
"recreatePassword": "Återskapa lösenord",
|
||||
"useRecoveryKey": "Använd återställningsnyckel",
|
||||
"incorrectPasswordTitle": "Felaktigt lösenord",
|
||||
"welcomeBack": "Välkommen tillbaka!",
|
||||
"changePassword": "Ändra lösenord",
|
||||
"cancel": "Avbryt",
|
||||
"yes": "Ja",
|
||||
"no": "Nej",
|
||||
"settings": "Inställningar",
|
||||
"pleaseTryAgain": "Försök igen",
|
||||
"existingUser": "Befintlig användare",
|
||||
"delete": "Radera",
|
||||
"enterYourPasswordHint": "Ange ditt lösenord",
|
||||
"forgotPassword": "Glömt lösenord",
|
||||
"oops": "Hoppsan",
|
||||
"suggestFeatures": "Föreslå funktionalitet",
|
||||
"faq": "FAQ",
|
||||
"faq_q_1": "Hur säkert är ente Auth?",
|
||||
"scan": "Skanna",
|
||||
"twoFactorAuthTitle": "Tvåfaktorsautentisering",
|
||||
"enterRecoveryKeyHint": "Ange din återställningsnyckel",
|
||||
"noRecoveryKeyTitle": "Ingen återställningsnyckel?",
|
||||
"enterEmailHint": "Ange din e-postadress",
|
||||
"invalidEmailTitle": "Ogiltig e-postadress",
|
||||
"invalidEmailMessage": "Ange en giltig e-postadress.",
|
||||
"deleteAccount": "Radera konto",
|
||||
"yesSendFeedbackAction": "Ja, skicka feedback",
|
||||
"noDeleteAccountAction": "Nej, radera konto",
|
||||
"createNewAccount": "Skapa nytt konto",
|
||||
"weakStrength": "Svag",
|
||||
"strongStrength": "Stark",
|
||||
"moderateStrength": "Måttligt",
|
||||
"confirmPassword": "Bekräfta lösenord",
|
||||
"close": "Stäng",
|
||||
"language": "Språk",
|
||||
"searchHint": "Sök...",
|
||||
"search": "Sök",
|
||||
"sorryUnableToGenCode": "Tyvärr, det gick inte att generera en kod för {issuerName}",
|
||||
"noResult": "Inga resultat",
|
||||
"addCode": "Lägg till kod",
|
||||
"scanAQrCode": "Skanna en QR-kod",
|
||||
"enterDetailsManually": "Ange uppgifter manuellt",
|
||||
"edit": "Redigera",
|
||||
"copiedToClipboard": "Kopierat till urklipp",
|
||||
"copiedNextToClipboard": "Kopierade nästa kod till urklipp",
|
||||
"error": "Fel",
|
||||
"recoveryKeyCopiedToClipboard": "Återställningsnyckel kopierad till urklipp",
|
||||
"recoveryKeyOnForgotPassword": "Om du glömmer ditt lösenord är det enda sättet du kan återställa dina data med denna nyckel.",
|
||||
"saveKey": "Spara nyckel",
|
||||
"back": "Tillbaka",
|
||||
"createAccount": "Skapa konto",
|
||||
"password": "Lösenord",
|
||||
"privacyPolicyTitle": "Integritetspolicy",
|
||||
"termsOfServicesTitle": "Villkor",
|
||||
"encryption": "Kryptering",
|
||||
"changePasswordTitle": "Ändra lösenord",
|
||||
"resetPasswordTitle": "Återställ lösenord",
|
||||
"encryptionKeys": "Krypteringsnycklar",
|
||||
"continueLabel": "Fortsätt",
|
||||
"logInLabel": "Logga in",
|
||||
"logout": "Logga ut",
|
||||
"areYouSureYouWantToLogout": "Är du säker på att du vill logga ut?",
|
||||
"yesLogout": "Ja, logga ut",
|
||||
"invalidKey": "Ogiltig nyckel",
|
||||
"tryAgain": "Försök igen",
|
||||
"viewRecoveryKey": "Visa återställningsnyckel",
|
||||
"confirmRecoveryKey": "Bekräfta återställningsnyckel",
|
||||
"confirmYourRecoveryKey": "Bekräfta din återställningsnyckel",
|
||||
"confirm": "Bekräfta",
|
||||
"copyEmailAddress": "Kopiera e-postadress",
|
||||
"exportLogs": "Exportera loggar",
|
||||
"enterYourRecoveryKey": "Ange din återställningsnyckel",
|
||||
"about": "Om",
|
||||
"terms": "Villkor",
|
||||
"warning": "Varning",
|
||||
"pendingSyncs": "Varning",
|
||||
"activeSessions": "Aktiva sessioner",
|
||||
"enterPassword": "Ange lösenord",
|
||||
"export": "Exportera",
|
||||
"singIn": "Logga in",
|
||||
"androidCancelButton": "Avbryt",
|
||||
"@androidCancelButton": {
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
|
||||
},
|
||||
"iOSOkButton": "OK",
|
||||
"@iOSOkButton": {
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters."
|
||||
}
|
||||
}
|
||||
1
auth/lib/l10n/arb/app_th.arb
Normal file
1
auth/lib/l10n/arb/app_th.arb
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -144,6 +144,7 @@
|
||||
"enterCodeHint": "从你的身份验证器应用中\n输入6位数字代码",
|
||||
"lostDeviceTitle": "丢失了设备吗?",
|
||||
"twoFactorAuthTitle": "双因素认证",
|
||||
"passkeyAuthTitle": "通行密钥认证",
|
||||
"recoverAccount": "恢复账户",
|
||||
"enterRecoveryKeyHint": "输入您的恢复密钥",
|
||||
"recover": "恢复",
|
||||
@@ -404,5 +405,8 @@
|
||||
"signOutOtherDevices": "登出其他设备",
|
||||
"doNotSignOut": "不要退登",
|
||||
"hearUsWhereTitle": "您是如何知道Ente的? (可选的)",
|
||||
"hearUsExplanation": "我们不跟踪应用程序安装情况。如果您告诉我们您是在哪里找到我们的,将会有所帮助!"
|
||||
"hearUsExplanation": "我们不跟踪应用程序安装情况。如果您告诉我们您是在哪里找到我们的,将会有所帮助!",
|
||||
"waitingForBrowserRequest": "正在等待浏览器请求...",
|
||||
"launchPasskeyUrlAgain": "再次启动 通行密钥 URL",
|
||||
"passkey": "通行密钥"
|
||||
}
|
||||
@@ -1,3 +1,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";
|
||||
@@ -33,7 +36,9 @@ void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await _runInForeground();
|
||||
await _setupPrivacyScreen();
|
||||
FlutterDisplayMode.setHighRefreshRate();
|
||||
if (Platform.isAndroid) {
|
||||
FlutterDisplayMode.setHighRefreshRate().ignore();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _runInForeground() async {
|
||||
@@ -42,7 +47,7 @@ Future<void> _runInForeground() async {
|
||||
_logger.info("Starting app in foreground");
|
||||
await _init(false, via: 'mainMethod');
|
||||
final Locale locale = await getLocale();
|
||||
UpdateService.instance.showUpdateNotification();
|
||||
unawaited(UpdateService.instance.showUpdateNotification());
|
||||
runApp(
|
||||
AppLock(
|
||||
builder: (args) => App(locale: locale),
|
||||
@@ -79,7 +84,7 @@ Future _runWithLogs(Function() function, {String prefix = ""}) async {
|
||||
|
||||
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);
|
||||
Computer.shared().turnOn(workersCount: 4, verbose: kDebugMode).ignore();
|
||||
CryptoUtil.init();
|
||||
await PreferenceService.instance.init();
|
||||
await CodeStore.instance.init();
|
||||
|
||||
13
auth/lib/models/account/two_factor.dart
Normal file
13
auth/lib/models/account/two_factor.dart
Normal file
@@ -0,0 +1,13 @@
|
||||
enum TwoFactorType { totp, passkey }
|
||||
|
||||
// ToString for TwoFactorType
|
||||
String twoFactorTypeToString(TwoFactorType type) {
|
||||
switch (type) {
|
||||
case TwoFactorType.totp:
|
||||
return "totp";
|
||||
case TwoFactorType.passkey:
|
||||
return "passkey";
|
||||
default:
|
||||
return type.name;
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,8 @@ import 'package:ente_auth/ui/common/gradient_button.dart';
|
||||
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
|
||||
import 'package:ente_auth/ui/components/models/button_result.dart';
|
||||
import 'package:ente_auth/ui/home_page.dart';
|
||||
import 'package:ente_auth/ui/settings/developer_settings_page.dart';
|
||||
import 'package:ente_auth/ui/settings/developer_settings_widget.dart';
|
||||
import 'package:ente_auth/ui/settings/language_picker.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/navigation_util.dart';
|
||||
@@ -33,8 +35,12 @@ class OnboardingPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _OnboardingPageState extends State<OnboardingPage> {
|
||||
static const kDeveloperModeTapCountThreshold = 7;
|
||||
|
||||
late StreamSubscription<TriggerLogoutEvent> _triggerLogoutEvent;
|
||||
|
||||
int _developerModeTapCount = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_triggerLogoutEvent =
|
||||
@@ -56,114 +62,143 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||
final l10n = context.l10n;
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
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();
|
||||
routeToPage(
|
||||
context,
|
||||
LanguageSelectorPage(
|
||||
appSupportedLocales,
|
||||
(locale) async {
|
||||
await setLocale(locale);
|
||||
App.setLocale(context, locale);
|
||||
},
|
||||
locale,
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
_developerModeTapCount++;
|
||||
if (_developerModeTapCount >= kDeveloperModeTapCountThreshold) {
|
||||
_developerModeTapCount = 0;
|
||||
final result = await showChoiceDialog(
|
||||
context,
|
||||
title: l10n.developerSettings,
|
||||
firstButtonLabel: l10n.yes,
|
||||
body: l10n.developerSettingsWarning,
|
||||
isDismissible: false,
|
||||
);
|
||||
if (result?.action == ButtonAction.first) {
|
||||
await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return const DeveloperSettingsPage();
|
||||
},
|
||||
),
|
||||
);
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
},
|
||||
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,
|
||||
),
|
||||
).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: 100),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: GradientButton(
|
||||
onTap: _navigateToSignUpPage,
|
||||
text: l10n.newUser,
|
||||
),
|
||||
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: 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: 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(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -194,6 +229,7 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||
}
|
||||
if (hasOptedBefore || result?.action == ButtonAction.first) {
|
||||
await Configuration.instance.optForOfflineMode();
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
|
||||
23
auth/lib/services/auth_feature_flag.dart
Normal file
23
auth/lib/services/auth_feature_flag.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class FeatureFlagService {
|
||||
FeatureFlagService._privateConstructor();
|
||||
static final FeatureFlagService instance =
|
||||
FeatureFlagService._privateConstructor();
|
||||
|
||||
static final _internalUserIDs = const String.fromEnvironment(
|
||||
"internal_user_ids",
|
||||
defaultValue: "1,2,3,4,191,125,1580559962388044,1580559962392434,10000025",
|
||||
).split(",").map((element) {
|
||||
return int.parse(element);
|
||||
}).toSet();
|
||||
|
||||
bool isInternalUserOrDebugBuild() {
|
||||
final String? email = Configuration.instance.getEmail();
|
||||
final userID = Configuration.instance.getUserID();
|
||||
return (email != null && email.endsWith("@ente.io")) ||
|
||||
_internalUserIDs.contains(userID) ||
|
||||
kDebugMode;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import 'dart:math';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/errors.dart';
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/core/network.dart';
|
||||
import 'package:ente_auth/events/codes_updated_event.dart';
|
||||
import 'package:ente_auth/events/signed_in_event.dart';
|
||||
import 'package:ente_auth/events/trigger_logout_event.dart';
|
||||
@@ -26,6 +25,7 @@ enum AccountMode {
|
||||
online,
|
||||
offline,
|
||||
}
|
||||
|
||||
extension on AccountMode {
|
||||
bool get isOnline => this == AccountMode.online;
|
||||
bool get isOffline => this == AccountMode.offline;
|
||||
@@ -56,7 +56,7 @@ class AuthenticatorService {
|
||||
_prefs = await SharedPreferences.getInstance();
|
||||
_db = AuthenticatorDB.instance;
|
||||
_offlineDb = OfflineAuthenticatorDB.instance;
|
||||
_gateway = AuthenticatorGateway(Network.instance.getDio(), _config);
|
||||
_gateway = AuthenticatorGateway();
|
||||
if (Configuration.instance.hasConfiguredAccount()) {
|
||||
unawaited(onlineSync());
|
||||
}
|
||||
@@ -154,7 +154,7 @@ class AuthenticatorService {
|
||||
} else {
|
||||
debugPrint("Skipping delete since account mode is offline");
|
||||
}
|
||||
if(accountMode.isOnline) {
|
||||
if (accountMode.isOnline) {
|
||||
await _db.deleteByIDs(generatedIDs: [genID]);
|
||||
} else {
|
||||
await _offlineDb.deleteByIDs(generatedIDs: [genID]);
|
||||
@@ -163,7 +163,7 @@ class AuthenticatorService {
|
||||
|
||||
Future<bool> onlineSync() async {
|
||||
try {
|
||||
if(getAccountMode().isOffline) {
|
||||
if (getAccountMode().isOffline) {
|
||||
debugPrint("Skipping sync since account mode is offline");
|
||||
return false;
|
||||
}
|
||||
@@ -208,7 +208,7 @@ class AuthenticatorService {
|
||||
if (deletedIDs.isNotEmpty) {
|
||||
await _db.deleteByIDs(ids: deletedIDs);
|
||||
}
|
||||
_prefs.setInt(_lastEntitySyncTime, maxSyncTime);
|
||||
await _prefs.setInt(_lastEntitySyncTime, maxSyncTime);
|
||||
_logger.info("Setting synctime to " + maxSyncTime.toString());
|
||||
if (result.length == fetchLimit) {
|
||||
_logger.info("Diff limit reached, pulling again");
|
||||
@@ -253,7 +253,7 @@ class AuthenticatorService {
|
||||
}
|
||||
|
||||
Future<Uint8List> getOrCreateAuthDataKey(AccountMode mode) async {
|
||||
if(mode.isOffline) {
|
||||
if (mode.isOffline) {
|
||||
return _config.getOfflineSecretKey()!;
|
||||
}
|
||||
if (_config.getAuthSecretKey() != null) {
|
||||
|
||||
@@ -54,6 +54,7 @@ class LocalAuthenticationService {
|
||||
.setEnabled(Configuration.instance.shouldShowLockScreen());
|
||||
}
|
||||
} else {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
errorDialogTitle,
|
||||
|
||||
@@ -27,6 +27,7 @@ class NotificationService {
|
||||
_flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>();
|
||||
if (implementation != null) {
|
||||
// ignore: unawaited_futures
|
||||
implementation.requestPermission();
|
||||
}
|
||||
}
|
||||
|
||||
55
auth/lib/services/passkey_service.dart
Normal file
55
auth/lib/services/passkey_service.dart
Normal file
@@ -0,0 +1,55 @@
|
||||
import 'package:ente_auth/core/network.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class PasskeyService {
|
||||
PasskeyService._privateConstructor();
|
||||
static final PasskeyService instance = PasskeyService._privateConstructor();
|
||||
|
||||
final _enteDio = Network.instance.enteDio;
|
||||
|
||||
Future<String> getJwtToken() async {
|
||||
final response = await _enteDio.get(
|
||||
"/users/accounts-token",
|
||||
);
|
||||
return response.data!["accountsToken"] as String;
|
||||
}
|
||||
|
||||
Future<bool> isPasskeyRecoveryEnabled() async {
|
||||
final response = await _enteDio.get(
|
||||
"/users/two-factor/recovery-status",
|
||||
);
|
||||
return response.data!["isPasskeyRecoveryEnabled"] as bool;
|
||||
}
|
||||
|
||||
Future<void> configurePasskeyRecovery(
|
||||
String secret,
|
||||
String userEncryptedSecret,
|
||||
String userSecretNonce,
|
||||
) async {
|
||||
await _enteDio.post(
|
||||
"/users/two-factor/passkeys/configure-recovery",
|
||||
data: {
|
||||
"secret": secret,
|
||||
"userSecretCipher": userEncryptedSecret,
|
||||
"userSecretNonce": userSecretNonce,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> openPasskeyPage(BuildContext context) async {
|
||||
try {
|
||||
final jwtToken = await getJwtToken();
|
||||
final url = "https://accounts.ente.io/account-handoff?token=$jwtToken";
|
||||
await launchUrlString(
|
||||
url,
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
} catch (e) {
|
||||
Logger('PasskeyService').severe("failed to open passkey page", e);
|
||||
showGenericErrorDialog(context: context).ignore();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ente_auth/core/constants.dart';
|
||||
@@ -70,9 +71,11 @@ class UpdateService {
|
||||
if (shouldUpdate &&
|
||||
hasBeen3DaysSinceLastNotification &&
|
||||
_latestVersion!.shouldNotify!) {
|
||||
NotificationService.instance.showNotification(
|
||||
"Update available",
|
||||
"Click to install our best version yet",
|
||||
unawaited(
|
||||
NotificationService.instance.showNotification(
|
||||
"Update available",
|
||||
"Click to install our best version yet",
|
||||
),
|
||||
);
|
||||
await _prefs.setInt(kUpdateAvailableShownTimeKey, now);
|
||||
} else {
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/core/network.dart';
|
||||
import 'package:ente_auth/events/user_details_changed_event.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/models/account/two_factor.dart';
|
||||
import 'package:ente_auth/models/api/user/srp.dart';
|
||||
import 'package:ente_auth/models/delete_account.dart';
|
||||
import 'package:ente_auth/models/key_attributes.dart';
|
||||
@@ -26,6 +27,7 @@ import 'package:ente_auth/ui/account/password_reentry_page.dart';
|
||||
import 'package:ente_auth/ui/account/recovery_page.dart';
|
||||
import 'package:ente_auth/ui/common/progress_dialog.dart';
|
||||
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';
|
||||
@@ -146,18 +148,18 @@ class UserService {
|
||||
final userDetails = UserDetails.fromMap(response.data);
|
||||
if (shouldCache) {
|
||||
if (userDetails.profileData != null) {
|
||||
_preferences.setBool(
|
||||
await _preferences.setBool(
|
||||
kIsEmailMFAEnabled,
|
||||
userDetails.profileData!.isEmailMFAEnabled,
|
||||
);
|
||||
_preferences.setBool(
|
||||
await _preferences.setBool(
|
||||
kCanDisableEmailMFA,
|
||||
userDetails.profileData!.canDisableEmailMFA,
|
||||
);
|
||||
}
|
||||
// handle email change from different client
|
||||
if (userDetails.email != _config.getEmail()) {
|
||||
setEmail(userDetails.email);
|
||||
await setEmail(userDetails.email);
|
||||
}
|
||||
}
|
||||
return userDetails;
|
||||
@@ -264,6 +266,34 @@ class UserService {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> onPassKeyVerified(BuildContext context, Map response) async {
|
||||
final userPassword = Configuration.instance.getVolatilePassword();
|
||||
if (userPassword == null) throw Exception("volatile password is null");
|
||||
|
||||
await _saveConfiguration(response);
|
||||
|
||||
Widget page;
|
||||
if (Configuration.instance.getEncryptedToken() != null) {
|
||||
await Configuration.instance.decryptSecretsAndGetKeyEncKey(
|
||||
userPassword,
|
||||
Configuration.instance.getKeyAttributes()!,
|
||||
);
|
||||
page = const HomePage();
|
||||
} else {
|
||||
throw Exception("unexpected response during passkey verification");
|
||||
}
|
||||
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return page;
|
||||
},
|
||||
),
|
||||
(route) => route.isFirst,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> verifyEmail(
|
||||
BuildContext context,
|
||||
String ott, {
|
||||
@@ -303,6 +333,7 @@ class UserService {
|
||||
);
|
||||
}
|
||||
}
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
@@ -326,6 +357,7 @@ class UserService {
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
} else {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.l10n.incorrectCode,
|
||||
@@ -335,6 +367,7 @@ class UserService {
|
||||
} catch (e) {
|
||||
await dialog.hide();
|
||||
_logger.severe(e);
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.l10n.oops,
|
||||
@@ -371,6 +404,7 @@ class UserService {
|
||||
Bus.instance.fire(UserDetailsChangedEvent());
|
||||
return;
|
||||
}
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.l10n.oops,
|
||||
@@ -379,12 +413,14 @@ class UserService {
|
||||
} on DioError catch (e) {
|
||||
await dialog.hide();
|
||||
if (e.response != null && e.response!.statusCode == 403) {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.l10n.oops,
|
||||
context.l10n.thisEmailIsAlreadyInUse,
|
||||
);
|
||||
} else {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.l10n.incorrectCode,
|
||||
@@ -394,6 +430,7 @@ class UserService {
|
||||
} catch (e) {
|
||||
await dialog.hide();
|
||||
_logger.severe(e);
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.l10n.oops,
|
||||
@@ -487,9 +524,9 @@ class UserService {
|
||||
final clientS = client.calculateSecret(serverB);
|
||||
final clientM = client.calculateClientEvidenceMessage();
|
||||
// ignore: unused_local_variable
|
||||
late Response srpCompleteResponse;
|
||||
late Response _;
|
||||
if (setKeysRequest == null) {
|
||||
srpCompleteResponse = await _enteDio.post(
|
||||
_ = await _enteDio.post(
|
||||
"/users/srp/complete",
|
||||
data: {
|
||||
'setupID': setupSRPResponse.setupID,
|
||||
@@ -497,7 +534,7 @@ class UserService {
|
||||
},
|
||||
);
|
||||
} else {
|
||||
srpCompleteResponse = await _enteDio.post(
|
||||
_ = await _enteDio.post(
|
||||
"/users/srp/update",
|
||||
data: {
|
||||
'setupID': setupSRPResponse.setupID,
|
||||
@@ -581,11 +618,15 @@ class UserService {
|
||||
},
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
Widget page;
|
||||
Widget? page;
|
||||
final String passkeySessionID = response.data["passkeySessionID"];
|
||||
final String twoFASessionID = response.data["twoFactorSessionID"];
|
||||
Configuration.instance.setVolatilePassword(userPassword);
|
||||
|
||||
if (twoFASessionID.isNotEmpty) {
|
||||
page = TwoFactorAuthenticationPage(twoFASessionID);
|
||||
} else if (passkeySessionID.isNotEmpty) {
|
||||
page = PasskeyPage(passkeySessionID);
|
||||
} else {
|
||||
await _saveConfiguration(response);
|
||||
if (Configuration.instance.getEncryptedToken() != null) {
|
||||
@@ -600,10 +641,11 @@ class UserService {
|
||||
}
|
||||
}
|
||||
await dialog.hide();
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return page;
|
||||
return page!;
|
||||
},
|
||||
),
|
||||
(route) => route.isFirst,
|
||||
@@ -677,6 +719,7 @@ class UserService {
|
||||
if (response.statusCode == 200) {
|
||||
showShortToast(context, context.l10n.authenticationSuccessful);
|
||||
await _saveConfiguration(response);
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
@@ -691,6 +734,7 @@ class UserService {
|
||||
_logger.severe(e);
|
||||
if (e.response != null && e.response!.statusCode == 404) {
|
||||
showToast(context, "Session expired");
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
@@ -700,6 +744,7 @@ class UserService {
|
||||
(route) => route.isFirst,
|
||||
);
|
||||
} else {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.l10n.incorrectCode,
|
||||
@@ -709,6 +754,7 @@ class UserService {
|
||||
} catch (e) {
|
||||
await dialog.hide();
|
||||
_logger.severe(e);
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.l10n.oops,
|
||||
@@ -717,7 +763,11 @@ class UserService {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> recoverTwoFactor(BuildContext context, String sessionID) async {
|
||||
Future<void> recoverTwoFactor(
|
||||
BuildContext context,
|
||||
String sessionID,
|
||||
TwoFactorType type,
|
||||
) async {
|
||||
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
|
||||
await dialog.show();
|
||||
try {
|
||||
@@ -725,13 +775,16 @@ class UserService {
|
||||
_config.getHttpEndpoint() + "/users/two-factor/recover",
|
||||
queryParameters: {
|
||||
"sessionID": sessionID,
|
||||
"twoFactorType": twoFactorTypeToString(type),
|
||||
},
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return TwoFactorRecoveryPage(
|
||||
type,
|
||||
sessionID,
|
||||
response.data["encryptedSecret"],
|
||||
response.data["secretDecryptionNonce"],
|
||||
@@ -742,9 +795,11 @@ class UserService {
|
||||
);
|
||||
}
|
||||
} on DioError catch (e) {
|
||||
await dialog.hide();
|
||||
_logger.severe(e);
|
||||
if (e.response != null && e.response!.statusCode == 404) {
|
||||
showToast(context, context.l10n.sessionExpired);
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
@@ -754,6 +809,7 @@ class UserService {
|
||||
(route) => route.isFirst,
|
||||
);
|
||||
} else {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.l10n.oops,
|
||||
@@ -761,7 +817,9 @@ class UserService {
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
await dialog.hide();
|
||||
_logger.severe(e);
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.l10n.oops,
|
||||
@@ -774,6 +832,7 @@ class UserService {
|
||||
|
||||
Future<void> removeTwoFactor(
|
||||
BuildContext context,
|
||||
TwoFactorType type,
|
||||
String sessionID,
|
||||
String recoveryKey,
|
||||
String encryptedSecret,
|
||||
@@ -813,6 +872,7 @@ class UserService {
|
||||
data: {
|
||||
"sessionID": sessionID,
|
||||
"secret": secret,
|
||||
"twoFactorType": twoFactorTypeToString(type),
|
||||
},
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
@@ -821,6 +881,7 @@ class UserService {
|
||||
context.l10n.twofactorAuthenticationSuccessfullyReset,
|
||||
);
|
||||
await _saveConfiguration(response);
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
@@ -831,9 +892,11 @@ class UserService {
|
||||
);
|
||||
}
|
||||
} on DioError catch (e) {
|
||||
await dialog.hide();
|
||||
_logger.severe(e);
|
||||
if (e.response != null && e.response!.statusCode == 404) {
|
||||
showToast(context, "Session expired");
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
@@ -843,6 +906,7 @@ class UserService {
|
||||
(route) => route.isFirst,
|
||||
);
|
||||
} else {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.l10n.oops,
|
||||
@@ -850,7 +914,9 @@ class UserService {
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
await dialog.hide();
|
||||
_logger.severe(e);
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.l10n.oops,
|
||||
@@ -861,16 +927,19 @@ class UserService {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _saveConfiguration(Response response) async {
|
||||
await Configuration.instance.setUserID(response.data["id"]);
|
||||
if (response.data["encryptedToken"] != null) {
|
||||
Future<void> _saveConfiguration(dynamic response) async {
|
||||
final responseData = response is Map ? response : response.data as Map?;
|
||||
if (responseData == null) return;
|
||||
|
||||
await Configuration.instance.setUserID(responseData["id"]);
|
||||
if (responseData["encryptedToken"] != null) {
|
||||
await Configuration.instance
|
||||
.setEncryptedToken(response.data["encryptedToken"]);
|
||||
.setEncryptedToken(responseData["encryptedToken"]);
|
||||
await Configuration.instance.setKeyAttributes(
|
||||
KeyAttributes.fromMap(response.data["keyAttributes"]),
|
||||
KeyAttributes.fromMap(responseData["keyAttributes"]),
|
||||
);
|
||||
} else {
|
||||
await Configuration.instance.setToken(response.data["token"]);
|
||||
await Configuration.instance.setToken(responseData["token"]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -890,7 +959,7 @@ class UserService {
|
||||
"isEnabled": isEnabled,
|
||||
},
|
||||
);
|
||||
_preferences.setBool(kIsEmailMFAEnabled, isEnabled);
|
||||
await _preferences.setBool(kIsEmailMFAEnabled, isEnabled);
|
||||
} catch (e) {
|
||||
_logger.severe("Failed to update email mfa", e);
|
||||
rethrow;
|
||||
|
||||
@@ -15,7 +15,8 @@ class OfflineAuthenticatorDB {
|
||||
static const entityTable = 'entities';
|
||||
|
||||
OfflineAuthenticatorDB._privateConstructor();
|
||||
static final OfflineAuthenticatorDB instance = OfflineAuthenticatorDB._privateConstructor();
|
||||
static final OfflineAuthenticatorDB instance =
|
||||
OfflineAuthenticatorDB._privateConstructor();
|
||||
|
||||
static Future<Database>? _dbFuture;
|
||||
|
||||
@@ -26,7 +27,7 @@ class OfflineAuthenticatorDB {
|
||||
|
||||
Future<Database> _initDatabase() async {
|
||||
final Directory documentsDirectory =
|
||||
await getApplicationDocumentsDirectory();
|
||||
await getApplicationDocumentsDirectory();
|
||||
final String path = join(documentsDirectory.path, _databaseName);
|
||||
debugPrint(path);
|
||||
return await openDatabase(
|
||||
@@ -70,10 +71,10 @@ class OfflineAuthenticatorDB {
|
||||
}
|
||||
|
||||
Future<int> updateEntry(
|
||||
int generatedID,
|
||||
String encData,
|
||||
String header,
|
||||
) async {
|
||||
int generatedID,
|
||||
String encData,
|
||||
String header,
|
||||
) async {
|
||||
final db = await instance.database;
|
||||
final int timeInMicroSeconds = DateTime.now().microsecondsSinceEpoch;
|
||||
int affectedRows = await db.update(
|
||||
|
||||
@@ -7,10 +7,6 @@ class UserStore {
|
||||
late SharedPreferences _preferences;
|
||||
|
||||
static final UserStore instance = UserStore._privateConstructor();
|
||||
static const endpoint = String.fromEnvironment(
|
||||
"endpoint",
|
||||
defaultValue: "https://api.ente.io",
|
||||
);
|
||||
|
||||
Future<void> init() async {
|
||||
_preferences = await SharedPreferences.getInstance();
|
||||
|
||||
@@ -236,7 +236,7 @@ class DeleteAccountPage extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
// ignore: unawaited_futures
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
|
||||
@@ -61,7 +61,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
isFormValid: _emailIsValid,
|
||||
buttonText: context.l10n.logInLabel,
|
||||
onPressedFunction: () async {
|
||||
UserService.instance.setEmail(_email!);
|
||||
await UserService.instance.setEmail(_email!);
|
||||
Configuration.instance.resetVolatilePassword();
|
||||
SrpAttributes? attr;
|
||||
bool isEmailVerificationEnabled = true;
|
||||
@@ -74,6 +74,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
}
|
||||
}
|
||||
if (attr != null && !isEmailVerificationEnabled) {
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
|
||||
@@ -23,6 +23,7 @@ Future<void> autoLogoutAlert(BuildContext context) async {
|
||||
int pendingSyncCount =
|
||||
await AuthenticatorDB.instance.getNeedSyncCount();
|
||||
if (pendingSyncCount > 0) {
|
||||
// ignore: unawaited_futures
|
||||
showChoiceActionSheet(
|
||||
context,
|
||||
title: l10n.pendingSyncs,
|
||||
|
||||
@@ -399,6 +399,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
} catch (e, s) {
|
||||
_logger.severe(e, s);
|
||||
await dialog.hide();
|
||||
// ignore: unawaited_futures
|
||||
showGenericErrorDialog(context: context);
|
||||
}
|
||||
}
|
||||
@@ -446,6 +447,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
await UserService.instance.setAttributes(result);
|
||||
await dialog.hide();
|
||||
Configuration.instance.resetVolatilePassword();
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
@@ -457,10 +459,11 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
} catch (e, s) {
|
||||
_logger.severe(e, s);
|
||||
await dialog.hide();
|
||||
// ignore: unawaited_futures
|
||||
showGenericErrorDialog(context: context);
|
||||
}
|
||||
}
|
||||
|
||||
// ignore: unawaited_futures
|
||||
routeToPage(
|
||||
context,
|
||||
RecoveryKeyPage(
|
||||
@@ -476,12 +479,14 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
_logger.severe(e);
|
||||
await dialog.hide();
|
||||
if (e is UnsupportedError) {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.l10n.insecureDevice,
|
||||
context.l10n.sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease,
|
||||
);
|
||||
} else {
|
||||
// ignore: unawaited_futures
|
||||
showGenericErrorDialog(context: context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +116,7 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
|
||||
firstButtonLabel: context.l10n.useRecoveryKey,
|
||||
);
|
||||
if (dialogChoice!.action == ButtonAction.first) {
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
|
||||
@@ -54,6 +54,7 @@ class _RecoveryPageState extends State<RecoveryPage> {
|
||||
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) {
|
||||
@@ -72,6 +73,7 @@ class _RecoveryPageState extends State<RecoveryPage> {
|
||||
if (e is AssertionError) {
|
||||
errMessage = '$errMessage : ${e.message}';
|
||||
}
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(context, "Incorrect recovery key", errMessage);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -78,7 +78,7 @@ class _RequestPasswordVerificationPageState
|
||||
onPressedFunction: () async {
|
||||
FocusScope.of(context).unfocus();
|
||||
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
|
||||
dialog.show();
|
||||
await dialog.show();
|
||||
try {
|
||||
final attributes = Configuration.instance.getKeyAttributes()!;
|
||||
final Uint8List keyEncryptionKey = await CryptoUtil.deriveKey(
|
||||
@@ -92,17 +92,18 @@ class _RequestPasswordVerificationPageState
|
||||
keyEncryptionKey,
|
||||
Sodium.base642bin(attributes.keyDecryptionNonce),
|
||||
);
|
||||
dialog.show();
|
||||
await dialog.show();
|
||||
// pop
|
||||
await widget.onPasswordVerified(keyEncryptionKey);
|
||||
dialog.hide();
|
||||
await dialog.hide();
|
||||
Navigator.of(context).pop(true);
|
||||
} catch (e, s) {
|
||||
_logger.severe("Error while verifying password", e, s);
|
||||
dialog.hide();
|
||||
await dialog.hide();
|
||||
if (widget.onPasswordError != null) {
|
||||
widget.onPasswordError!();
|
||||
} else {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.l10n.incorrectPasswordTitle,
|
||||
|
||||
@@ -121,6 +121,7 @@ class _SessionsPageState extends State<SessionsPage> {
|
||||
} catch (e) {
|
||||
await dialog.hide();
|
||||
_logger.severe('failed to terminate');
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.l10n.oops,
|
||||
@@ -184,7 +185,7 @@ class _SessionsPageState extends State<SessionsPage> {
|
||||
if (isLoggingOutFromThisDevice) {
|
||||
await UserService.instance.logout(context);
|
||||
} else {
|
||||
_terminateSession(session);
|
||||
await _terminateSession(session);
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
@@ -92,6 +92,7 @@ class _VerifyRecoveryPageState extends State<VerifyRecoveryPage> {
|
||||
String recoveryKey;
|
||||
try {
|
||||
recoveryKey = Sodium.bin2hex(Configuration.instance.getRecoveryKey());
|
||||
// ignore: unawaited_futures
|
||||
routeToPage(
|
||||
context,
|
||||
RecoveryKeyPage(
|
||||
@@ -104,6 +105,7 @@ class _VerifyRecoveryPageState extends State<VerifyRecoveryPage> {
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
// ignore: unawaited_futures
|
||||
showGenericErrorDialog(context: context);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -356,6 +356,7 @@ class _CodeWidgetState extends State<CodeWidget> {
|
||||
await FlutterClipboard.copy(content);
|
||||
showToast(context, confirmationMessage);
|
||||
if (Platform.isAndroid && shouldMinimizeOnCopy) {
|
||||
// ignore: unawaited_futures
|
||||
MoveToBackground.moveTaskToBack();
|
||||
}
|
||||
}
|
||||
@@ -385,7 +386,7 @@ class _CodeWidgetState extends State<CodeWidget> {
|
||||
),
|
||||
);
|
||||
if (code != null) {
|
||||
CodeStore.instance.addCode(code);
|
||||
await CodeStore.instance.addCode(code);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -146,6 +146,7 @@ class ProgressDialog {
|
||||
try {
|
||||
if (!_isShowing) {
|
||||
_dialog = _Body();
|
||||
// ignore: unawaited_futures
|
||||
showDialog<dynamic>(
|
||||
context: _context!,
|
||||
barrierDismissible: _barrierDismissible,
|
||||
|
||||
@@ -120,7 +120,7 @@ class _HomePageState extends State<HomePage> {
|
||||
),
|
||||
);
|
||||
if (code != null) {
|
||||
CodeStore.instance.addCode(code);
|
||||
await CodeStore.instance.addCode(code);
|
||||
// Focus the new code by searching
|
||||
if (_codes.length > 2) {
|
||||
_focusNewCode(code);
|
||||
@@ -137,7 +137,7 @@ class _HomePageState extends State<HomePage> {
|
||||
),
|
||||
);
|
||||
if (code != null) {
|
||||
CodeStore.instance.addCode(code);
|
||||
await CodeStore.instance.addCode(code);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,6 +151,7 @@ class _HomePageState extends State<HomePage> {
|
||||
return false;
|
||||
}
|
||||
if (Platform.isAndroid) {
|
||||
// ignore: unawaited_futures
|
||||
MoveToBackground.moveTaskToBack();
|
||||
return false;
|
||||
} else {
|
||||
|
||||
151
auth/lib/ui/passkey_page.dart
Normal file
151
auth/lib/ui/passkey_page.dart
Normal file
@@ -0,0 +1,151 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/models/account/two_factor.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
|
||||
import 'package:ente_auth/ui/components/models/button_type.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:uni_links/uni_links.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class PasskeyPage extends StatefulWidget {
|
||||
final String sessionID;
|
||||
|
||||
const PasskeyPage(
|
||||
this.sessionID, {
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<PasskeyPage> createState() => _PasskeyPageState();
|
||||
}
|
||||
|
||||
class _PasskeyPageState extends State<PasskeyPage> {
|
||||
final Logger _logger = Logger("PasskeyPage");
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
launchPasskey();
|
||||
_initDeepLinks();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> launchPasskey() async {
|
||||
await launchUrlString(
|
||||
"https://accounts.ente.io/passkeys/flow?"
|
||||
"passkeySessionID=${widget.sessionID}"
|
||||
"&redirect=enteauth://passkey",
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _handleDeeplink(String? link) async {
|
||||
if (!context.mounted ||
|
||||
Configuration.instance.hasConfiguredAccount() ||
|
||||
link == null) {
|
||||
_logger.warning(
|
||||
'ignored deeplink: contextMounted ${context.mounted} hasConfiguredAccount ${Configuration.instance.hasConfiguredAccount()}',
|
||||
);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (mounted && link.toLowerCase().startsWith("enteauth://passkey")) {
|
||||
final String? uri = Uri.parse(link).queryParameters['response'];
|
||||
String base64String = uri!.toString();
|
||||
while (base64String.length % 4 != 0) {
|
||||
base64String += '=';
|
||||
}
|
||||
final res = utf8.decode(base64.decode(base64String));
|
||||
final json = jsonDecode(res) as Map<String, dynamic>;
|
||||
await UserService.instance.onPassKeyVerified(context, json);
|
||||
} else {
|
||||
_logger.info('ignored deeplink: $link mounted $mounted');
|
||||
}
|
||||
} catch (e, s) {
|
||||
_logger.severe('passKey: failed to handle deeplink', e, s);
|
||||
showGenericErrorDialog(context: context).ignore();
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _initDeepLinks() async {
|
||||
// Attach a listener to the stream
|
||||
linkStream.listen(
|
||||
_handleDeeplink,
|
||||
onError: (err) {
|
||||
_logger.severe(err);
|
||||
},
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
l10n.passkeyAuthTitle,
|
||||
),
|
||||
),
|
||||
body: _getBody(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getBody() {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
context.l10n.waitingForVerification,
|
||||
style: const TextStyle(
|
||||
height: 1.4,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ButtonWidget(
|
||||
buttonType: ButtonType.primary,
|
||||
labelText: context.l10n.verifyPasskey,
|
||||
onTap: () => launchPasskey(),
|
||||
),
|
||||
const Padding(padding: EdgeInsets.all(30)),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
UserService.instance.recoverTwoFactor(
|
||||
context,
|
||||
widget.sessionID,
|
||||
TwoFactorType.passkey,
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Center(
|
||||
child: Text(
|
||||
context.l10n.recoverAccount,
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ class AboutSectionWidget extends StatelessWidget {
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
// ignore: unawaited_futures
|
||||
launchUrl(Uri.parse("https://github.com/ente-io/ente"));
|
||||
},
|
||||
),
|
||||
@@ -68,6 +69,7 @@ class AboutSectionWidget extends StatelessWidget {
|
||||
await UpdateService.instance.shouldUpdate();
|
||||
await dialog.hide();
|
||||
if (shouldUpdate) {
|
||||
// ignore: unawaited_futures
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
@@ -115,6 +117,7 @@ class AboutMenuItemWidget extends StatelessWidget {
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/services/local_authentication_service.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
@@ -7,6 +6,7 @@ import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:ente_auth/ui/account/change_email_dialog.dart';
|
||||
import 'package:ente_auth/ui/account/delete_account_page.dart';
|
||||
import 'package:ente_auth/ui/account/password_entry_page.dart';
|
||||
import 'package:ente_auth/ui/account/recovery_key_page.dart';
|
||||
import 'package:ente_auth/ui/components/captioned_text_widget.dart';
|
||||
import 'package:ente_auth/ui/components/expandable_menu_item_widget.dart';
|
||||
import 'package:ente_auth/ui/components/menu_item_widget.dart';
|
||||
@@ -14,6 +14,7 @@ import 'package:ente_auth/ui/settings/common_settings.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/navigation_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
||||
|
||||
class AccountSectionWidget extends StatelessWidget {
|
||||
AccountSectionWidget({Key? key}) : super(key: key);
|
||||
@@ -47,6 +48,7 @@ class AccountSectionWidget extends StatelessWidget {
|
||||
l10n.authToChangeYourEmail,
|
||||
);
|
||||
if (hasAuthenticated) {
|
||||
// ignore: unawaited_futures
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
@@ -73,6 +75,7 @@ class AccountSectionWidget extends StatelessWidget {
|
||||
l10n.authToChangeYourPassword,
|
||||
);
|
||||
if (hasAuthenticated) {
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
@@ -86,6 +89,43 @@ class AccountSectionWidget extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: l10n.recoveryKey,
|
||||
),
|
||||
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
final hasAuthenticated = await LocalAuthenticationService.instance
|
||||
.requestLocalAuthentication(
|
||||
context,
|
||||
l10n.authToViewYourRecoveryKey,
|
||||
);
|
||||
if (hasAuthenticated) {
|
||||
String recoveryKey;
|
||||
try {
|
||||
recoveryKey =
|
||||
Sodium.bin2hex(Configuration.instance.getRecoveryKey());
|
||||
} catch (e) {
|
||||
// ignore: unawaited_futures
|
||||
showGenericErrorDialog(context: context);
|
||||
return;
|
||||
}
|
||||
// ignore: unawaited_futures
|
||||
routeToPage(
|
||||
context,
|
||||
RecoveryKeyPage(
|
||||
recoveryKey,
|
||||
l10n.ok,
|
||||
showAppBar: true,
|
||||
onDone: () {},
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: context.l10n.logout,
|
||||
@@ -106,6 +146,7 @@ class AccountSectionWidget extends StatelessWidget {
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
// ignore: unawaited_futures
|
||||
routeToPage(context, const DeleteAccountPage());
|
||||
},
|
||||
),
|
||||
@@ -115,6 +156,7 @@ class AccountSectionWidget extends StatelessWidget {
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
|
||||
void _onLogoutTapped(BuildContext context) {
|
||||
showChoiceActionSheet(
|
||||
context,
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/network.dart';
|
||||
import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/services/update_service.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:open_filex/open_filex.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class AppUpdateDialog extends StatefulWidget {
|
||||
@@ -114,115 +107,3 @@ class _AppUpdateDialogState extends State<AppUpdateDialog> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ApkDownloaderDialog extends StatefulWidget {
|
||||
final LatestVersionInfo? versionInfo;
|
||||
|
||||
const ApkDownloaderDialog(this.versionInfo, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<ApkDownloaderDialog> createState() => _ApkDownloaderDialogState();
|
||||
}
|
||||
|
||||
class _ApkDownloaderDialogState extends State<ApkDownloaderDialog> {
|
||||
String? _saveUrl;
|
||||
double? _downloadProgress;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_saveUrl = Configuration.instance.getTempDirectory() +
|
||||
"ente-" +
|
||||
widget.versionInfo!.name! +
|
||||
".apk";
|
||||
_downloadApk();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async => false,
|
||||
child: AlertDialog(
|
||||
title: const Text(
|
||||
"Downloading...",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
content: LinearProgressIndicator(
|
||||
value: _downloadProgress,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Theme.of(context).colorScheme.alternativeColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _downloadApk() async {
|
||||
try {
|
||||
if (!File(_saveUrl!).existsSync()) {
|
||||
await Network.instance.getDio().download(
|
||||
widget.versionInfo!.url!,
|
||||
_saveUrl,
|
||||
onReceiveProgress: (count, _) {
|
||||
setState(() {
|
||||
_downloadProgress = count / widget.versionInfo!.size!;
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
Navigator.of(context, rootNavigator: true).pop('dialog');
|
||||
OpenFilex.open(_saveUrl);
|
||||
} catch (e) {
|
||||
Logger("ApkDownloader").severe(e);
|
||||
final AlertDialog alert = AlertDialog(
|
||||
title: const Text("Sorry"),
|
||||
content: const Text("The download could not be completed"),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: const Text(
|
||||
"Ignore",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context, rootNavigator: true).pop('dialog');
|
||||
Navigator.of(context, rootNavigator: true).pop('dialog');
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text(
|
||||
"Retry",
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.alternativeColor,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context, rootNavigator: true).pop('dialog');
|
||||
Navigator.of(context, rootNavigator: true).pop('dialog');
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return ApkDownloaderDialog(widget.versionInfo);
|
||||
},
|
||||
barrierDismissible: false,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return alert;
|
||||
},
|
||||
barrierColor: Colors.black87,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ class DangerSectionWidget extends StatelessWidget {
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
// ignore: unawaited_futures
|
||||
routeToPage(context, const DeleteAccountPage());
|
||||
},
|
||||
),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:ente_auth/ui/components/captioned_text_widget.dart';
|
||||
@@ -36,6 +35,7 @@ class DataSectionWidget extends StatelessWidget {
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
// ignore: unawaited_futures
|
||||
routeToPage(context, ImportCodePage());
|
||||
},
|
||||
),
|
||||
|
||||
@@ -98,7 +98,7 @@ Future<void> _requestForEncryptionPassword(
|
||||
),
|
||||
);
|
||||
// get json value of data
|
||||
_exportCodes(context, jsonEncode(data.toJson()));
|
||||
await _exportCodes(context, jsonEncode(data.toJson()));
|
||||
} catch (e, s) {
|
||||
Logger("ExportWidget").severe(e, s);
|
||||
showToast(context, "Error while exporting codes.");
|
||||
|
||||
@@ -56,6 +56,7 @@ Future<void> showGoogleAuthInstruction(BuildContext context) async {
|
||||
await CodeStore.instance.addCode(code, shouldSync: false);
|
||||
}
|
||||
unawaited(AuthenticatorService.instance.onlineSync());
|
||||
// ignore: unawaited_futures
|
||||
importSuccessDialog(context, codes.length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,29 +19,29 @@ class ImportService {
|
||||
Future<void> initiateImport(BuildContext context, ImportType type) async {
|
||||
switch (type) {
|
||||
case ImportType.plainText:
|
||||
showImportInstructionDialog(context);
|
||||
await showImportInstructionDialog(context);
|
||||
break;
|
||||
case ImportType.encrypted:
|
||||
showEncryptedImportInstruction(context);
|
||||
await showEncryptedImportInstruction(context);
|
||||
break;
|
||||
case ImportType.ravio:
|
||||
showRaivoImportInstruction(context);
|
||||
await showRaivoImportInstruction(context);
|
||||
break;
|
||||
case ImportType.googleAuthenticator:
|
||||
showGoogleAuthInstruction(context);
|
||||
await showGoogleAuthInstruction(context);
|
||||
// showToast(context, 'coming soon');
|
||||
break;
|
||||
case ImportType.aegis:
|
||||
showAegisImportInstruction(context);
|
||||
await showAegisImportInstruction(context);
|
||||
break;
|
||||
case ImportType.twoFas:
|
||||
show2FasImportInstruction(context);
|
||||
await show2FasImportInstruction(context);
|
||||
break;
|
||||
case ImportType.bitwarden:
|
||||
showBitwardenImportInstruction(context);
|
||||
await showBitwardenImportInstruction(context);
|
||||
break;
|
||||
case ImportType.lastpass:
|
||||
showLastpassImportInstruction(context);
|
||||
await showLastpassImportInstruction(context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ class ImportCodePage extends StatelessWidget {
|
||||
index != importOptions.length - 1,
|
||||
isTopBorderRadiusRemoved: index != 0,
|
||||
onTap: () async {
|
||||
ImportService().initiateImport(context, type);
|
||||
await ImportService().initiateImport(context, type);
|
||||
// routeToPage(context, ImportCodePage());
|
||||
// _showImportInstructionDialog(context);
|
||||
},
|
||||
|
||||
90
auth/lib/ui/settings/developer_settings_page.dart
Normal file
90
auth/lib/ui/settings/developer_settings_page.dart
Normal file
@@ -0,0 +1,90 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/ui/common/gradient_button.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class DeveloperSettingsPage extends StatefulWidget {
|
||||
const DeveloperSettingsPage({super.key});
|
||||
|
||||
@override
|
||||
_DeveloperSettingsPageState createState() => _DeveloperSettingsPageState();
|
||||
}
|
||||
|
||||
class _DeveloperSettingsPageState extends State<DeveloperSettingsPage> {
|
||||
final _logger = Logger('DeveloperSettingsPage');
|
||||
final _urlController = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_urlController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_logger.info(
|
||||
"Current endpoint is: " + Configuration.instance.getHttpEndpoint(),
|
||||
);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.l10n.developerSettings),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
TextField(
|
||||
controller: _urlController,
|
||||
decoration: InputDecoration(
|
||||
labelText: context.l10n.serverEndpoint,
|
||||
hintText: Configuration.instance.getHttpEndpoint(),
|
||||
),
|
||||
autofocus: true,
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
GradientButton(
|
||||
onTap: () async {
|
||||
String url = _urlController.text;
|
||||
_logger.info("Entered endpoint: " + url);
|
||||
try {
|
||||
final uri = Uri.parse(url);
|
||||
if ((uri.scheme == "http" || uri.scheme == "https")) {
|
||||
await _ping(url);
|
||||
await Configuration.instance.setHttpEndpoint(url);
|
||||
showToast(context, context.l10n.endpointUpdatedMessage);
|
||||
Navigator.of(context).pop();
|
||||
} else {
|
||||
throw const FormatException();
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.l10n.invalidEndpoint,
|
||||
context.l10n.invalidEndpointMessage,
|
||||
);
|
||||
}
|
||||
},
|
||||
text: context.l10n.saveAction,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _ping(String endpoint) async {
|
||||
try {
|
||||
final response = await Dio().get(endpoint + '/ping');
|
||||
if (response.data['message'] != 'pong') {
|
||||
throw Exception('Invalid response');
|
||||
}
|
||||
} catch (e) {
|
||||
throw Exception('Error occurred: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
27
auth/lib/ui/settings/developer_settings_widget.dart
Normal file
27
auth/lib/ui/settings/developer_settings_widget.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/constants.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DeveloperSettingsWidget extends StatelessWidget {
|
||||
const DeveloperSettingsWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final endpoint = Configuration.instance.getHttpEndpoint();
|
||||
if (endpoint != kDefaultProductionEndpoint) {
|
||||
final endpointURI = Uri.parse(endpoint);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 20),
|
||||
child: Text(
|
||||
context.l10n.customEndpoint(
|
||||
endpointURI.host + ":" + endpointURI.port.toString(),
|
||||
),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,7 @@ class _AdvancedSectionWidgetState extends State<AdvancedSectionWidget> {
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
final locale = await getLocale();
|
||||
// ignore: unawaited_futures
|
||||
routeToPage(
|
||||
context,
|
||||
LanguageSelectorPage(
|
||||
|
||||
@@ -4,10 +4,11 @@ import 'dart:typed_data';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/models/user_details.dart';
|
||||
import 'package:ente_auth/services/auth_feature_flag.dart';
|
||||
import 'package:ente_auth/services/local_authentication_service.dart';
|
||||
import 'package:ente_auth/services/passkey_service.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:ente_auth/ui/account/recovery_key_page.dart';
|
||||
import 'package:ente_auth/ui/account/request_pwd_verification_page.dart';
|
||||
import 'package:ente_auth/ui/account/sessions_page.dart';
|
||||
import 'package:ente_auth/ui/components/captioned_text_widget.dart';
|
||||
@@ -20,7 +21,7 @@ import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/navigation_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class SecuritySectionWidget extends StatefulWidget {
|
||||
const SecuritySectionWidget({Key? key}) : super(key: key);
|
||||
@@ -32,6 +33,7 @@ class SecuritySectionWidget extends StatefulWidget {
|
||||
class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
|
||||
final _config = Configuration.instance;
|
||||
late bool _hasLoggedIn;
|
||||
final Logger _logger = Logger('SecuritySectionWidget');
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -63,42 +65,21 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
|
||||
// We don't know if the user can disable MFA yet, so we fetch the info
|
||||
UserService.instance.getUserDetailsV2().ignore();
|
||||
}
|
||||
final bool isInternalUser =
|
||||
FeatureFlagService.instance.isInternalUserOrDebugBuild();
|
||||
children.addAll([
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: l10n.recoveryKey,
|
||||
if (isInternalUser) sectionOptionSpacing,
|
||||
if (isInternalUser)
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: l10n.passkey,
|
||||
),
|
||||
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async => await onPasskeyClick(context),
|
||||
),
|
||||
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
final hasAuthenticated = await LocalAuthenticationService.instance
|
||||
.requestLocalAuthentication(
|
||||
context,
|
||||
l10n.authToViewYourRecoveryKey,
|
||||
);
|
||||
if (hasAuthenticated) {
|
||||
String recoveryKey;
|
||||
try {
|
||||
recoveryKey =
|
||||
Sodium.bin2hex(Configuration.instance.getRecoveryKey());
|
||||
} catch (e) {
|
||||
showGenericErrorDialog(context: context);
|
||||
return;
|
||||
}
|
||||
routeToPage(
|
||||
context,
|
||||
RecoveryKeyPage(
|
||||
recoveryKey,
|
||||
l10n.ok,
|
||||
showAppBar: true,
|
||||
onDone: () {},
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: l10n.emailVerificationToggle,
|
||||
@@ -137,6 +118,7 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
|
||||
context.l10n.authToViewYourActiveSessions,
|
||||
);
|
||||
if (hasAuthenticated) {
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
@@ -179,6 +161,31 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> onPasskeyClick(BuildContext buildContext) async {
|
||||
try {
|
||||
final isPassKeyResetEnabled =
|
||||
await PasskeyService.instance.isPasskeyRecoveryEnabled();
|
||||
if (!isPassKeyResetEnabled) {
|
||||
final Uint8List recoveryKey = Configuration.instance.getRecoveryKey();
|
||||
final resetKey = CryptoUtil.generateKey();
|
||||
final resetKeyBase64 = CryptoUtil.bin2base64(resetKey);
|
||||
final encryptionResult = CryptoUtil.encryptSync(
|
||||
resetKey,
|
||||
recoveryKey,
|
||||
);
|
||||
await PasskeyService.instance.configurePasskeyRecovery(
|
||||
resetKeyBase64,
|
||||
CryptoUtil.bin2base64(encryptionResult.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptionResult.nonce!),
|
||||
);
|
||||
}
|
||||
PasskeyService.instance.openPasskeyPage(buildContext).ignore();
|
||||
} catch (e, s) {
|
||||
_logger.severe("failed to open passkey page", e, s);
|
||||
await showGenericErrorDialog(context: context);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateEmailMFA(bool enableEmailMFA) async {
|
||||
try {
|
||||
final UserDetails details =
|
||||
|
||||
@@ -81,6 +81,7 @@ class SocialsMenuItemWidget extends StatelessWidget {
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
// ignore: unawaited_futures
|
||||
launchUrlString(
|
||||
url,
|
||||
mode: launchInExternalApp
|
||||
|
||||
@@ -42,6 +42,7 @@ class _SupportSectionWidgetState extends State<SupportSectionWidget> {
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
// ignore: unawaited_futures
|
||||
showModalBottomSheet<void>(
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
barrierColor: Colors.black87,
|
||||
@@ -61,6 +62,7 @@ class _SupportSectionWidgetState extends State<SupportSectionWidget> {
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
// ignore: unawaited_futures
|
||||
launchUrlString(
|
||||
githubDiscussionsUrl,
|
||||
mode: LaunchMode.externalApplication,
|
||||
|
||||
@@ -16,6 +16,7 @@ import 'package:ente_auth/ui/settings/account_section_widget.dart';
|
||||
import 'package:ente_auth/ui/settings/app_version_widget.dart';
|
||||
import 'package:ente_auth/ui/settings/data/data_section_widget.dart';
|
||||
import 'package:ente_auth/ui/settings/data/export_widget.dart';
|
||||
import 'package:ente_auth/ui/settings/developer_settings_widget.dart';
|
||||
import 'package:ente_auth/ui/settings/general_section_widget.dart';
|
||||
import 'package:ente_auth/ui/settings/security_section_widget.dart';
|
||||
import 'package:ente_auth/ui/settings/social_section_widget.dart';
|
||||
@@ -149,6 +150,7 @@ class SettingsPage extends StatelessWidget {
|
||||
sectionSpacing,
|
||||
const AboutSectionWidget(),
|
||||
const AppVersionWidget(),
|
||||
const DeveloperSettingsWidget(),
|
||||
const SupportDevWidget(),
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 60),
|
||||
|
||||
@@ -56,6 +56,7 @@ class _LockScreenState extends State<LockScreen> with WidgetsBindingObserver {
|
||||
text: context.l10n.unlock,
|
||||
iconData: Icons.lock_open_outlined,
|
||||
onTap: () async {
|
||||
// ignore: unawaited_futures
|
||||
_showLockScreen(source: "tapUnlock");
|
||||
},
|
||||
),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/models/account/two_factor.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/ui/lifecycle_event_handler.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -119,7 +120,7 @@ class _TwoFactorAuthenticationPageState
|
||||
child: OutlinedButton(
|
||||
onPressed: _code.length == 6
|
||||
? () async {
|
||||
_verifyTwoFactorCode(_code);
|
||||
await _verifyTwoFactorCode(_code);
|
||||
}
|
||||
: null,
|
||||
child: Text(l10n.verify),
|
||||
@@ -129,7 +130,11 @@ class _TwoFactorAuthenticationPageState
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
UserService.instance.recoverTwoFactor(context, widget.sessionID);
|
||||
UserService.instance.recoverTwoFactor(
|
||||
context,
|
||||
widget.sessionID,
|
||||
TwoFactorType.totp,
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/models/account/two_factor.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -9,8 +10,10 @@ class TwoFactorRecoveryPage extends StatefulWidget {
|
||||
final String sessionID;
|
||||
final String encryptedSecret;
|
||||
final String secretDecryptionNonce;
|
||||
final TwoFactorType type;
|
||||
|
||||
const TwoFactorRecoveryPage(
|
||||
this.type,
|
||||
this.sessionID,
|
||||
this.encryptedSecret,
|
||||
this.secretDecryptionNonce, {
|
||||
@@ -72,6 +75,7 @@ class _TwoFactorRecoveryPageState extends State<TwoFactorRecoveryPage> {
|
||||
? () async {
|
||||
await UserService.instance.removeTwoFactor(
|
||||
context,
|
||||
widget.type,
|
||||
widget.sessionID,
|
||||
_recoveryKey.text,
|
||||
widget.encryptedSecret,
|
||||
|
||||
@@ -62,6 +62,7 @@ Future<void> sendLogs(
|
||||
],
|
||||
),
|
||||
onPressed: () async {
|
||||
// ignore: unawaited_futures
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
@@ -118,6 +119,7 @@ Future<void> sendLogs(
|
||||
),
|
||||
),
|
||||
);
|
||||
// ignore: unawaited_futures
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) {
|
||||
@@ -159,7 +161,7 @@ Future<String> getZippedLogsFile(BuildContext context) async {
|
||||
tempPath + "/logs-${Configuration.instance.getUserID() ?? 0}.zip";
|
||||
final encoder = ZipFileEncoder();
|
||||
encoder.create(zipFilePath);
|
||||
encoder.addDirectory(logsDirectory);
|
||||
await encoder.addDirectory(logsDirectory);
|
||||
encoder.close();
|
||||
await dialog.hide();
|
||||
return zipFilePath;
|
||||
|
||||
@@ -2,14 +2,14 @@ import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
|
||||
Future showToast(
|
||||
void showToast(
|
||||
BuildContext context,
|
||||
String message, {
|
||||
toastLength = Toast.LENGTH_LONG,
|
||||
iOSDismissOnTap = true,
|
||||
}) async {
|
||||
await Fluttertoast.cancel();
|
||||
return Fluttertoast.showToast(
|
||||
await Fluttertoast.showToast(
|
||||
msg: message,
|
||||
toastLength: toastLength,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
@@ -20,6 +20,6 @@ Future showToast(
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> showShortToast(context, String message) {
|
||||
return showToast(context, message, toastLength: Toast.LENGTH_SHORT);
|
||||
void showShortToast(context, String message) {
|
||||
showToast(context, message, toastLength: Toast.LENGTH_SHORT);
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0920;
|
||||
LastUpgradeCheck = 1300;
|
||||
LastUpgradeCheck = 1430;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
33CC10EC2044A3C60003C045 = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1300"
|
||||
LastUpgradeVersion = "1430"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user