Compare commits
252 Commits
cli-v0.1.1
...
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 | ||
|
|
a9631c09c8 | ||
|
|
c4ec818fb8 | ||
|
|
981e3866d3 | ||
|
|
7ca217f753 | ||
|
|
07b496be4c | ||
|
|
a45129b75b | ||
|
|
3593ee4931 | ||
|
|
690f90d296 | ||
|
|
d6fc57fc3f | ||
|
|
b893affbfa | ||
|
|
07e48ce318 | ||
|
|
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
|
||||
9
.github/workflows/auth-crowdin.yml
vendored
9
.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:
|
||||
|
||||
6
.github/workflows/auth-lint.yml
vendored
6
.github/workflows/auth-lint.yml
vendored
@@ -1,11 +1,9 @@
|
||||
name: "Lint (auth)"
|
||||
|
||||
on:
|
||||
# Run on every push to branches (this also covers pull requests)
|
||||
# Run on every push to a branch other than main that changes auth/
|
||||
push:
|
||||
# See: [Note: Specify branch when specifying a path filter]
|
||||
branches: ["**"]
|
||||
# Only run if something changes in these paths
|
||||
branches-ignore: [main]
|
||||
paths:
|
||||
- "auth/**"
|
||||
- ".github/workflows/auth-lint.yml"
|
||||
|
||||
7
.github/workflows/auth-release.yml
vendored
7
.github/workflows/auth-release.yml
vendored
@@ -29,7 +29,7 @@ on:
|
||||
- "auth-v*"
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.16.9"
|
||||
FLUTTER_VERSION: "3.13.4"
|
||||
|
||||
jobs:
|
||||
build-ubuntu:
|
||||
@@ -118,14 +118,11 @@ jobs:
|
||||
updateOnlyUnreleased: true
|
||||
|
||||
- name: Upload AAB to PlayStore
|
||||
# Temporarily disable GP upload, enable this once desktop build
|
||||
# testing is complete.
|
||||
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:
|
||||
|
||||
2
.github/workflows/cli-release.yml
vendored
2
.github/workflows/cli-release.yml
vendored
@@ -49,6 +49,6 @@ jobs:
|
||||
project_path: "./cli"
|
||||
pre_command: export CGO_ENABLED=0
|
||||
build_flags: "-trimpath"
|
||||
ldflags: "-s -w"
|
||||
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
|
||||
9
.github/workflows/mobile-crowdin.yml
vendored
9
.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:
|
||||
|
||||
6
.github/workflows/mobile-lint.yml
vendored
6
.github/workflows/mobile-lint.yml
vendored
@@ -1,11 +1,9 @@
|
||||
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:
|
||||
# See: [Note: Specify branch when specifying a path filter]
|
||||
branches: ["**"]
|
||||
# Only run if something changes in these paths
|
||||
branches-ignore: [main]
|
||||
paths:
|
||||
- "mobile/**"
|
||||
- ".github/workflows/mobile-lint.yml"
|
||||
|
||||
6
.github/workflows/mobile-release.yml
vendored
6
.github/workflows/mobile-release.yml
vendored
@@ -39,7 +39,9 @@ jobs:
|
||||
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.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 }}
|
||||
@@ -52,5 +54,5 @@ jobs:
|
||||
- name: Create a draft GitHub release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "mobile/build/app/outputs/flutter-apk/ente.apk,mobile/build/app/outputs/flutter-apk/sha256sum"
|
||||
artifacts: "mobile/build/app/outputs/flutter-apk/ente-${{ github.ref_name }}.apk,mobile/build/app/outputs/flutter-apk/sha256sum"
|
||||
draft: true
|
||||
|
||||
6
.github/workflows/server-lint.yml
vendored
6
.github/workflows/server-lint.yml
vendored
@@ -1,11 +1,9 @@
|
||||
name: "Lint (server)"
|
||||
|
||||
on:
|
||||
# Run on every push (this also covers pull requests)
|
||||
# Run on every push to a branch other than main that changes server/
|
||||
push:
|
||||
# See: [Note: Specify branch when specifying a path filter]
|
||||
branches: ["**"]
|
||||
# Only run if something changes in these paths
|
||||
branches-ignore: [main]
|
||||
paths:
|
||||
- "server/**"
|
||||
- ".github/workflows/server-lint.yml"
|
||||
|
||||
9
.github/workflows/web-crowdin.yml
vendored
9
.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:
|
||||
|
||||
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"
|
||||
15
.github/workflows/web-lint.yml
vendored
15
.github/workflows/web-lint.yml
vendored
@@ -1,20 +1,9 @@
|
||||
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:
|
||||
# [Note: Specify branch when specifying a path filter]
|
||||
#
|
||||
# Path filters are ignored for tag pushes, which causes this workflow to
|
||||
# always run when we push a tag. Defining an explicit branch solves the
|
||||
# issue. From GitHub's docs:
|
||||
#
|
||||
# > if you define both branches/branches-ignore and paths/paths-ignore,
|
||||
# > the workflow will only run when both filters are satisfied.
|
||||
#
|
||||
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions
|
||||
branches: ["**"]
|
||||
# Only run if something changes in these paths
|
||||
branches-ignore: [main]
|
||||
paths:
|
||||
- "web/**"
|
||||
- ".github/workflows/web-lint.yml"
|
||||
|
||||
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
|
||||
|
||||
@@ -50,13 +50,13 @@ 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.
|
||||
|
||||
@@ -70,7 +70,7 @@ existing users will be grandfathered in.
|
||||
[<img height="42" src=".github/assets/app-store-badge.svg">](https://apps.apple.com/app/id6444121398)
|
||||
[<img height="42" src=".github/assets/play-store-badge.png">](https://play.google.com/store/apps/details?id=io.ente.auth)
|
||||
[<img height="42" src=".github/assets/f-droid-badge.png">](https://f-droid.org/packages/io.ente.auth/)
|
||||
[<img height="42" src=".github/assets/github-badge.png">](https://github.com/ente-io/ente/releases?q=tag%3Av2.0.34&expanded=true)
|
||||
[<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>
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -37,8 +37,7 @@
|
||||
{
|
||||
"title": "BorgBase",
|
||||
"altNames": ["borg"],
|
||||
"slug": "BorgBase",
|
||||
"hex": "222C31"
|
||||
"slug": "BorgBase"
|
||||
},
|
||||
{
|
||||
"title": "Brave Creators",
|
||||
@@ -109,8 +108,7 @@
|
||||
{
|
||||
"title": "Gosuslugi",
|
||||
"altNames": ["Госуслуги"],
|
||||
"slug": "Gosuslugi",
|
||||
"hex": "EE2F53"
|
||||
"slug": "Gosuslugi"
|
||||
},
|
||||
{
|
||||
"title": "Healthchecks.io",
|
||||
@@ -127,13 +125,11 @@
|
||||
},
|
||||
{
|
||||
"title": "IVPN",
|
||||
"slug": "IVPN",
|
||||
"hex": "FA3243"
|
||||
"slug": "IVPN"
|
||||
},
|
||||
{
|
||||
"title": "IceDrive",
|
||||
"slug": "Icedrive",
|
||||
"hex": "1F4FD0"
|
||||
"slug": "Icedrive"
|
||||
},
|
||||
{
|
||||
"title": "Jagex",
|
||||
@@ -154,8 +150,7 @@
|
||||
"title": "Kite"
|
||||
},
|
||||
{
|
||||
"title": "Koofr",
|
||||
"hex": "71BA05"
|
||||
"title": "Koofr"
|
||||
},
|
||||
{
|
||||
"title": "Kraken",
|
||||
@@ -184,8 +179,7 @@
|
||||
{
|
||||
"title": "Murena",
|
||||
"altNames": ["eCloud"],
|
||||
"slug": "ecloud",
|
||||
"hex": "EC6A55"
|
||||
"slug": "ecloud"
|
||||
},
|
||||
{
|
||||
"title": "Microsoft"
|
||||
@@ -230,8 +224,7 @@
|
||||
},
|
||||
{
|
||||
"title": "pCloud",
|
||||
"slug": "pCloud",
|
||||
"hex": "1EBCC5"
|
||||
"slug": "pCloud"
|
||||
},
|
||||
{
|
||||
"title": "Peerberry",
|
||||
@@ -371,8 +364,7 @@
|
||||
{
|
||||
"title": "Yandex",
|
||||
"altNames": ["Ya", "Яндекс"],
|
||||
"slug": "Yandex",
|
||||
"hex": "FC3F1D"
|
||||
"slug": "Yandex"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
@@ -214,7 +209,7 @@ SPEC CHECKSUMS:
|
||||
file_picker: ce3938a0df3cc1ef404671531facef740d03f920
|
||||
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
|
||||
fk_user_agent: 1f47ec39291e8372b1d692b50084b0d54103c545
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||
flutter_email_sender: 02d7443217d8c41483223627972bfdc09f74276b
|
||||
flutter_inappwebview: acd4fc0f012cefd09015000c241137d82f01ba62
|
||||
flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
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;
|
||||
|
||||
@@ -144,7 +144,8 @@
|
||||
"enterCodeHint": "Enter the 6-digit code from\nyour authenticator app",
|
||||
"lostDeviceTitle": "Lost device?",
|
||||
"twoFactorAuthTitle": "Two-factor authentication",
|
||||
"passkeyAuthTitle": "Passkey authentication",
|
||||
"passkeyAuthTitle": "Passkey verification",
|
||||
"verifyPasskey": "Verify passkey",
|
||||
"recoverAccount": "Recover account",
|
||||
"enterRecoveryKeyHint": "Enter your recovery key",
|
||||
"recover": "Recover",
|
||||
@@ -407,6 +408,13 @@
|
||||
"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!",
|
||||
"waitingForBrowserRequest": "Waiting for browser request...",
|
||||
"launchPasskeyUrlAgain": "Launch passkey URL again",
|
||||
"passkey": "Passkey"
|
||||
"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}"
|
||||
}
|
||||
@@ -59,6 +59,12 @@
|
||||
"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",
|
||||
@@ -68,9 +74,23 @@
|
||||
"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}",
|
||||
@@ -83,5 +103,45 @@
|
||||
"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."
|
||||
"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."
|
||||
}
|
||||
}
|
||||
@@ -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,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:adaptive_theme/adaptive_theme.dart';
|
||||
@@ -46,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),
|
||||
@@ -83,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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,28 @@ class PasskeyService {
|
||||
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();
|
||||
|
||||
@@ -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';
|
||||
@@ -147,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;
|
||||
@@ -282,6 +283,7 @@ class UserService {
|
||||
throw Exception("unexpected response during passkey verification");
|
||||
}
|
||||
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
@@ -331,6 +333,7 @@ class UserService {
|
||||
);
|
||||
}
|
||||
}
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
@@ -354,6 +357,7 @@ class UserService {
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
} else {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.l10n.incorrectCode,
|
||||
@@ -363,6 +367,7 @@ class UserService {
|
||||
} catch (e) {
|
||||
await dialog.hide();
|
||||
_logger.severe(e);
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.l10n.oops,
|
||||
@@ -399,6 +404,7 @@ class UserService {
|
||||
Bus.instance.fire(UserDetailsChangedEvent());
|
||||
return;
|
||||
}
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.l10n.oops,
|
||||
@@ -407,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,
|
||||
@@ -422,6 +430,7 @@ class UserService {
|
||||
} catch (e) {
|
||||
await dialog.hide();
|
||||
_logger.severe(e);
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.l10n.oops,
|
||||
@@ -632,6 +641,7 @@ class UserService {
|
||||
}
|
||||
}
|
||||
await dialog.hide();
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
@@ -709,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) {
|
||||
@@ -723,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) {
|
||||
@@ -732,6 +744,7 @@ class UserService {
|
||||
(route) => route.isFirst,
|
||||
);
|
||||
} else {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.l10n.incorrectCode,
|
||||
@@ -741,6 +754,7 @@ class UserService {
|
||||
} catch (e) {
|
||||
await dialog.hide();
|
||||
_logger.severe(e);
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.l10n.oops,
|
||||
@@ -749,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 {
|
||||
@@ -757,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"],
|
||||
@@ -774,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) {
|
||||
@@ -786,6 +809,7 @@ class UserService {
|
||||
(route) => route.isFirst,
|
||||
);
|
||||
} else {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.l10n.oops,
|
||||
@@ -793,7 +817,9 @@ class UserService {
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
await dialog.hide();
|
||||
_logger.severe(e);
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.l10n.oops,
|
||||
@@ -806,6 +832,7 @@ class UserService {
|
||||
|
||||
Future<void> removeTwoFactor(
|
||||
BuildContext context,
|
||||
TwoFactorType type,
|
||||
String sessionID,
|
||||
String recoveryKey,
|
||||
String encryptedSecret,
|
||||
@@ -845,6 +872,7 @@ class UserService {
|
||||
data: {
|
||||
"sessionID": sessionID,
|
||||
"secret": secret,
|
||||
"twoFactorType": twoFactorTypeToString(type),
|
||||
},
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
@@ -853,6 +881,7 @@ class UserService {
|
||||
context.l10n.twofactorAuthenticationSuccessfullyReset,
|
||||
);
|
||||
await _saveConfiguration(response);
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
@@ -863,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) {
|
||||
@@ -875,6 +906,7 @@ class UserService {
|
||||
(route) => route.isFirst,
|
||||
);
|
||||
} else {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.l10n.oops,
|
||||
@@ -882,7 +914,9 @@ class UserService {
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
await dialog.hide();
|
||||
_logger.severe(e);
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.l10n.oops,
|
||||
@@ -925,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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/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';
|
||||
@@ -99,30 +101,50 @@ class _PasskeyPageState extends State<PasskeyPage> {
|
||||
}
|
||||
|
||||
Widget _getBody() {
|
||||
final l10n = context.l10n;
|
||||
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
l10n.waitingForBrowserRequest,
|
||||
style: const TextStyle(
|
||||
height: 1.4,
|
||||
fontSize: 16,
|
||||
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),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||
child: ElevatedButton(
|
||||
style: Theme.of(context).colorScheme.optionalActionButtonStyle,
|
||||
onPressed: launchPasskey,
|
||||
child: Text(l10n.launchPasskeyUrlAgain),
|
||||
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) {
|
||||
|
||||
@@ -48,6 +48,7 @@ class AccountSectionWidget extends StatelessWidget {
|
||||
l10n.authToChangeYourEmail,
|
||||
);
|
||||
if (hasAuthenticated) {
|
||||
// ignore: unawaited_futures
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
@@ -74,6 +75,7 @@ class AccountSectionWidget extends StatelessWidget {
|
||||
l10n.authToChangeYourPassword,
|
||||
);
|
||||
if (hasAuthenticated) {
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
@@ -106,9 +108,11 @@ class AccountSectionWidget extends StatelessWidget {
|
||||
recoveryKey =
|
||||
Sodium.bin2hex(Configuration.instance.getRecoveryKey());
|
||||
} catch (e) {
|
||||
// ignore: unawaited_futures
|
||||
showGenericErrorDialog(context: context);
|
||||
return;
|
||||
}
|
||||
// ignore: unawaited_futures
|
||||
routeToPage(
|
||||
context,
|
||||
RecoveryKeyPage(
|
||||
@@ -142,6 +146,7 @@ class AccountSectionWidget extends StatelessWidget {
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
// ignore: unawaited_futures
|
||||
routeToPage(context, const DeleteAccountPage());
|
||||
},
|
||||
),
|
||||
|
||||
@@ -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());
|
||||
},
|
||||
),
|
||||
|
||||
@@ -35,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(
|
||||
|
||||
@@ -21,6 +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: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() {
|
||||
@@ -75,7 +77,7 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
|
||||
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () => PasskeyService.instance.openPasskeyPage(context),
|
||||
onTap: () async => await onPasskeyClick(context),
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
@@ -116,6 +118,7 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
|
||||
context.l10n.authToViewYourActiveSessions,
|
||||
);
|
||||
if (hasAuthenticated) {
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
@@ -158,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"
|
||||
|
||||
@@ -197,10 +197,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: collection
|
||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
version: "1.17.2"
|
||||
computer:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -827,10 +827,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
|
||||
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
version: "1.9.1"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -879,14 +879,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
open_filex:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: open_filex
|
||||
sha256: "854aefd72dfd74219dc8c8d1767c34ec1eae64b8399a5be317bddb1ec2108915"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.3.2"
|
||||
otp:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1304,10 +1296,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
version: "1.11.0"
|
||||
step_progress_indicator:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1320,10 +1312,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "2.1.1"
|
||||
stream_transform:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1368,26 +1360,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test
|
||||
sha256: a1f7595805820fcc05e5c52e3a231aedd0b72972cb333e8c738a8b1239448b6f
|
||||
sha256: "13b41f318e2a5751c3169137103b60c584297353d4b1761b66029bae6411fe46"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.24.9"
|
||||
version: "1.24.3"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
|
||||
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.1"
|
||||
version: "0.6.0"
|
||||
test_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_core
|
||||
sha256: a757b14fc47507060a162cc2530d9a4a2f92f5100a952c7443b5cad5ef5b106a
|
||||
sha256: "99806e9e6d95c7b059b7a0fc08f07fc53fabe54a829497f0d9676299f1e8637e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.9"
|
||||
version: "0.5.3"
|
||||
timezone:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1576,10 +1568,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
|
||||
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.0"
|
||||
version: "0.1.4-beta"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1637,5 +1629,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.2.0-194.0.dev <4.0.0"
|
||||
dart: ">=3.1.0-185.0.dev <4.0.0"
|
||||
flutter: ">=3.10.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: ente_auth
|
||||
description: ente two-factor authenticator
|
||||
version: 2.0.35+235
|
||||
version: 2.0.42+242
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
@@ -54,13 +54,11 @@ dependencies:
|
||||
intl: ^0.18.0
|
||||
json_annotation: ^4.5.0
|
||||
local_auth: ^2.1.7
|
||||
|
||||
local_auth_android: ^1.0.31
|
||||
local_auth_ios: ^1.1.3
|
||||
logging: ^1.0.1
|
||||
modal_bottom_sheet: ^3.0.0-pre
|
||||
move_to_background: ^1.0.2
|
||||
open_filex: ^4.3.2
|
||||
otp: ^3.1.1
|
||||
package_info_plus: ^4.1.0
|
||||
password_strength: ^0.2.0
|
||||
|
||||
@@ -1,49 +1,77 @@
|
||||
# Command Line Utility for exporting data from [Ente](https://ente.io)
|
||||
# Ente CLI
|
||||
|
||||
The Ente CLI is a Command Line Utility for exporting data from
|
||||
[Ente](https://ente.io). It also does a few more things, for example, you can
|
||||
use it to decrypting the export from Ente Auth.
|
||||
|
||||
## Install
|
||||
|
||||
You can either download the binary from the [GitHub releases
|
||||
page](https://github.com/ente-io/ente/releases?q=tag%3Acli-v0&expanded=true) or
|
||||
build it yourself.
|
||||
The easiest way is to download a pre-built binary from the [GitHub
|
||||
releases](https://github.com/ente-io/ente/releases?q=tag%3Acli-v0&expanded=true).
|
||||
|
||||
### Build from source
|
||||
You can also build these binaries yourself
|
||||
|
||||
```shell
|
||||
./release.sh
|
||||
```
|
||||
|
||||
Or you can build from source
|
||||
|
||||
```shell
|
||||
go build -o "bin/ente" main.go
|
||||
```
|
||||
|
||||
### Getting Started
|
||||
The generated binaries are standalone, static binaries with no dependencies. You
|
||||
can run them directly, or put them somewhere in your PATH.
|
||||
|
||||
There is also an option to use [Docker](#docker).
|
||||
|
||||
## Usage
|
||||
|
||||
Run the help command to see all available commands.
|
||||
|
||||
```shell
|
||||
ente --help
|
||||
```
|
||||
|
||||
#### Accounts
|
||||
### Accounts
|
||||
|
||||
If you wish, you can add multiple accounts (your own and that of your family members) and export all data using this tool.
|
||||
|
||||
##### Add an account
|
||||
#### Add an account
|
||||
|
||||
```shell
|
||||
ente account add
|
||||
```
|
||||
|
||||
##### List accounts
|
||||
#### List accounts
|
||||
|
||||
```shell
|
||||
ente account list
|
||||
```
|
||||
|
||||
##### Change export directory
|
||||
#### Change export directory
|
||||
|
||||
```shell
|
||||
ente account update --email email@domain.com --dir ~/photos
|
||||
```
|
||||
|
||||
### Export
|
||||
##### Start export
|
||||
|
||||
#### Start export
|
||||
|
||||
```shell
|
||||
ente export
|
||||
```
|
||||
|
||||
---
|
||||
### CLI Docs
|
||||
You can view more cli documents at [docs](docs/generated/ente.md).
|
||||
To update the docs, run the following command:
|
||||
|
||||
```shell
|
||||
go run main.go docs
|
||||
```
|
||||
|
||||
|
||||
## Docker
|
||||
|
||||
@@ -51,16 +79,22 @@ If you fancy Docker, you can also run the CLI within a container.
|
||||
|
||||
### Configure
|
||||
|
||||
Modify the `docker-compose.yml` and add volume.
|
||||
``cli-data`` volume is mandatory, you can add more volumes for your export directory.
|
||||
Modify the `docker-compose.yml` and add volume. ``cli-data`` volume is
|
||||
mandatory, you can add more volumes for your export directory.
|
||||
|
||||
Build the docker image
|
||||
|
||||
```shell
|
||||
docker build -t ente:latest .
|
||||
```
|
||||
|
||||
Note that [BuildKit](https://docs.docker.com/go/buildkit/) is needed to build
|
||||
this image. If you face this issue, a quick fix is to add `DOCKER_BUILDKIT=1` in
|
||||
front of the build command.
|
||||
|
||||
Start the container in detached mode
|
||||
```bash
|
||||
|
||||
```shell
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
@@ -69,20 +103,8 @@ docker-compose up -d
|
||||
docker-compose exec ente /bin/sh
|
||||
```
|
||||
|
||||
|
||||
#### Directly executing commands
|
||||
|
||||
```shell
|
||||
docker run -it --rm ente:latest ls
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Releases
|
||||
|
||||
Run the release script to build the binary and run it.
|
||||
|
||||
```shell
|
||||
./release.sh
|
||||
```
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ var updateAccCmd = &cobra.Command{
|
||||
fmt.Printf("invalid app. Accepted values are 'photos', 'locker', 'auth'")
|
||||
|
||||
}
|
||||
err := ctrl.UpdateAccount(context.Background(), model.UpdateAccountParams{
|
||||
err := ctrl.UpdateAccount(context.Background(), model.AccountCommandParams{
|
||||
Email: email,
|
||||
App: api.StringToApp(app),
|
||||
ExportDir: &exportDir,
|
||||
@@ -73,12 +73,49 @@ var updateAccCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
// Subcommand for 'account update'
|
||||
var getTokenCmd = &cobra.Command{
|
||||
Use: "get-token",
|
||||
Short: "Get token for an account for a specific app",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
recoverWithLog()
|
||||
app, _ := cmd.Flags().GetString("app")
|
||||
email, _ := cmd.Flags().GetString("email")
|
||||
if email == "" {
|
||||
|
||||
fmt.Println("email must be specified, use --help for more information")
|
||||
// print help
|
||||
return
|
||||
}
|
||||
|
||||
validApps := map[string]bool{
|
||||
"photos": true,
|
||||
"locker": true,
|
||||
"auth": true,
|
||||
}
|
||||
|
||||
if !validApps[app] {
|
||||
fmt.Printf("invalid app. Accepted values are 'photos', 'locker', 'auth'")
|
||||
|
||||
}
|
||||
err := ctrl.GetToken(context.Background(), model.AccountCommandParams{
|
||||
Email: email,
|
||||
App: api.StringToApp(app),
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("Error updating account: %v\n", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Add 'config' subcommands to the root command
|
||||
rootCmd.AddCommand(accountCmd)
|
||||
// Add 'config' subcommands to the 'config' command
|
||||
updateAccCmd.Flags().String("dir", "", "update export directory")
|
||||
updateAccCmd.Flags().String("email", "", "email address of the account to update")
|
||||
updateAccCmd.Flags().String("email", "", "email address of the account")
|
||||
updateAccCmd.Flags().String("app", "photos", "Specify the app, default is 'photos'")
|
||||
accountCmd.AddCommand(listAccCmd, addAccCmd, updateAccCmd)
|
||||
getTokenCmd.Flags().String("email", "", "email address of the account")
|
||||
getTokenCmd.Flags().String("app", "photos", "Specify the app, default is 'photos'")
|
||||
accountCmd.AddCommand(listAccCmd, addAccCmd, updateAccCmd, getTokenCmd)
|
||||
}
|
||||
|
||||
90
cli/cmd/admin.go
Normal file
90
cli/cmd/admin.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/ente-io/cli/pkg/model"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var _adminCmd = &cobra.Command{
|
||||
Use: "admin",
|
||||
Short: "Commands for admin actions",
|
||||
Long: "Commands for admin actions like disable or enabling 2fa, bumping up the storage limit, etc.",
|
||||
}
|
||||
|
||||
var _userDetailsCmd = &cobra.Command{
|
||||
Use: "get-user-id",
|
||||
Short: "Get user id",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
recoverWithLog()
|
||||
var flags = &model.AdminActionForUser{}
|
||||
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
||||
if f.Name == "admin-user" {
|
||||
flags.AdminEmail = f.Value.String()
|
||||
}
|
||||
if f.Name == "user" {
|
||||
flags.UserEmail = f.Value.String()
|
||||
}
|
||||
})
|
||||
return ctrl.GetUserId(context.Background(), *flags)
|
||||
},
|
||||
}
|
||||
|
||||
var _disable2faCmd = &cobra.Command{
|
||||
Use: "disable-2fa",
|
||||
Short: "Disable 2fa for a user",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
recoverWithLog()
|
||||
var flags = &model.AdminActionForUser{}
|
||||
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
||||
if f.Name == "admin-user" {
|
||||
flags.AdminEmail = f.Value.String()
|
||||
}
|
||||
if f.Name == "user" {
|
||||
flags.UserEmail = f.Value.String()
|
||||
}
|
||||
})
|
||||
fmt.Println("Not supported yet")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var _updateFreeUserStorage = &cobra.Command{
|
||||
Use: "update-subscription",
|
||||
Short: "Update subscription for the free user",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
recoverWithLog()
|
||||
var flags = &model.AdminActionForUser{}
|
||||
noLimit := false
|
||||
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
||||
if f.Name == "admin-user" {
|
||||
flags.AdminEmail = f.Value.String()
|
||||
}
|
||||
if f.Name == "user" {
|
||||
flags.UserEmail = f.Value.String()
|
||||
}
|
||||
if f.Name == "no-limit" {
|
||||
noLimit = strings.ToLower(f.Value.String()) == "true"
|
||||
}
|
||||
})
|
||||
return ctrl.UpdateFreeStorage(context.Background(), *flags, noLimit)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(_adminCmd)
|
||||
_ = _userDetailsCmd.MarkFlagRequired("admin-user")
|
||||
_ = _userDetailsCmd.MarkFlagRequired("user")
|
||||
_userDetailsCmd.Flags().StringP("admin-user", "a", "", "The email of the admin user. (required)")
|
||||
_userDetailsCmd.Flags().StringP("user", "u", "", "The email of the user to fetch details for. (required)")
|
||||
_disable2faCmd.Flags().StringP("admin-user", "a", "", "The email of the admin user. (required)")
|
||||
_disable2faCmd.Flags().StringP("user", "u", "", "The email of the user to disable 2FA for. (required)")
|
||||
_updateFreeUserStorage.Flags().StringP("admin-user", "a", "", "The email of the admin user. (required)")
|
||||
_updateFreeUserStorage.Flags().StringP("user", "u", "", "The email of the user to update subscription for. (required)")
|
||||
// add a flag with no value --no-limit
|
||||
_updateFreeUserStorage.Flags().String("no-limit", "True", "When true, sets 100TB as storage limit, and expiry to current date + 100 years")
|
||||
_adminCmd.AddCommand(_userDetailsCmd, _disable2faCmd, _updateFreeUserStorage)
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/ente-io/cli/pkg"
|
||||
"github.com/spf13/cobra/doc"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
@@ -11,7 +12,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const AppVersion = "0.1.11"
|
||||
var version string
|
||||
|
||||
var ctrl *pkg.ClICtrl
|
||||
|
||||
@@ -27,10 +28,15 @@ var rootCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
func GenerateDocs() error {
|
||||
return doc.GenMarkdownTree(rootCmd, "./docs/generated")
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute(controller *pkg.ClICtrl) {
|
||||
func Execute(controller *pkg.ClICtrl, ver string) {
|
||||
ctrl = controller
|
||||
version = ver
|
||||
err := rootCmd.Execute()
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
|
||||
@@ -12,7 +12,7 @@ var versionCmd = &cobra.Command{
|
||||
Short: "Prints the current version",
|
||||
Long: ``,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("Version %s\n", AppVersion)
|
||||
fmt.Printf("Version %s\n", version)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
10
cli/config.yaml.example
Normal file
10
cli/config.yaml.example
Normal file
@@ -0,0 +1,10 @@
|
||||
# You can put this configuration file in the following locations:
|
||||
# - $HOME/.ente/config.yaml
|
||||
# - config.yaml in the current working directory
|
||||
# - $ENTE_CLI_CONFIG_PATH/config.yaml
|
||||
|
||||
endpoint:
|
||||
api: "http://localhost:8080"
|
||||
|
||||
log:
|
||||
http: false # log status code & time taken by requests
|
||||
28
cli/docs/generated/ente.md
Normal file
28
cli/docs/generated/ente.md
Normal file
@@ -0,0 +1,28 @@
|
||||
## ente
|
||||
|
||||
CLI tool for exporting your photos from ente.io
|
||||
|
||||
### Synopsis
|
||||
|
||||
Start by creating a config file in your home directory:
|
||||
|
||||
```
|
||||
ente [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for ente
|
||||
-t, --toggle Help message for toggle
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [ente account](ente_account.md) - Manage account settings
|
||||
* [ente admin](ente_admin.md) - Commands for admin actions
|
||||
* [ente auth](ente_auth.md) - Authenticator commands
|
||||
* [ente export](ente_export.md) - Starts the export process
|
||||
* [ente version](ente_version.md) - Prints the current version
|
||||
|
||||
###### Auto generated by spf13/cobra on 13-Mar-2024
|
||||
19
cli/docs/generated/ente_account.md
Normal file
19
cli/docs/generated/ente_account.md
Normal file
@@ -0,0 +1,19 @@
|
||||
## ente account
|
||||
|
||||
Manage account settings
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for account
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [ente](ente.md) - CLI tool for exporting your photos from ente.io
|
||||
* [ente account add](ente_account_add.md) - Add a new account
|
||||
* [ente account get-token](ente_account_get-token.md) - Get token for an account for a specific app
|
||||
* [ente account list](ente_account_list.md) - list configured accounts
|
||||
* [ente account update](ente_account_update.md) - Update an existing account's export directory
|
||||
|
||||
###### Auto generated by spf13/cobra on 13-Mar-2024
|
||||
19
cli/docs/generated/ente_account_add.md
Normal file
19
cli/docs/generated/ente_account_add.md
Normal file
@@ -0,0 +1,19 @@
|
||||
## ente account add
|
||||
|
||||
Add a new account
|
||||
|
||||
```
|
||||
ente account add [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for add
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [ente account](ente_account.md) - Manage account settings
|
||||
|
||||
###### Auto generated by spf13/cobra on 13-Mar-2024
|
||||
21
cli/docs/generated/ente_account_get-token.md
Normal file
21
cli/docs/generated/ente_account_get-token.md
Normal file
@@ -0,0 +1,21 @@
|
||||
## ente account get-token
|
||||
|
||||
Get token for an account for a specific app
|
||||
|
||||
```
|
||||
ente account get-token [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
--app string Specify the app, default is 'photos' (default "photos")
|
||||
--email string email address of the account
|
||||
-h, --help help for get-token
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [ente account](ente_account.md) - Manage account settings
|
||||
|
||||
###### Auto generated by spf13/cobra on 13-Mar-2024
|
||||
19
cli/docs/generated/ente_account_list.md
Normal file
19
cli/docs/generated/ente_account_list.md
Normal file
@@ -0,0 +1,19 @@
|
||||
## ente account list
|
||||
|
||||
list configured accounts
|
||||
|
||||
```
|
||||
ente account list [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for list
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [ente account](ente_account.md) - Manage account settings
|
||||
|
||||
###### Auto generated by spf13/cobra on 13-Mar-2024
|
||||
22
cli/docs/generated/ente_account_update.md
Normal file
22
cli/docs/generated/ente_account_update.md
Normal file
@@ -0,0 +1,22 @@
|
||||
## ente account update
|
||||
|
||||
Update an existing account's export directory
|
||||
|
||||
```
|
||||
ente account update [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
--app string Specify the app, default is 'photos' (default "photos")
|
||||
--dir string update export directory
|
||||
--email string email address of the account
|
||||
-h, --help help for update
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [ente account](ente_account.md) - Manage account settings
|
||||
|
||||
###### Auto generated by spf13/cobra on 13-Mar-2024
|
||||
22
cli/docs/generated/ente_admin.md
Normal file
22
cli/docs/generated/ente_admin.md
Normal file
@@ -0,0 +1,22 @@
|
||||
## ente admin
|
||||
|
||||
Commands for admin actions
|
||||
|
||||
### Synopsis
|
||||
|
||||
Commands for admin actions like disable or enabling 2fa, bumping up the storage limit, etc.
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for admin
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [ente](ente.md) - CLI tool for exporting your photos from ente.io
|
||||
* [ente admin disable-2fa](ente_admin_disable-2fa.md) - Disable 2fa for a user
|
||||
* [ente admin get-user-id](ente_admin_get-user-id.md) - Get user id
|
||||
* [ente admin update-subscription](ente_admin_update-subscription.md) - Update subscription for the free user
|
||||
|
||||
###### Auto generated by spf13/cobra on 13-Mar-2024
|
||||
21
cli/docs/generated/ente_admin_disable-2fa.md
Normal file
21
cli/docs/generated/ente_admin_disable-2fa.md
Normal file
@@ -0,0 +1,21 @@
|
||||
## ente admin disable-2fa
|
||||
|
||||
Disable 2fa for a user
|
||||
|
||||
```
|
||||
ente admin disable-2fa [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-a, --admin-user string The email of the admin user. (required)
|
||||
-h, --help help for disable-2fa
|
||||
-u, --user string The email of the user to disable 2FA for. (required)
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [ente admin](ente_admin.md) - Commands for admin actions
|
||||
|
||||
###### Auto generated by spf13/cobra on 13-Mar-2024
|
||||
21
cli/docs/generated/ente_admin_get-user-id.md
Normal file
21
cli/docs/generated/ente_admin_get-user-id.md
Normal file
@@ -0,0 +1,21 @@
|
||||
## ente admin get-user-id
|
||||
|
||||
Get user id
|
||||
|
||||
```
|
||||
ente admin get-user-id [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-a, --admin-user string The email of the admin user. (required)
|
||||
-h, --help help for get-user-id
|
||||
-u, --user string The email of the user to fetch details for. (required)
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [ente admin](ente_admin.md) - Commands for admin actions
|
||||
|
||||
###### Auto generated by spf13/cobra on 13-Mar-2024
|
||||
22
cli/docs/generated/ente_admin_update-subscription.md
Normal file
22
cli/docs/generated/ente_admin_update-subscription.md
Normal file
@@ -0,0 +1,22 @@
|
||||
## ente admin update-subscription
|
||||
|
||||
Update subscription for the free user
|
||||
|
||||
```
|
||||
ente admin update-subscription [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-a, --admin-user string The email of the admin user. (required)
|
||||
-h, --help help for update-subscription
|
||||
--no-limit string When true, sets 100TB as storage limit, and expiry to current date + 100 years (default "True")
|
||||
-u, --user string The email of the user to update subscription for. (required)
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [ente admin](ente_admin.md) - Commands for admin actions
|
||||
|
||||
###### Auto generated by spf13/cobra on 13-Mar-2024
|
||||
16
cli/docs/generated/ente_auth.md
Normal file
16
cli/docs/generated/ente_auth.md
Normal file
@@ -0,0 +1,16 @@
|
||||
## ente auth
|
||||
|
||||
Authenticator commands
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for auth
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [ente](ente.md) - CLI tool for exporting your photos from ente.io
|
||||
* [ente auth decrypt](ente_auth_decrypt.md) - Decrypt authenticator export
|
||||
|
||||
###### Auto generated by spf13/cobra on 13-Mar-2024
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user