Compare commits

..

19 Commits

Author SHA1 Message Date
atyabbin
da0832fc7d Deleted fileee from <branch-name> 2024-07-16 17:15:58 +05:30
atyabbin
827ac9ddf7 Dashboard with imporved UI 2024-07-16 16:21:59 +05:30
atyabbin
2e35b1eeb4 First view of the dashboard with new UI 2024-07-06 16:33:26 +05:30
atyabbin
65c72f6cf5 Showing usage data in GB in the fetch table 2024-06-27 15:59:39 +05:30
atyabbin
f11cc82e44 modification in URL for update subscription 2024-06-27 14:16:29 +05:30
atyabbin
206e387834 Modified calender for date picking 2024-06-23 11:37:20 +05:30
atyabbin
567dfb7e6b Added options for delete account and update subscription 2024-06-23 11:13:59 +05:30
atyabbin
b7d3e5439a Pressing enter key will call fetchdata 2024-06-19 12:09:55 +05:30
atyabbin
ef37a4cad8 Reverting the changes in local.yaml 2024-06-19 09:56:59 +05:30
atyabbin
480a86af0a Changed the label of the new Button from 'More' to 'MORE' 2024-06-18 19:00:32 +05:30
atyabbin
a5b0bc259d Added a dropdown button to select different actions to be performed 2024-06-18 18:40:40 +05:30
atyabbin
326e673d12 Added display message after button clicks 2024-06-18 14:25:37 +05:30
atyabbin
00b68131a8 Solved the lint issues 2024-06-18 13:21:37 +05:30
atyabbin
de8d81300f Modified the Sidebar Component 2024-06-18 13:10:19 +05:30
atyabbin
e287e80257 Added new buttons in the dashboard 2024-06-18 09:56:00 +05:30
atyabbin
d716f18c2e Added buttons for disable2fa, close family and disable passkeys 2024-06-18 09:38:36 +05:30
atyabbin
93bddbe6f1 Merge branch 'main' into dashboard-improvement 2024-06-10 19:41:29 +05:30
atyabbin
17e48ed83f Added new components 2024-06-10 19:29:28 +05:30
atyabbin
4c7583240f Sidebar added 2024-06-10 19:24:32 +05:30
319 changed files with 9113 additions and 6458 deletions

View File

@@ -9,7 +9,7 @@ on:
- ".github/workflows/auth-lint.yml"
env:
FLUTTER_VERSION: "3.22.2"
FLUTTER_VERSION: "3.19.3"
jobs:
lint:

View File

@@ -29,7 +29,7 @@ on:
- "auth-v*"
env:
FLUTTER_VERSION: "3.22.2"
FLUTTER_VERSION: "3.19.3"
jobs:
build-ubuntu:

View File

@@ -4,7 +4,7 @@ on:
workflow_dispatch: # Allow manually running the action
env:
FLUTTER_VERSION: "3.22.2"
FLUTTER_VERSION: "3.22.1"
jobs:
build:

View File

@@ -10,7 +10,7 @@ on:
env:
FLUTTER_VERSION: "3.22.2"
FLUTTER_VERSION: "3.22.1"
jobs:
lint:

View File

@@ -9,7 +9,7 @@ on:
- "photos-v*"
env:
FLUTTER_VERSION: "3.22.2"
FLUTTER_VERSION: "3.22.1"
jobs:
build:

View File

@@ -33,7 +33,7 @@ jobs:
registry: ghcr.io
enableBuildKit: true
multiPlatform: true
platform: linux/amd64,linux/arm64
platform: linux/amd64,linux/arm64,linux/arm/v7
buildArgs: GIT_COMMIT=${{ inputs.commit }}
tags: ${{ inputs.commit }}, latest
username: ${{ github.actor }}

View File

@@ -1,54 +1,51 @@
# Security Policy
Ente believes that working with security researchers across the globe is crucial
to keeping our users safe. If you believe you've found a security issue in our
product or service, we encourage you to notify us by email at security@ente.io
or by
[filling out this form](https://github.com/ente-io/ente/security/advisories/new).
We welcome working with you to resolve the issue promptly. Thanks in advance!
product or service, we encourage you to notify us, by email (security@ente.io)
or by [filling this
form](https://github.com/ente-io/ente/security/advisories/new) We welcome
working with you to resolve the issue promptly. Thanks in advance!
## Disclosure Policy
- Let us know as soon as possible upon discovery of a potential security
issue, and we'll make every effort to quickly resolve the issue.
- Provide us with a reasonable amount of time to resolve the issue before any
disclosure to the public or a third party. We may publicly disclose the
issue before resolving it if appropriate.
- Make a good faith effort to avoid privacy violations, destruction of data,
and interruption or degradation of our service. Only interact with accounts
you own or with the explicit permission of the account holder.
- If you would like to encrypt your report, please use the PGP key with long
ID `E273695C0403F34F74171932DF6DDDE98EBD2394` (available in the public
keyserver pool).
- Let us know as soon as possible upon discovery of a potential security issue,
and we'll make every effort to quickly resolve the issue.
- Provide us a reasonable amount of time to resolve the issue before any
disclosure to the public or a third-party. We may publicly disclose the issue
before resolving it, if appropriate.
- Make a good faith effort to avoid privacy violations, destruction of data, and
interruption or degradation of our service. Only interact with accounts you
own or with explicit permission of the account holder.
- If you would like to encrypt your report, please use the PGP key with long ID
`E273695C0403F34F74171932DF6DDDE98EBD2394` (available in the public keyserver
pool).
## In-scope
- Security issues in any current release of Ente's services. Product downloads
are available at [https://ente.io](https://ente.io). Source code is
available at [https://github.com/ente-io](https://github.com/ente-io).
- Security issues in any current release of Ente's services. Product downloads
are available at https://ente.io. Source code is available at
https://github.com/ente-io.
## Exclusions
The following bug classes are out of scope:
The following bug classes are out-of scope:
- Bugs that are already reported on any of
[Ente's issue trackers](https://github.com/ente-io) or that we already know
of (note that some of our issue tracking is private).
- Issues in an upstream software dependency (e.g., Flutter, Next.js, etc.)
that are already reported to the upstream maintainer.
- Attacks requiring physical access to a user's device.
- Self-XSS.
- Issues related to software or protocols not under Ente's control.
- Vulnerabilities in outdated versions of Ente.
- Missing security best practices that do not directly lead to a
vulnerability.
- Issues that do not have any impact on the general public.
- Bugs that are already reported on any of [Ente's issue
trackers](https://github.com/ente-io), or that we already know of (Note that
some of our issue tracking is private)
- Issues in an upstream software dependency (ex: Flutter, Next.js etc) which are
already reported to the upstream maintainer
- Attacks requiring physical access to a user's device
- Self-XSS
- Issues related to software or protocols not under ente's control
- Vulnerabilities in outdated versions of ente
- Missing security best practices that do not directly lead to a vulnerability
- Issues that do not have any impact on the general public
While researching, we'd like to ask you to refrain from:
- Denial of service
- Spamming
- Social engineering (including phishing) of Ente staff or contractors
- Any physical attempts against Ente property or data centers
- Denial of service
- Spamming
- Social engineering (including phishing) of Ente staff or contractors
- Any physical attempts against Ente property or data centers
Thank you for helping keep Ente and our users safe!

View File

@@ -6,7 +6,7 @@ FEATURES
- Secure Backups
Auth provides end-to-end encrypted cloud backups so that you don't have to worry
about losing your tokens. We use the same protocols Ente Photos uses to encrypt
about losing your tokens. We use the same protocols ente Photos uses to encrypt
and preserve your data.
- Multi Device Synchronization

View File

@@ -2,5 +2,4 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -23,12 +23,6 @@
{
"title": "BitMEX"
},
{
"title": "BitSkins"
},
{
"title": "Bitstamp"
},
{
"title": "Bitvavo",
"hex": "0051FF"
@@ -56,9 +50,6 @@
{
"title": "CERN"
},
{
"title": "ChangeNOW"
},
{
"title": "Channel Island Hosting",
"slug": "cih",
@@ -67,9 +58,6 @@
{
"title": "Cloudflare"
},
{
"title": "CoinDCX"
},
{
"title": "ConfigCat"
},
@@ -95,9 +83,6 @@
{
"title": "Discourse"
},
{
"title": "DMarket"
},
{
"title": "Doppler"
},
@@ -169,10 +154,6 @@
{
"title": "INWX"
},
{
"title": "Itch.io",
"slug": "itch_io"
},
{
"title": "IVPN",
"slug": "IVPN"
@@ -221,10 +202,6 @@
"slug": "local_wp",
"altNames": ["LocalWP", "Local WP", "Local Wordpress"]
},
{
"title": "Marketplace.tf",
"slug": "marketplacedottf"
},
{
"title": "Mastodon",
"altNames": ["mstdn", "fediscience", "mathstodon", "fosstodon"],
@@ -239,9 +216,6 @@
{
"title": "Microsoft"
},
{
"title": "Migros"
},
{
"title": "Mintos"
},
@@ -257,10 +231,6 @@
"title": "MyFRITZ!Net",
"slug": "myfritz"
},
{
"title": "Name.com",
"slug": "name_com"
},
{
"title": "NextDNS"
},
@@ -371,9 +341,6 @@
"title": "Skiff",
"hex": "EF5A3C"
},
{
"title": "Skinport"
},
{
"title": "Snapchat"
},
@@ -430,10 +397,6 @@
"title": "Ubisoft",
"hex": "4285f4"
},
{
"title": "Ubuntu One",
"slug": "ubuntu_one"
},
{
"title": "Unity",
"hex": "858585"
@@ -460,7 +423,9 @@
{
"title": "WorkOS",
"slug": "workos",
"altNames": ["Work OS"]
"altNames": [
"Work OS"
]
},
{
"title": "X",

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="layer" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 652 652" style="enable-background:new 0 0 652 652;" xml:space="preserve">
<style type="text/css">
.st0{fill:#003B2F;}
</style>
<path class="st0" d="M108.8,331.4c0-11.4-6.6-20.5-17.3-24.2c8.1-3.7,13.7-11.4,13.7-21.3c0-15.5-13-26.3-31.5-26.3H21v13.2
c8.3,0,15,6.7,15,15s-6.7,15-15,15v11.9c8.3,0,15,6.7,15,15s-6.7,15-15,15V359h55c18.9,0,32.6-11.2,32.6-27.5 M49.3,273.9H71
c8.9,0,15.2,5.8,15.2,13.9s-6.3,13.7-15.2,13.7H49.3C49.3,301.5,49.3,273.9,49.3,273.9z M49.3,344.5v-30H73
c9.8,0,16.4,6.1,16.4,14.9s-6.6,15.1-16.4,15.1C73,344.5,49.3,344.5,49.3,344.5z M130.2,254.4c6.6,0,11.4,4.7,11.4,11.4
s-4.7,11.4-11.4,11.4c-6.6,0-11.2-4.7-11.2-11.4S123.7,254.4,130.2,254.4 M120.7,286.5h18.9v72.4h-18.9V286.5L120.7,286.5z
M176.2,286.4h20.1v14.4h-20.1v34.6c0,5.5,4.4,10.1,9.9,10.2c2.9,0,6.1-0.4,9.6-1.3l2.5,14.2c-5.8,1.5-11.2,2.2-16.4,2.2
c-14.5,0-24.6-9.5-24.6-23.4v-67.4c0-2.6,2.1-4.7,4.7-4.7h14.2V286.4L176.2,286.4z M234.6,284.7c8.4,0,17.4,1.7,26.6,5l-4.1,13.6
c-7.3-2.8-14.5-4.6-21-4.6c-6.5,0-11.2,3.1-11.2,7.8c0,13.1,38.7,3.6,38.7,31.6c0,13.2-11.9,22.5-29,22.5c-8.9,0-18.6-2-28.8-5.8
l4-13.5c8.4,3.4,16.4,5.2,23.4,5.2c7,0,11.9-3.4,11.9-8.4c0-13.7-38.6-4.1-38.6-31.6c0-12.7,11.5-21.9,28.3-21.9 M293.7,286.4h20.1
v14.4h-20.1v34.6c0,5.5,4.4,10.1,9.9,10.2c2.9,0,6.1-0.4,9.6-1.3l2.5,14.2c-5.8,1.5-11.2,2.2-16.4,2.2c-14.5,0-24.6-9.5-24.6-23.4
v-67.4c0-2.7,2.2-4.7,4.7-4.7h14.2V286.4z M413.8,286.4h15.1l2.4,9.9c5.8-7.5,14.2-11.6,23.8-11.6c10.5,0,19,5,23.2,13.3
c6.6-8.6,15.7-13.3,26.2-13.3c15.8,0,27.1,11,27.1,26.4v47.7h-18.9v-44.8c0-8.1-5.8-13.9-14-13.9c-9.6,0-16.4,7.3-16.4,17.6v41.1
h-18.9v-44.8c0-8.1-5.8-13.9-13.9-13.9c-9.8,0-16.5,7.3-16.5,17.6v41.1h-18.9v-72.4H413.8z M547.1,286.4h15.1l2.4,9.3
c5.3-7.1,13.6-11.1,23.2-11.1c19.5,0,33.3,15.7,33.3,38c0,22.3-13.7,37.9-33.3,37.9c-8.9,0-16.5-3.6-21.8-10v32.2
c0,2.6-2.1,4.7-4.7,4.7H547L547.1,286.4L547.1,286.4z M582.8,299.6c-10,0-17.2,7.5-17.2,18.2v9.8c0,10.5,7.1,18.2,17.2,18.2
c11.1,0,18.9-9.6,18.9-23C601.8,309.4,593.9,299.7,582.8,299.6 M378.1,327.5c0,10.5-7.1,18.2-17.2,18.2c-11.1,0-18.9-9.6-18.9-23
c0-13.4,7.8-23.1,18.9-23.1c10,0,17.2,7.5,17.2,18.2V327.5L378.1,327.5z M396.7,323.3L396.7,323.3v-36.9h-15.1l-2.4,9.3
c-5.3-7.1-13.6-11.1-23.2-11.1c-19.5,0-33.3,15.7-33.3,38c0,22.3,13.7,37.9,33.3,37.9c1.1,0,2.2,0,3.3-0.2
c8.2-0.8,15.1-4.7,19.9-10.9l2.4,9.3h15.1L396.7,323.3L396.7,323.3z"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.8 KiB

View File

@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 500 500">
<path fill="#182954" d="m75.705 269.386 12.606 10.812a40.902 40.902 0 0 1-8.642 8.853 53.365 53.365 0 0 1-13.599 7.73 45.769 45.769 0 0 1-16.998 3.094 49.02 49.02 0 0 1-25.212-6.466A45.84 45.84 0 0 1 6.72 275.84a50.83 50.83 0 0 1-6.212-25.287 52.621 52.621 0 0 1 3.525-19.394 49.28 49.28 0 0 1 10.2-16.022 46.603 46.603 0 0 1 15.44-10.812 49.626 49.626 0 0 1 19.969-3.938 45.9 45.9 0 0 1 23.51 5.48A49.016 49.016 0 0 1 88.308 219.5l-12.744 11.244A39.368 39.368 0 0 0 64.938 220.2a27.358 27.358 0 0 0-15.296-3.933 27.636 27.636 0 0 0-16.147 4.632 30.695 30.695 0 0 0-10.478 12.508 38.957 38.957 0 0 0-3.688 16.879 36.724 36.724 0 0 0 3.684 16.442 29.719 29.719 0 0 0 10.184 11.793 27.208 27.208 0 0 0 15.44 4.358c4.608.197 9.203-.62 13.456-2.391a27.765 27.765 0 0 0 8.214-5.622l5.381-5.481M93.275 264.047a35.477 35.477 0 0 1 4.535-17.71 34.84 34.84 0 0 1 12.748-12.929 39.497 39.497 0 0 1 18.838-4.778 39.497 39.497 0 0 1 18.838 4.778 34.846 34.846 0 0 1 12.749 12.928 36.889 36.889 0 0 1 4.532 17.709 36.891 36.891 0 0 1-4.532 17.708 36.519 36.519 0 0 1-13.365 13.153 36.875 36.875 0 0 1-18.181 4.837 36.88 36.88 0 0 1-18.203-4.756 36.513 36.513 0 0 1-13.424-13.092 35.479 35.479 0 0 1-4.535-17.707v-.141zm35.979 21.224a16.949 16.949 0 0 0 10.623-3.23c2.804-2.121 5-4.93 6.375-8.151a24.848 24.848 0 0 0 2.124-9.698 24.293 24.293 0 0 0-2.124-9.697 20.265 20.265 0 0 0-6.375-8.15 19.056 19.056 0 0 0-10.623-3.233 19.057 19.057 0 0 0-10.625 3.233 20.118 20.118 0 0 0-6.231 8.009 24.296 24.296 0 0 0-2.125 9.697 24.713 24.713 0 0 0 2.125 9.839 19.985 19.985 0 0 0 6.374 8.15 16.949 16.949 0 0 0 10.624 3.231M168.905 202.628h16.856v17.71h-16.856v-17.71zm0 28.11h16.856v66.758h-16.856v-66.758zM192.416 297.495V230.88h16.147l.42 7.589a35.937 35.937 0 0 1 7.505-5.905 23.656 23.656 0 0 1 12.749-3.094 24.38 24.38 0 0 1 10.396 1.612 24.22 24.22 0 0 1 8.726 5.836 29.047 29.047 0 0 1 6.66 20.097v40.477H238.02v-40.335a13.257 13.257 0 0 0-.76-5.278 13.337 13.337 0 0 0-2.78-4.561 12.19 12.19 0 0 0-4.164-2.694 12.27 12.27 0 0 0-4.902-.82 14.974 14.974 0 0 0-6.377 1.24 14.87 14.87 0 0 0-5.236 3.82 18.046 18.046 0 0 0-4.534 12.51v36.118l-16.851.004z"/>
<path fill="#FA4A29" d="m463.25 246.618 29.754-44.007h-28.187l-15.44 24.596-15.883-24.596h-31.163l1.416 1.967-.993-.416a63.329 63.329 0 0 0-23.083-4.046 50.453 50.453 0 0 0-25.92 6.607 46.609 46.609 0 0 0-14.308 12.929 40.334 40.334 0 0 0-15.582-11.806 65.028 65.028 0 0 0-26.344-5.077h-36.686v94.727h36.544a64.026 64.026 0 0 0 26.344-5.202A41.612 41.612 0 0 0 339.3 280.63c3.87 5.299 8.846 9.709 14.59 12.928a51.44 51.44 0 0 0 25.777 6.325 55.023 55.023 0 0 0 24.646-5.34l-1.982 2.953h27.76l18.558-29.108 19.122 29.108h31.73l-36.252-50.878zm-147.452 21.624a25.772 25.772 0 0 1-8.902 5.504 25.916 25.916 0 0 1-10.376 1.523h-10.334v-50.573h10.338c3.62-.305 7.264.165 10.685 1.378a25.427 25.427 0 0 1 9.147 5.65 26.146 26.146 0 0 1 6.374 18.271 24.821 24.821 0 0 1-1.597 9.836 24.965 24.965 0 0 1-5.343 8.436l.008-.025zm101.549 6.911-12.04-11.228a38.572 38.572 0 0 1-10.197 9.149 27.09 27.09 0 0 1-13.6 2.952 25.509 25.509 0 0 1-13.314-3.372 22.838 22.838 0 0 1-8.8-9.415 29.459 29.459 0 0 1-3.118-13.63c-.091-4.623.929-9.2 2.975-13.353a23.258 23.258 0 0 1 8.642-9.415 25.653 25.653 0 0 1 13.738-3.513 24.798 24.798 0 0 1 12.748 3.23 32.061 32.061 0 0 1 9.639 8.733l12.606-12.508 18.415 26.28-17.694 26.09z"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 2000 1948.02"><defs><style>.cls-1,.cls-2{opacity:0.25;isolation:isolate;}.cls-1{fill:url(#linear-gradient);}.cls-2{fill:url(#linear-gradient-2);}.cls-3{fill:url(#linear-gradient-3);}.cls-4{fill:url(#linear-gradient-4);}</style><linearGradient id="linear-gradient" x1="83.62" y1="947.07" x2="878.54" y2="947.07" gradientTransform="matrix(1, 0, 0, -1, 0, 1923)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#00ba96"/><stop offset="0.52" stop-color="#00d9af"/><stop offset="1" stop-color="#00d9af"/></linearGradient><linearGradient id="linear-gradient-2" x1="1144.49" y1="948.99" x2="2001.4" y2="948.99" gradientTransform="matrix(1, 0, 0, -1, 0, 1923)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#008d85"/><stop offset="1" stop-color="#00ffe0"/></linearGradient><linearGradient id="linear-gradient-3" x1="250.91" y1="949" x2="1904.03" y2="949" gradientTransform="matrix(1, 0, 0, -1, 0, 1923)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#008d94"/><stop offset="1" stop-color="#00ffe0"/></linearGradient><linearGradient id="linear-gradient-4" y1="947.06" x2="794.89" y2="947.06" gradientTransform="matrix(1, 0, 0, -1, 0, 1923)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#00ba96"/><stop offset="0.52" stop-color="#00d9af"/><stop offset="1" stop-color="#00faaf"/></linearGradient></defs><title>dmt</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M857,890.24,625.08,489,389.28,83.76a168.38,168.38,0,0,0-54.57-56.49h-1.95a148.49,148.49,0,0,0-56.52,58.44L100.85,389.6a138.94,138.94,0,0,0,0,140.26L311.32,894.13c15.59,25.32,21.44,54.54,21.44,83.76h0a178.34,178.34,0,0,1-21.44,83.76L102.8,1422a138.94,138.94,0,0,0,0,140.26l175.39,303.89c13.64,23.38,33.13,44.8,56.52,58.44h2c23.39-13.64,40.93-33.12,54.57-56.49l233.86-405.18L857,1057.76A171,171,0,0,0,857,890.24Z"/><path class="cls-2" d="M1975.62,878.55,1741.76,475.31,1502.06,60.39a171.53,171.53,0,0,0-42.87-48.7C1449.44,3.9,1439.69,0,1428,0c-122.78,0-206.58,3.9-268.94,3.9-9.74,0-15.59,11.69-13.64,21.43l60.41,339c0,3.9,0,7.79-1.95,11.69-23.39,42.86-21.44,95.45,3.9,138.31l214.37,374c15.59,27.27,23.39,56.49,23.39,85.71s-7.8,58.44-23.39,85.71l-216.32,374c-25.33,42.86-25.33,95.45-3.9,138.31,2,3.9,2,7.79,2,11.69l-60.41,337c-2,9.74,3.9,21.43,13.64,21.43,56.52,0-23.39,5.84,276.73,5.84,5.85,0,13.64-2,19.49-3.9,21.44-7.79,35.08-33.12,46.77-54.54l239.71-416.87,231.91-403.24c37-58.47,37-130.53,3.9-190.92"/><path class="cls-3" d="M1880.13,890.24,1414.36,83.76C1385.13,33.12,1330.56,0,1270.15,0H338.61c-31.18,0-60.41,9.74-87.7,25.32v1.95a168.38,168.38,0,0,1,54.57,56.49L539.34,489c21.44-9.74,46.77-15.58,70.16-15.58H995.36c60.41,0,115,31.17,144.21,83.76L1332.5,890.24c29.23,50.65,29.23,114.93,0,167.53l-192.93,333.11c-29.23,50.65-85.75,83.76-144.21,83.76H611.44a168.21,168.21,0,0,1-70.16-15.58L307.43,1864.24a168.38,168.38,0,0,1-54.57,56.49v2c25.33,15.58,56.52,25.32,87.7,25.32H1274c60.41,0,115-31.17,144.21-83.76L1884,1057.76c27.31-52.59,27.31-114.93-3.87-167.52"/><path class="cls-4" d="M771.25,890.24,539.34,489,305.48,83.76a168.38,168.38,0,0,0-54.57-56.49H249a148.49,148.49,0,0,0-56.52,58.44L19,387.65a138.94,138.94,0,0,0,0,140.26L229.47,892.18c15.59,25.32,21.44,54.54,21.44,83.76h0a178.34,178.34,0,0,1-21.44,83.76L19,1422a138.94,138.94,0,0,0,0,140.26l175.4,303.92c13.64,23.38,33.13,44.8,56.52,58.44h2c23.39-13.64,40.93-33.12,54.57-56.49L541.3,1463l231.9-401.29C802.43,1005.17,802.43,942.83,771.25,890.24Z"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -1,6 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="235.452" width="261.728"
viewBox="0 0 245.371 220.736">
<path
d="M31.99 1.365C21.287 7.72.2 31.945 0 38.298v10.516C0 62.144 12.46 73.86 23.773 73.86c13.584 0 24.902-11.258 24.903-24.62 0 13.362 10.93 24.62 24.515 24.62 13.586 0 24.165-11.258 24.165-24.62 0 13.362 11.622 24.62 25.207 24.62h.246c13.586 0 25.208-11.258 25.208-24.62 0 13.362 10.58 24.62 24.164 24.62 13.585 0 24.515-11.258 24.515-24.62 0 13.362 11.32 24.62 24.903 24.62 11.313 0 23.773-11.714 23.773-25.046V38.298c-.2-6.354-21.287-30.58-31.988-36.933C180.118.197 157.056-.005 122.685 0c-34.37.003-81.228.54-90.697 1.365zm65.194 66.217a28.025 28.025 0 0 1-4.78 6.155c-5.128 5.014-12.157 8.122-19.906 8.122a28.482 28.482 0 0 1-19.948-8.126c-1.858-1.82-3.27-3.766-4.563-6.032l-.006.004c-1.292 2.27-3.092 4.215-4.954 6.037a28.5 28.5 0 0 1-19.948 8.12c-.934 0-1.906-.258-2.692-.528-1.092 11.372-1.553 22.24-1.716 30.164l-.002.045c-.02 4.024-.04 7.333-.06 11.93.21 23.86-2.363 77.334 10.52 90.473 19.964 4.655 56.7 6.775 93.555 6.788h.006c36.854-.013 73.59-2.133 93.554-6.788 12.883-13.14 10.31-66.614 10.52-90.474-.022-4.596-.04-7.905-.06-11.93l-.003-.045c-.162-7.926-.623-18.793-1.715-30.165-.786.27-1.757.528-2.692.528a28.5 28.5 0 0 1-19.948-8.12c-1.862-1.822-3.662-3.766-4.955-6.037l-.006-.004c-1.294 2.266-2.705 4.213-4.563 6.032a28.48 28.48 0 0 1-19.947 8.125c-7.748 0-14.778-3.11-19.906-8.123a28.025 28.025 0 0 1-4.78-6.155 27.99 27.99 0 0 1-4.736 6.155 28.49 28.49 0 0 1-19.95 8.124c-.27 0-.54-.012-.81-.02h-.007c-.27.008-.54.02-.813.02a28.49 28.49 0 0 1-19.95-8.123 27.992 27.992 0 0 1-4.736-6.155zm-20.486 26.49l-.002.01h.015c8.113.017 15.32 0 24.25 9.746 7.028-.737 14.372-1.105 21.722-1.094h.006c7.35-.01 14.694.357 21.723 1.094 8.93-9.747 16.137-9.73 24.25-9.746h.014l-.002-.01c3.833 0 19.166 0 29.85 30.007L210 165.244c8.504 30.624-2.723 31.373-16.727 31.4-20.768-.773-32.267-15.855-32.267-30.935-11.496 1.884-24.907 2.826-38.318 2.827h-.006c-13.412 0-26.823-.943-38.318-2.827 0 15.08-11.5 30.162-32.267 30.935-14.004-.027-25.23-.775-16.726-31.4L46.85 124.08C57.534 94.073 72.867 94.073 76.7 94.073zm45.985 23.582v.006c-.02.02-21.863 20.08-25.79 27.215l14.304-.573v12.474c0 .584 5.74.346 11.486.08h.006c5.744.266 11.485.504 11.485-.08v-12.474l14.304.573c-3.928-7.135-25.79-27.215-25.79-27.215v-.006l-.003.002z"
color="#000" />
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.0"
width="602.36218"
height="139.53543"
id="svg3058">
<defs
id="defs3060" />
<g
id="layer1">
<path
d="M 106.369,119.88512 L 106.369,62.018665 L 81.544594,119.88512 L 67.772114,119.88512 L 43.359087,62.018665 L 43.359087,119.88512 L 17.716529,119.88512 L 17.716529,19.650275 L 52.045504,19.650275 L 75.627274,76.245395 L 100.30782,19.650275 L 132.00731,19.650275 L 132.00731,119.88512 L 106.369,119.88512 z M 151.74424,119.88514 L 151.74424,19.650295 L 173.67129,19.650295 C 175.59247,19.650295 177.51259,21.571475 177.51259,23.661675 L 177.51259,119.88514 L 151.74424,119.88514 z M 248.65971,62.160065 L 281.25389,62.160065 C 283.82526,62.160065 285.27873,64.264785 285.27873,66.736595 L 285.27873,118.51459 C 276.11148,120.56404 261.78448,121.82085 251.13365,121.82085 C 213.76593,121.82085 194.05842,104.00581 194.05842,69.633955 C 194.05948,38.962025 213.76593,17.714585 246.61239,17.714585 C 261.08007,17.714585 273.76688,21.571475 281.93349,27.080965 L 271.83223,44.330845 C 264.76546,41.165965 255.25983,38.962025 248.53357,38.962025 C 229.64739,38.962025 220.81534,51.492935 220.81534,69.633955 C 220.81534,89.002025 231.04275,100.96814 249.90412,100.96814 C 253.35105,100.96814 257.64767,100.84093 261.08007,100.30377 L 261.08007,82.644625 L 248.65971,82.644625 L 248.65971,62.160065 z M 342.46983,38.962025 L 329.85459,38.962025 L 329.85459,64.094705 L 342.46983,64.094705 C 349.99971,64.094705 355.91916,60.082975 355.91916,50.546515 C 355.92022,40.883215 349.99865,38.962025 342.46983,38.962025 z M 361.45735,119.88514 L 349.56211,92.294115 C 347.1314,86.671595 345.43451,83.888325 340.27794,83.888325 L 329.85459,83.888325 L 329.85459,119.88514 L 304.66452,119.88514 L 304.66452,23.661675 C 304.66452,21.571475 306.95349,19.650295 308.59192,19.650295 L 344.15113,19.650295 C 363.47597,19.650295 380.04625,23.661675 380.04625,48.611165 C 380.04625,59.785695 373.86247,71.230215 362.26026,73.547185 C 368.0401,75.510535 373.86247,82.913205 377.01176,90.951915 L 388.47046,119.88514 L 361.45735,119.88514 z M 444.16061,38.962025 C 430.64148,38.962025 421.01998,48.611165 421.01998,69.633955 C 421.02105,90.951915 430.64042,100.57341 444.15991,100.57341 C 455.75892,100.57341 465.25215,90.951915 465.25215,69.633955 C 465.25321,48.611165 455.75892,38.962025 444.16061,38.962025 z M 444.16061,121.82085 C 411.34321,121.82085 393.97817,98.638055 393.97817,69.633955 C 393.97817,40.883215 411.34215,17.714585 444.15991,17.714585 C 474.90235,17.714585 492.29396,40.883215 492.29396,69.633955 C 492.29502,98.636985 474.90128,121.82085 444.16061,121.82085 z M 541.64053,121.82085 C 524.689,121.82085 508.94892,115.74656 501.92857,110.91455 L 513.3575,90.655685 C 519.73014,94.512575 532.13207,100.4455 543.56101,100.4455 C 550.03179,100.4455 558.21187,99.202855 558.21187,92.012065 C 558.21187,85.258875 549.07227,83.196315 537.08986,79.762855 C 522.62431,75.623205 505.78439,69.859315 505.78439,50.405135 C 505.7851,29.439745 524.41723,17.714585 548.66337,17.714585 C 564.66778,17.714585 577.07254,23.378915 583.4023,29.439745 L 569.48526,45.277265 C 562.60593,41.165965 555.57104,37.973445 547.68719,37.973445 C 538.19396,37.973445 532.82585,41.576285 532.82585,46.816835 C 532.82691,52.015925 539.70518,54.671985 548.37813,57.285175 C 563.71002,62.018685 584.64565,66.850335 584.64565,86.926005 C 584.64671,107.60829 568.11435,121.82085 541.64053,121.82085 z "
style="fill:#f60;fill-opacity:1;fill-rule:evenodd"
id="polygon3036" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -1,25 +0,0 @@
<svg enable-background="new 0 0 1510.1 240.3" viewBox="0 0 1510.1 240.3"
xmlns="http://www.w3.org/2000/svg">
<path
d="m873.5 203.7c0 16.2-11.9 29.1-28.9 29.1-16.4 0-28.6-12.9-28.6-29.1s12.2-30 28.6-30c17 0 28.9 13.7 28.9 30z"
fill="#6eda78" />
<g fill="#282828">
<path
d="m9.7 229v-135.4l38.7-9.4-5.9 43.6h4.2c2.4-10.1 6.2-18.6 11.3-25.7 5.2-7 11.8-12.4 19.8-16 8.2-3.8 17.6-5.7 28.4-5.7 14 0 26.1 3.2 36.3 9.7 10.1 6.3 17.9 15.5 23.3 27.7 5.6 12 8.3 26.3 8.3 43.1v68.1h-38.5v-57.9c0-11.2-1.7-20.7-5.1-28.5-3.4-8-8.4-14-14.9-18-6.5-4.2-14.4-6.3-23.5-6.3-13.7 0-24.4 4.6-32.1 13.7s-11.5 22.1-11.5 39.1v57.9z" />
<path clip-rule="evenodd"
d="m252.4 232.7c-19.6 0-36.4-3.9-45.7-11.7-9.3-8-14-18.7-14-32.2 0-13.1 4.7-23.6 14-31.4 25.1-21.6 103.6-13.3 108.6-11.6 0-3.3-.2-8.9-3.5-14.4-7.9-13.8-24.3-19.7-39-19.7-12.3 0-25.3 6.1-32.9 15.4-3 3.7-5.5 9.1-5.5 9.1h-36.8c.6-7 4.6-18.4 10.7-27 6-8.7 15.4-16.1 27-21.2 11.8-5.3 20.7-7.6 38-7.6 18 0 31.8 3 43.9 9.1s21.1 14.6 26.9 25.7c5.9 11 8.8 24 8.8 38.8v23.3 52h-37.7l6-30.9h-4.1c-1.9 8.5-5.4 14.9-9.6 19.6-4.3 4.7-9.8 7.6-19.5 10.5-9.5 2.8-21.4 4.2-35.6 4.2zm-23.2-46.4c-2-32.9 77-23.3 86.1-22.2 0 4.8-.3 9.5-1.4 14-5.8 23.3-38.1 28.4-58 26.6-14.5-1.3-26-6.6-26.7-18.4z"
fill-rule="evenodd" />
<path
d="m375.3 94.7v134.3h38.7v-71.9c0-12.2 2.8-21.7 8.3-28.5 5.6-6.8 13.1-10.3 22.5-10.3 6.4 0 11.8 1.5 16.4 4.6 4.7 3 8.3 7.5 10.8 13.4 2.6 5.7 3.9 12.6 3.9 20.8v71.9h38.5v-71.9c0-12.2 2.8-21.7 8.3-28.5 5.7-6.8 13.3-10.3 22.8-10.3 6.4 0 11.8 1.5 16.4 4.6 4.7 3 8.3 7.5 10.8 13.4 2.4 5.7 3.7 12.6 3.7 20.8v71.9h38.7v-82.1c0-14.1-2.2-26-6.6-35.9-4.2-10.1-10.3-17.7-18.1-22.8s-17.1-7.7-27.9-7.7c-13.1 0-23.8 3.5-32.1 10.6-8.2 6.8-14 17.1-17.4 30.8h-3.4c-1.1-7.8-3.8-14.7-7.8-20.8-4.1-6.3-9.6-11.2-16.4-14.8-6.7-3.8-14.7-5.7-24-5.7-12.7 0-23.1 3.4-31.1 10.3-8 6.7-13.7 16.5-17.1 29.7h-4.2l5.1-36.2z" />
<path clip-rule="evenodd"
d="m718.3 232.7c-18 0-33.4-3.3-46.3-10-12.7-6.8-22.4-16-28.9-27.4-6.5-11.6-9.8-24.5-9.8-38.8s3.3-27.1 9.8-38.5c6.5-11.6 16.2-20.7 29.1-27.4 12.9-6.8 28.4-10.3 46.5-10.3s33.6 3.4 46.3 10.3c12.9 6.7 22.6 15.8 29.2 27.4 6.5 11.6 9.8 24.6 9.8 39.1 0 4-.7 10.2-.7 10.2h-133.4c.9 4.3 2.4 8.3 4.5 12.1 3.8 6.5 9.3 11.6 16.7 15.4 7.5 3.6 16.8 5.4 27.9 5.4 12.6 0 22.2-1.7 28.9-5.1 5.2-2.7 9.5-5.9 13-9.5h39.1c-2.6 6.7-6.7 13.4-12.2 20-6.4 7.6-15.4 14.1-27.2 19.4-11.5 5.2-25.6 7.7-42.3 7.7zm-43.8-98.3c-1.4 2.4-2.9 6.1-3.4 8.8h47.6 47.6c-.6-2.7-2-6.4-3.4-8.8-3.8-6.7-9.3-11.8-16.7-15.4-7.3-3.8-16.5-5.7-27.4-5.7-11.1 0-20.3 1.9-27.7 5.7-7.3 3.6-12.9 8.7-16.6 15.4z"
fill-rule="evenodd" />
<path
d="m896.2 195.4c6.5 11.4 16.1 20.5 28.7 27.4 12.7 6.7 28.1 10 46.1 10 16 0 30.1-2.8 42.4-8.3 12.2-5.5 22-13.2 29.2-23.1s11.4-21.1 12.7-33.6h-38.7c-1.5 8.4-6.1 15.4-14 21.1-7.8 5.7-18.4 8.6-31.6 8.6-9.8 0-18.1-1.8-25-5.4-6.9-3.8-12.1-8.7-15.7-14.8s-5.4-12.9-5.4-20.5c0-7.4 1.8-14.2 5.4-20.2 3.6-6.3 8.8-11.2 15.7-14.8s15.2-5.4 25-5.4c8.8 0 16.5 1.4 23 4.3 6.5 2.7 11.7 6.2 15.4 10.6 3.8 4.4 6.1 9.3 7.1 14.8h38.7c-1.3-12.7-5.6-24-12.7-33.6-7.2-9.9-16.9-17.6-29.2-23.1-12.2-5.7-26.4-8.6-42.4-8.6-18 0-33.3 3.4-46.1 10.3-12.6 6.7-22.1 15.8-28.7 27.4-6.5 11.6-9.8 24.4-9.8 38.5.1 13.9 3.4 26.8 9.9 38.4z" />
<path clip-rule="evenodd"
d="m1159.2 232.7c-18.6 0-34.5-3.3-47.5-10-13.1-6.8-22.9-16-29.6-27.4-6.5-11.6-9.8-24.5-9.8-38.8s3.3-27.1 9.8-38.5c6.7-11.6 16.6-20.7 29.6-27.4 13.1-6.8 28.9-10.3 47.5-10.3 18.5 0 34.2 3.4 47.3 10.3 13.1 6.7 22.9 15.8 29.4 27.4 6.7 11.4 10 24.2 10 38.5s-3.3 27.2-10 38.8c-6.5 11.4-16.3 20.5-29.4 27.4-13 6.7-28.8 10-47.3 10zm0-35.3c10.3 0 19-1.8 26.2-5.4 7.3-3.6 12.8-8.5 16.4-14.5 3.8-6.1 5.6-13 5.6-20.8 0-7.4-1.9-14.2-5.6-20.2-3.6-6.3-9.1-11.2-16.4-14.8-7.2-3.6-15.9-5.4-26.2-5.4-10.5 0-19.4 1.8-26.7 5.4-7.2 3.6-12.7 8.6-16.4 14.8-3.6 6.1-5.4 12.8-5.4 20.2 0 7.8 1.8 14.7 5.4 20.8 3.8 6.1 9.2 10.9 16.4 14.5 7.4 3.6 16.3 5.4 26.7 5.4z"
fill-rule="evenodd" />
<path
d="m1265.3 93.6v135.4h38.7v-71.9c0-12.2 2.8-21.7 8.3-28.5 5.6-6.8 13.1-10.3 22.5-10.3 6.4 0 11.8 1.5 16.4 4.6 4.7 3 8.3 7.5 10.8 13.4 2.6 5.7 3.9 12.6 3.9 20.8v71.9h38.5v-71.9c0-12.2 2.8-21.7 8.3-28.5 5.7-6.8 13.3-10.3 22.8-10.3 6.4 0 11.8 1.5 16.4 4.6 4.7 3 8.3 7.5 10.8 13.4 2.4 5.7 3.7 12.6 3.7 20.8v71.9h38.7v-82.1c0-14.1-2.2-26-6.6-35.9-4.2-10.1-10.3-17.7-18.1-22.8s-17.1-7.7-27.9-7.7c-13.1 0-23.8 3.5-32.1 10.6-8.2 6.8-14 17.1-17.4 30.8h-3.4c-1.1-7.8-3.8-14.7-7.8-20.8-4.1-6.3-9.6-11.2-16.4-14.8-6.7-3.8-14.7-5.7-24-5.7-12.7 0-23.1 3.4-31.1 10.3-8 6.7-13.7 16.5-17.1 29.7h-4.2l5.1-36.2z" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.5 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -1,113 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg3039"
version="1.1"
inkscape:version="0.48.2 r9819"
width="240.20215"
height="98.041267"
sodipodi:docname="U1_logo.svg">
<metadata
id="metadata3045">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs3043" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1022"
inkscape:window-height="529"
id="namedview3041"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="3.5580214"
inkscape:cx="188.39168"
inkscape:cy="46.745142"
inkscape:window-x="0"
inkscape:window-y="24"
inkscape:window-maximized="1"
inkscape:current-layer="svg3039" />
<path
style="fill:#dd4713;fill-opacity:1;stroke:none"
d="m 0,13.135439 0.31287126,55.797305 c 0,0 -0.29397798,23.453759 25.17115774,28.396822 14.646306,2.843004 42.347094,-3.749492 42.347094,-3.749492 l -0.220563,-80.503585 -22.276334,0 0,62.638417 -20.95299,0 0,-41.464863 -9.04287,0 10.586775,-21.394113 z"
id="path3049"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccsccccccccc" />
<g
id="g3898"
transform="scale(2.2196062,2.2196062)">
<path
d="m 47.937238,17.527712 c -0.49137,0.12284 -1.143956,0.253358 -1.95776,0.391552 -0.798468,0.138195 -1.727443,0.207292 -2.786929,0.207293 -0.921305,-10e-7 -1.696731,-0.130518 -2.32628,-0.391552 -0.629558,-0.27639 -1.136272,-0.660264 -1.520143,-1.151624 -0.383877,-0.491358 -0.660267,-1.067169 -0.829169,-1.727435 -0.168907,-0.675616 -0.253359,-1.420332 -0.253358,-2.23415 l 0,-6.7254823 2.14202,0 0,6.2648323 c -3e-6,1.458728 0.230321,2.502865 0.690975,3.132417 0.460644,0.629556 1.23607,0.944333 2.326279,0.944331 0.230318,2e-6 0.46832,-0.0077 0.714007,-0.02303 0.245672,-0.01535 0.475996,-0.03071 0.690974,-0.04606 0.214961,-0.03071 0.406898,-0.05374 0.575812,-0.0691 0.184251,-0.03071 0.314768,-0.06142 0.391552,-0.09213 l 0,-10.1112553 2.14202,0 0,11.6313983"
style="font-size:23.03247261px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#dd4713;fill-opacity:1;stroke:none;font-family:Ubuntu;-inkscape-font-specification:Ubuntu"
id="path3871"
inkscape:connector-curvature="0" />
<path
d="m 53.207382,6.4260606 c 0.26103,-0.1688932 0.652582,-0.3454753 1.174656,-0.5297469 0.537419,-0.1842476 1.151617,-0.2763774 1.842598,-0.2763897 0.859871,1.23e-5 1.619942,0.153562 2.280214,0.4606495 0.67561,0.3071111 1.243743,0.7370502 1.704403,1.2898185 0.460638,0.5527892 0.806125,1.2130528 1.036462,1.9807926 0.245667,0.7677564 0.368507,1.6122804 0.368519,2.5335724 -1.2e-5,0.967368 -0.145884,1.842602 -0.437617,2.625701 -0.276401,0.767752 -0.67563,1.420338 -1.197688,1.957761 -0.52208,0.537425 -1.151634,0.952009 -1.888663,1.243753 -0.737047,0.291745 -1.566215,0.437617 -2.487507,0.437617 -0.998079,0 -1.88099,-0.0691 -2.648734,-0.207292 -0.767752,-0.138195 -1.397306,-0.27639 -1.888663,-0.414585 l 0,-17.15919172 2.14202,-0.36851956283681 0,6.42605988283681 m 0,9.5815084 c 0.214965,0.06142 0.514387,0.122842 0.898266,0.18426 0.399224,0.04607 0.890583,0.0691 1.474078,0.0691 1.151617,2e-6 2.072915,-0.376195 2.763897,-1.128591 0.690965,-0.767745 1.036451,-1.85027 1.036461,-3.247578 -10e-6,-0.614193 -0.06143,-1.190004 -0.184259,-1.727436 C 59.072975,9.619905 58.87336,9.159256 58.59698,8.7753728 58.320582,8.3761528 57.95974,8.0690534 57.514454,7.8540739 57.084507,7.6237594 56.562438,7.5085972 55.948246,7.5085868 c -0.583495,1.04e-5 -1.120919,0.099818 -1.612273,0.2994221 -0.491364,0.1996247 -0.867561,0.4069167 -1.128591,0.6218768 l 0,7.5776833"
style="font-size:23.03247261px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#dd4713;fill-opacity:1;stroke:none;font-family:Ubuntu;-inkscape-font-specification:Ubuntu"
id="path3873"
inkscape:connector-curvature="0" />
<path
d="m 73.905311,17.527712 c -0.49137,0.12284 -1.143957,0.253358 -1.95776,0.391552 -0.798468,0.138195 -1.727443,0.207292 -2.78693,0.207293 -0.921304,-10e-7 -1.69673,-0.130518 -2.326279,-0.391552 -0.629558,-0.27639 -1.136272,-0.660264 -1.520143,-1.151624 -0.383877,-0.491358 -0.660267,-1.067169 -0.829169,-1.727435 -0.168907,-0.675616 -0.253359,-1.420332 -0.253358,-2.23415 l 0,-6.7254823 2.14202,0 0,6.2648323 c -4e-6,1.458728 0.230321,2.502865 0.690974,3.132417 0.460645,0.629556 1.236071,0.944333 2.32628,0.944331 0.230318,2e-6 0.46832,-0.0077 0.714007,-0.02303 0.245672,-0.01535 0.475996,-0.03071 0.690974,-0.04606 0.214961,-0.03071 0.406898,-0.05374 0.575812,-0.0691 0.184251,-0.03071 0.314768,-0.06142 0.391552,-0.09213 l 0,-10.1112553 2.14202,0 0,11.6313983"
style="font-size:23.03247261px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#dd4713;fill-opacity:1;stroke:none;font-family:Ubuntu;-inkscape-font-specification:Ubuntu"
id="path3875"
inkscape:connector-curvature="0" />
<path
d="m 77.314487,6.2418008 c 0.491357,-0.1228281 1.143943,-0.2533453 1.95776,-0.391552 0.81381,-0.1381827 1.750463,-0.2072801 2.809962,-0.2072923 0.952001,1.22e-5 1.742782,0.1382069 2.372345,0.4145845 0.629544,0.2610463 1.128581,0.6372429 1.497111,1.1285912 0.383863,0.4760146 0.652575,1.0518259 0.806136,1.7274354 0.153538,0.6756275 0.230313,1.4203434 0.230325,2.2341494 l 0,6.725482 -2.14202,0 0,-6.264832 c -1e-5,-0.737032 -0.05375,-1.366586 -0.161227,-1.8886628 C 84.59274,9.1976435 84.431512,8.7753819 84.201197,8.4529182 83.970863,8.1304733 83.663764,7.9001488 83.279898,7.761944 82.896016,7.6084044 82.420012,7.5316296 81.851884,7.5316193 c -0.23033,1.03e-5 -0.468332,0.00769 -0.714006,0.023032 -0.245685,0.015365 -0.483687,0.038398 -0.714007,0.069097 -0.214974,0.015365 -0.414589,0.038398 -0.598844,0.069097 -0.168909,0.03072 -0.291749,0.053752 -0.36852,0.069097 l 0,10.1112547 -2.14202,0 0,-11.6313979"
style="font-size:23.03247261px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#dd4713;fill-opacity:1;stroke:none;font-family:Ubuntu;-inkscape-font-specification:Ubuntu"
id="path3877"
inkscape:connector-curvature="0" />
<path
d="m 91.700827,5.8963137 4.537397,0 0,1.7965329 -4.537397,0 0,5.5277934 c -3e-6,0.598848 0.04606,1.097885 0.138195,1.497111 0.09213,0.383877 0.230321,0.690976 0.414585,0.921299 0.184255,0.214971 0.414579,0.368521 0.690974,0.460649 0.276384,0.09213 0.598838,0.138197 0.967364,0.138195 0.644902,2e-6 1.159294,-0.0691 1.543175,-0.207292 0.399222,-0.153548 0.675611,-0.261033 0.829169,-0.322455 l 0.414585,1.7735 c -0.214978,0.107486 -0.591175,0.238003 -1.128591,0.391552 -0.537432,0.168905 -1.15163,0.253357 -1.842598,0.253358 -0.813819,-10e-7 -1.489438,-0.09981 -2.026858,-0.299422 -0.522072,-0.21497 -0.944334,-0.529747 -1.266786,-0.944332 -0.322456,-0.414583 -0.552781,-0.921297 -0.690974,-1.520143 -0.122841,-0.614196 -0.184261,-1.320525 -0.18426,-2.118988 l 0,-10.6870668 2.14202,-0.3685196 0,3.7082281"
style="font-size:23.03247261px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#dd4713;fill-opacity:1;stroke:none;font-family:Ubuntu;-inkscape-font-specification:Ubuntu"
id="path3879"
inkscape:connector-curvature="0" />
<path
d="m 108.21836,17.527712 c -0.49137,0.12284 -1.14395,0.253358 -1.95776,0.391552 -0.79847,0.138195 -1.72744,0.207292 -2.78693,0.207293 -0.9213,-10e-7 -1.69673,-0.130518 -2.32628,-0.391552 -0.62955,-0.27639 -1.13627,-0.660264 -1.52014,-1.151624 -0.383877,-0.491358 -0.660267,-1.067169 -0.829169,-1.727435 -0.168907,-0.675616 -0.253359,-1.420332 -0.253357,-2.23415 l 0,-6.7254823 2.142016,0 0,6.2648323 c 0,1.458728 0.23032,2.502865 0.69098,3.132417 0.46064,0.629556 1.23607,0.944333 2.32628,0.944331 0.23032,2e-6 0.46832,-0.0077 0.714,-0.02303 0.24568,-0.01535 0.476,-0.03071 0.69098,-0.04606 0.21496,-0.03071 0.4069,-0.05374 0.57581,-0.0691 0.18425,-0.03071 0.31477,-0.06142 0.39155,-0.09213 l 0,-10.1112553 2.14202,0 0,11.6313983"
style="font-size:23.03247261px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#dd4713;fill-opacity:1;stroke:none;font-family:Ubuntu;-inkscape-font-specification:Ubuntu"
id="path3881"
inkscape:connector-curvature="0" />
<path
d="m 48.692644,28.027864 c -1.2e-5,0.952013 -0.138207,1.811892 -0.414584,2.579636 -0.276402,0.767752 -0.667953,1.428016 -1.174656,1.980793 -0.49137,0.55278 -1.082536,0.982719 -1.773501,1.289819 -0.690982,0.291744 -1.443376,0.437616 -2.257182,0.437617 -0.81382,-10e-7 -1.566214,-0.145873 -2.257183,-0.437617 -0.690978,-0.3071 -1.289821,-0.737039 -1.796532,-1.289819 -0.491362,-0.552777 -0.875236,-1.213041 -1.151624,-1.980793 -0.276391,-0.767744 -0.414586,-1.627623 -0.414585,-2.579636 -10e-7,-0.936647 0.138194,-1.788848 0.414585,-2.556605 0.276388,-0.783095 0.660262,-1.451036 1.151624,-2.003825 0.506711,-0.552768 1.105554,-0.97503 1.796532,-1.266786 0.690969,-0.307088 1.443363,-0.460637 2.257183,-0.46065 0.813806,1.3e-5 1.5662,0.153562 2.257182,0.46065 0.690965,0.291756 1.282131,0.714018 1.773501,1.266786 0.506703,0.552789 0.898254,1.22073 1.174656,2.003825 0.276377,0.767757 0.414572,1.619958 0.414584,2.556605 m -2.23415,0 c -1e-5,-1.351232 -0.307109,-2.418402 -0.921299,-3.201514 -0.598853,-0.798449 -1.420343,-1.197678 -2.464474,-1.197689 -1.044145,1.1e-5 -1.873313,0.39924 -2.487507,1.197689 -0.598848,0.783112 -0.89827,1.850282 -0.898267,3.201514 -3e-6,1.351243 0.299419,2.42609 0.898267,3.224546 0.614194,0.783106 1.443362,1.174657 2.487507,1.174656 1.044131,10e-7 1.865621,-0.39155 2.464474,-1.174656 0.61419,-0.798456 0.921289,-1.873303 0.921299,-3.224546"
style="font-size:23.03247261px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;font-family:Ubuntu;-inkscape-font-specification:Ubuntu"
id="path3883"
inkscape:connector-curvature="0" />
<path
d="m 51.144189,22.384908 c 0.491357,-0.122828 1.143943,-0.253346 1.95776,-0.391552 0.813809,-0.138183 1.750462,-0.20728 2.809962,-0.207293 0.952001,1.3e-5 1.742782,0.138207 2.372344,0.414585 0.629545,0.261046 1.128581,0.637243 1.497111,1.128591 0.383864,0.476015 0.652576,1.051826 0.806136,1.727436 0.153539,0.675627 0.230314,1.420343 0.230325,2.234149 l 0,6.725482 -2.14202,0 0,-6.264832 c -9e-6,-0.737032 -0.05375,-1.366586 -0.161227,-1.888663 -0.09214,-0.522061 -0.253366,-0.944322 -0.483682,-1.266786 -0.230333,-0.322445 -0.537433,-0.552769 -0.921299,-0.690974 -0.383882,-0.15354 -0.859886,-0.230314 -1.428013,-0.230325 -0.230331,1.1e-5 -0.468333,0.0077 -0.714007,0.02303 -0.245685,0.01536 -0.483687,0.0384 -0.714006,0.0691 -0.214975,0.01536 -0.41459,0.0384 -0.598845,0.0691 -0.168909,0.03072 -0.291748,0.05375 -0.368519,0.0691 l 0,10.111255 -2.14202,0 0,-11.631398"
style="font-size:23.03247261px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;font-family:Ubuntu;-inkscape-font-specification:Ubuntu"
id="path3885"
inkscape:connector-curvature="0" />
<path
d="m 63.139815,28.050896 c -2e-6,-1.059487 0.153548,-1.980785 0.460649,-2.763897 0.307098,-0.798449 0.714004,-1.458713 1.220721,-1.980792 0.506711,-0.522059 1.0902,-0.91361 1.750468,-1.174656 0.660259,-0.261023 1.335877,-0.39154 2.026858,-0.391553 1.612264,1.3e-5 2.848339,0.506727 3.708228,1.520144 0.859867,0.998083 1.289806,2.525902 1.289818,4.583462 -1.2e-5,0.09214 -1.2e-5,0.214975 0,0.368519 -1.2e-5,0.138201 -0.0077,0.268718 -0.02303,0.391552 l -8.199561,0 c 0.09213,1.243758 0.452969,2.188088 1.082527,2.832994 0.629549,0.644912 1.612267,0.967366 2.948156,0.967364 0.752386,2e-6 1.38194,-0.06142 1.888663,-0.184259 0.522059,-0.138193 0.913611,-0.268711 1.174656,-0.391552 l 0.299422,1.796532 c -0.261045,0.138195 -0.721694,0.284068 -1.381948,0.437617 -0.644918,0.15355 -1.381957,0.230325 -2.211118,0.230325 -1.044145,0 -1.950088,-0.15355 -2.717831,-0.460649 -0.752398,-0.322454 -1.374274,-0.760071 -1.865631,-1.312851 -0.491361,-0.552778 -0.85988,-1.205364 -1.105558,-1.95776 -0.230326,-0.767745 -0.345489,-1.604591 -0.345487,-2.51054 m 8.222592,-1.174656 c 0.01535,-0.967356 -0.230334,-1.758137 -0.737039,-2.372345 -0.491367,-0.629544 -1.174663,-0.944321 -2.04989,-0.944331 -0.491365,10e-6 -0.928982,0.09982 -1.312851,0.299422 -0.368524,0.18427 -0.683301,0.429949 -0.944331,0.737039 -0.261039,0.307109 -0.468331,0.660273 -0.621877,1.059494 -0.138198,0.399237 -0.230328,0.806144 -0.27639,1.220721 l 5.942378,0"
style="font-size:23.03247261px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;font-family:Ubuntu;-inkscape-font-specification:Ubuntu"
id="path3887"
inkscape:connector-curvature="0" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -6,7 +6,7 @@ FEATURES
- Secure Backups
ente provides end-to-end encrypted cloud backups so that you don't have to worry
about losing your tokens. We use the same protocols Ente Photos uses to encrypt
about losing your tokens. We use the same protocols ente Photos uses to encrypt
and preserve your data.
- Multi Device Synchronization

View File

@@ -42,7 +42,3 @@ class InvalidStateError extends AssertionError {
class SrpSetupNotCompleteError extends Error {}
class AuthenticatorKeyNotFound extends Error {}
class PassKeySessionNotVerifiedError extends Error {}
class PassKeySessionExpiredError extends Error {}

View File

@@ -269,7 +269,6 @@
"privacy": "Privacy",
"terms": "Terms",
"checkForUpdates": "Check for updates",
"checkStatus": "Check status",
"downloadUpdate": "Download",
"criticalUpdateAvailable": "Critical update available",
"updateAvailable": "Update available",
@@ -418,9 +417,6 @@
"waitingForBrowserRequest": "Waiting for browser request...",
"waitingForVerification": "Waiting for verification...",
"passkey": "Passkey",
"passKeyPendingVerification": "Verification is still pending",
"loginSessionExpired" : "Session expired",
"loginSessionExpiredDetails": "Your session has expired. Please login again.",
"developerSettingsWarning":"Are you sure that you want to modify Developer settings?",
"developerSettings": "Developer settings",
"serverEndpoint": "Server endpoint",

View File

@@ -20,8 +20,6 @@
"codeIssuerHint": "Emittente",
"codeSecretKeyHint": "Codice segreto",
"codeAccountHint": "Account (username@dominio.it)",
"codeTagHint": "Tag",
"accountKeyType": "Tipo di chiave",
"sessionExpired": "Sessione scaduta",
"@sessionExpired": {
"description": "Title of the dialog when the users current session is invalid/expired"
@@ -79,19 +77,16 @@
"data": "Dati",
"importCodes": "Importa codici",
"importTypePlainText": "Testo in chiaro",
"importTypeEnteEncrypted": "Esportazione Ente criptata",
"passwordForDecryptingExport": "Password per decriptare il file esportato",
"passwordEmptyError": "La password è obbligatoria",
"importFromApp": "Importa codici da {appName}",
"importGoogleAuthGuide": "Esporta i tuoi account da Google Authenticator in un codice QR utilizzando l'opzione \"Trasferisci Account\". Quindi, usando un altro dispositivo, scansiona il codice QR.\n\nSuggerimento: Puoi usare la webcam del tuo computer portatile per scattare una foto del codice QR.",
"importSelectJsonFile": "Seleziona file JSON",
"importSelectAppExport": "Seleziona il file di esportazione {appName}",
"importEnteEncGuide": "Seleziona il file JSON criptato esportato da Ente",
"importRaivoGuide": "Utilizza l'opzione \"Esporta i codici OTP in archivio Zip\" nelle impostazioni di Raivo.\n\nEstrai il file zip e importa il file JSON.",
"importBitwardenGuide": "Utilizzare l'opzione \"Esporta vault\" all'interno di Bitwarden Tools e importa il file JSON non crittografato.",
"importAegisGuide": "Usa l'opzione \"Esporta la cassaforte\" nelle impostazioni di Aegis.\n\nSe la tua cassaforte è criptata, dovrai inserire la password della cassaforte per decriptarla.",
"import2FasGuide": "Utilizza l'opzione \"Impostazioni->Backup -Export\" in 2FAS.\n\nSe il backup è crittografato, è necessario inserire la password per decriptare il backup",
"importLastpassGuide": "Usa l'opzione \"Trasferisci account\" all'interno delle impostazioni di Lastpass Authenticator e premi \"Esporta account su file\". Importa il JSON scaricato.",
"exportCodes": "Esporta codici",
"importLabel": "Importa",
"importInstruction": "Per favore seleziona un file contenente una lista dei tuoi codici nel seguente formato",
@@ -116,22 +111,18 @@
"copied": "Copiato",
"pleaseTryAgain": "Per favore riprova",
"existingUser": "Accedi",
"newUser": "Nuovo utente",
"delete": "Cancella",
"enterYourPasswordHint": "Inserisci la tua password",
"forgotPassword": "Password dimenticata",
"oops": "Oops",
"suggestFeatures": "Suggerisci funzionalità",
"faq": "FAQ",
"faq_q_1": "Quanto è sicuro Auth?",
"faq_a_1": "Tutti i codici di cui fai il backup tramite Auth sono memorizzati con crittografia end-to-end. Ciò significa che solo tu puoi accedere ai tuoi codici. Le nostre app sono open source e la nostra crittografia è stata verificata esternamente.",
"faq_q_2": "Posso accedere ai miei codici sul desktop?",
"faq_a_2": "Puoi accedere ai tuoi codici sul web @ auth.ente.io.",
"faq_q_3": "Come posso cancellare i codici?",
"faq_a_3": "Puoi eliminare un codice scorrendo il dito a sinistra sul codice in questione.",
"faq_q_4": "Come posso supportare questo progetto?",
"faq_a_4": "Puoi supportare lo sviluppo di questo progetto abbonandoti alla nostra app Photos @ ente.io.",
"faq_q_5": "Come posso abilitare il blocco FaceID in Auth",
"faq_a_5": "Puoi abilitare il blocco FaceID in Impostazioni → Sicurezza → Schermata di blocco.",
"somethingWentWrongMessage": "Qualcosa è andato storto, per favore riprova",
"leaveFamily": "Abbandona il piano famiglia",
@@ -145,8 +136,6 @@
"enterCodeHint": "Inserisci il codice di 6 cifre dalla tua app di autenticazione",
"lostDeviceTitle": "Dispositivo perso?",
"twoFactorAuthTitle": "Autenticazione a due fattori",
"passkeyAuthTitle": "Verifica della passkey",
"verifyPasskey": "Verifica passkey",
"recoverAccount": "Recupera account",
"enterRecoveryKeyHint": "Inserisci la tua chiave di recupero",
"recover": "Recupera",
@@ -158,7 +147,6 @@
}
}
},
"invalidQRCode": "Codice QR non valido",
"noRecoveryKeyTitle": "Nessuna chiave di recupero?",
"enterEmailHint": "Inserisci il tuo indirizzo email",
"invalidEmailTitle": "Indirizzo email non valido",
@@ -202,9 +190,6 @@
"doThisLater": "Fallo più tardi",
"saveKey": "Salva chiave",
"save": "Salva",
"send": "Invia",
"saveOrSendDescription": "Vuoi salvarlo nel tuo spazio di archiviazione (cartella Download per impostazione predefinita) o inviarlo ad altre applicazioni?",
"saveOnlyDescription": "Vuoi salvarlo nel tuo spazio di archiviazione (cartella Download per impostazione predefinita)?",
"back": "Indietro",
"createAccount": "Crea account",
"passwordStrength": "Forza password: {passwordStrengthValue}",
@@ -352,7 +337,6 @@
"deleteCodeAuthMessage": "Autenticarsi per cancellare il codice",
"showQRAuthMessage": "Autenticarsi per mostrare il codice QR",
"confirmAccountDeleteTitle": "Conferma l'eliminazione dell'account",
"confirmAccountDeleteMessage": "Questo account è collegato ad altre app di Ente, se ne utilizzi.\n\nI tuoi dati caricati, su tutte le app di Ente, saranno pianificati per la cancellazione e il tuo account verrà eliminato definitivamente.",
"androidBiometricHint": "Verifica l'identità",
"@androidBiometricHint": {
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
@@ -413,28 +397,5 @@
"doNotSignOut": "Non uscire",
"hearUsWhereTitle": "Dove hai sentito parlare di Ente? (opzionale)",
"hearUsExplanation": "Non teniamo traccia delle installazioni dell'app. Sarebbe utile se ci dicessi dove ci hai trovato!",
"recoveryKeySaved": "Chiave di recupero salvata nella cartella Download!",
"waitingForBrowserRequest": "In attesa della richiesta del browser...",
"waitingForVerification": "In attesa di verifica...",
"passkey": "Passkey",
"developerSettingsWarning": "Siete sicuri di voler modificare le impostazioni sviluppatore?",
"developerSettings": "Impostazioni sviluppatore",
"serverEndpoint": "Endpoint del server",
"invalidEndpoint": "Endpoint invalido",
"invalidEndpointMessage": "Spiacenti, l'endpoint inserito non è valido. Inserisci un endpoint valido e riprova.",
"endpointUpdatedMessage": "Endpoint aggiornato con successo",
"customEndpoint": "Connesso a {endpoint}",
"pinText": "Fissa",
"unpinText": "Sgancia",
"pinnedCodeMessage": "{code} è stato fissato",
"unpinnedCodeMessage": "{code} è stato sganciato",
"tags": "Tag",
"createNewTag": "Crea un nuovo tag",
"tag": "Tag",
"create": "Crea",
"editTag": "Modifica tag",
"deleteTagTitle": "Eliminare il tag?",
"deleteTagMessage": "Sei sicuro di voler eliminare questo tag? Questa azione è irreversibile.",
"somethingWentWrongParsingCode": "Non siamo riusciti ad analizzare i codici {x}.",
"updateNotAvailable": "Aggiornamento non disponibile"
"passkey": "Passkey"
}

View File

@@ -19,7 +19,7 @@
"pleaseVerifyDetails": "Por favor, verifique os detalhes e tente novamente",
"codeIssuerHint": "Emissor",
"codeSecretKeyHint": "Chave secreta",
"codeAccountHint": "Conta (você@domínio.com)",
"codeAccountHint": "Conta (voce@dominio.com)",
"codeTagHint": "Etiqueta",
"accountKeyType": "Tipo de chave",
"sessionExpired": "Sessão expirada",
@@ -27,7 +27,7 @@
"description": "Title of the dialog when the users current session is invalid/expired"
},
"pleaseLoginAgain": "Por favor, faça login novamente",
"loggingOut": "Saindo...",
"loggingOut": "Desconectando...",
"timeBasedKeyType": "Baseado no horário (TOTP)",
"counterBasedKeyType": "Baseado em um contador (HOTP)",
"saveAction": "Salvar",
@@ -51,7 +51,7 @@
"reportABug": "Informar um problema",
"crashAndErrorReporting": "Reporte de erros e falhas",
"reportBug": "Informar problema",
"emailUsMessage": "Envie um e-mail para {email}",
"emailUsMessage": "Por favor, envie um e-mail para {email}",
"@emailUsMessage": {
"placeholders": {
"email": {
@@ -59,12 +59,12 @@
}
}
},
"contactSupport": "Falar com o Suporte",
"contactSupport": "Falar com o suporte",
"rateUsOnStore": "Avalie-nos na {storeName}",
"blog": "Blog",
"merchandise": "Produtos",
"verifyPassword": "Verificar senha",
"pleaseWait": "Aguarde...",
"pleaseWait": "Por favor, aguarde...",
"generatingEncryptionKeysTitle": "Gerando chaves de criptografia...",
"recreatePassword": "Recriar senha",
"recreatePasswordMessage": "O dispositivo atual não é poderoso o suficiente para verificar sua senha, mas podemos regenerar de uma forma que funcione com todos os dispositivos.\n\nPor favor, faça o login usando sua chave de recuperação e recrie sua senha (você pode usar o mesmo novamente se desejar).",
@@ -81,10 +81,10 @@
"importTypePlainText": "Texto simples",
"importTypeEnteEncrypted": "Exportação Ente criptografada",
"passwordForDecryptingExport": "Senha para descriptografar a exportação",
"passwordEmptyError": "A senha não pode estar vazia",
"passwordEmptyError": "O campo senha não pode estar vazio",
"importFromApp": "Importar códigos do {appName}",
"importGoogleAuthGuide": "Exporte suas contas do Google Authenticator para um QR code usando a opção \"Transferir contas\". Então, usando outro dispositivo, escaneie o QR code.\n\nDica: Você pode usar a câmera do seu notebook para fotografar o QR code.",
"importSelectJsonFile": "Selecionar arquivo JSON",
"importSelectJsonFile": "Selecione o arquivo JSON",
"importSelectAppExport": "Selecione o arquivo de exportação do aplicativo {appName}",
"importEnteEncGuide": "Selecione o arquivo JSON criptografado exportado do Ente",
"importRaivoGuide": "Use a opção \"Exportar OTPs para arquivo Zip\" nas configurações do Raivo.\n\nExtraia o arquivo zip e importe o arquivo JSON.",
@@ -92,7 +92,7 @@
"importAegisGuide": "Use a opção \"Exportar cofre\" nas Configurações do Aegis.\n\nSe o seu cofre estiver criptografado, você precisará inserir a senha do cofre para descriptografá-lo.",
"import2FasGuide": "Use a opção \"Configurações->Exportar cópia de segurança\" no aplicativo 2FAS.\n\nSe a cópia de segurança estiver criptografada, será necessário inserir a senha para descriptografá-la",
"importLastpassGuide": "Use a opção \"Transferir contas\" nas configurações do LastPass Authenticator e pressione \"Exportar contas para arquivo\". Importe o arquivo JSON baixado.",
"exportCodes": "Exportar códigos",
"exportCodes": "Exportar Códigos",
"importLabel": "Importar",
"importInstruction": "Por favor, selecione um arquivo que contenha uma lista de códigos no seguinte formato",
"importCodeDelimiterInfo": "Os códigos podem ser separados por uma vírgula ou uma nova linha",
@@ -114,14 +114,14 @@
"general": "Geral",
"settings": "Ajustes",
"copied": "Copiado",
"pleaseTryAgain": "Tente de novo",
"existingUser": "Usuário existente",
"pleaseTryAgain": "Por favor, tente novamente",
"existingUser": "Usuário Existente",
"newUser": "Novo no Ente",
"delete": "Excluir",
"enterYourPasswordHint": "Insira sua senha",
"forgotPassword": "Esqueci a senha",
"oops": "Opa",
"suggestFeatures": "Sugerir recursos",
"suggestFeatures": "Sugerir funcionalidades",
"faq": "Perguntas frequentes",
"faq_q_1": "Quão seguro é o Auth?",
"faq_a_1": "Todos os códigos que você faz backup via Auth são armazenados criptografados de ponta a ponta. Isso significa que somente você pode acessar seus códigos. Nossos aplicativos são de código aberto e nossa criptografia foi auditada externamente.",
@@ -143,12 +143,12 @@
"verify": "Verificar",
"verifyEmail": "Verificar e-mail",
"enterCodeHint": "Digite o código de 6 dígitos de\nseu aplicativo autenticador",
"lostDeviceTitle": "Perdeu um dispositivo?",
"lostDeviceTitle": "Perdeu seu dispositivo?",
"twoFactorAuthTitle": "Autenticação de dois fatores",
"passkeyAuthTitle": "Autenticação via Chave de acesso",
"verifyPasskey": "Verificar senha-mestra",
"verifyPasskey": "Verificar chave de acesso",
"recoverAccount": "Recuperar conta",
"enterRecoveryKeyHint": "Digite a chave de recuperação",
"enterRecoveryKeyHint": "Digite sua chave de recuperação",
"recover": "Recuperar",
"contactSupportViaEmailMessage": "Por favor, envie um e-mail para {email} a partir do seu endereço de e-mail registrado",
"@contactSupportViaEmailMessage": {
@@ -160,7 +160,7 @@
},
"invalidQRCode": "QR Code inválido",
"noRecoveryKeyTitle": "Sem chave de recuperação?",
"enterEmailHint": "Insira o endereço de e-mail",
"enterEmailHint": "Insira o seu endereço de e-mail",
"invalidEmailTitle": "Endereço de e-mail inválido",
"invalidEmailMessage": "Por favor, insira um endereço de e-mail válido.",
"deleteAccount": "Excluir conta",
@@ -175,8 +175,8 @@
"moderateStrength": "Moderada",
"confirmPassword": "Confirme sua senha",
"close": "Fechar",
"oopsSomethingWentWrong": "Opa. Algo deu errado.",
"selectLanguage": "Trocar idioma",
"oopsSomethingWentWrong": "Oops, Algo deu errado.",
"selectLanguage": "Selecionar idioma",
"language": "Idioma",
"social": "Redes sociais",
"security": "Segurança",
@@ -199,14 +199,14 @@
"recoveryKeyCopiedToClipboard": "A chave de recuperação foi copiada para a área de transferência",
"recoveryKeyOnForgotPassword": "Caso você esqueça sua senha, a única maneira de recuperar seus dados é com essa chave.",
"recoveryKeySaveDescription": "Não armazenamos essa chave, por favor, salve essa chave de 24 palavras em um lugar seguro.",
"doThisLater": "Fazer isso depois",
"doThisLater": "Fazer isso mais tarde",
"saveKey": "Salvar chave",
"save": "Salvar",
"send": "Enviar",
"saveOrSendDescription": "Você deseja salvar isso no seu armazenamento (pasta de downloads por padrão) ou enviá-lo para outros aplicativos?",
"saveOnlyDescription": "Você deseja salvar isto no seu armazenamento (pasta de downloads por padrão)?",
"back": "Voltar",
"createAccount": "Criar conta",
"createAccount": "Criar uma conta",
"passwordStrength": "Força da senha: {passwordStrengthValue}",
"@passwordStrength": {
"description": "Text to indicate the password strength",
@@ -234,7 +234,7 @@
"passwordChangedSuccessfully": "Senha alterada com sucesso",
"generatingEncryptionKeys": "Gerando chaves de criptografia...",
"continueLabel": "Continuar",
"insecureDevice": "Dispositivo inseguro",
"insecureDevice": "Dispositivo não seguro",
"sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Desculpe, não foi possível gerar chaves seguras neste dispositivo.\n\npor favor, faça o login com um dispositivo diferente.",
"howItWorks": "Como funciona",
"ackPasswordLostWarning": "Eu entendo que se eu perder minha senha, posso perder meus dados, já que meus dados são <underline>criptografados de ponta a ponta</underline>.",
@@ -257,11 +257,11 @@
"recoveryKeyVerifyReason": "Sua chave de recuperação é a única maneira de recuperar suas fotos se você esquecer sua senha. Você pode encontrar sua chave de recuperação em Configurações > Conta.\n\nDigite sua chave de recuperação aqui para verificar se você a salvou corretamente.",
"confirmYourRecoveryKey": "Confirme sua chave de recuperação",
"confirm": "Confirmar",
"emailYourLogs": "Enviar logs por e-mail",
"emailYourLogs": "Enviar por email seus logs",
"pleaseSendTheLogsTo": "Por favor, envie os logs para \n{toEmail}",
"copyEmailAddress": "Copiar endereço de e-mail",
"exportLogs": "Exportar logs",
"enterYourRecoveryKey": "Digite a chave de recuperação",
"enterYourRecoveryKey": "Digite sua chave de recuperação",
"tempErrorContactSupportIfPersists": "Parece que algo deu errado. Por favor, tente novamente mais tarde. Se o erro persistir, entre em contato com nossa equipe de suporte.",
"itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Parece que algo deu errado. Por favor, tente novamente mais tarde. Se o erro persistir, entre em contato com nossa equipe de suporte.",
"about": "Sobre",
@@ -277,7 +277,7 @@
"youAreOnTheLatestVersion": "Você está na versão mais recente",
"warning": "Atenção",
"exportWarningDesc": "O arquivo exportado contém informações confidenciais. Por favor, armazene-o com segurança.",
"iUnderStand": "Entendo",
"iUnderStand": "Eu entendo",
"@iUnderStand": {
"description": "Text for the button to confirm the user understands the warning"
},
@@ -326,11 +326,11 @@
"sorryTheCodeYouveEnteredIsIncorrect": "Desculpe, o código que você inseriu está incorreto",
"emailChangedTo": "E-mail alterado para {newEmail}",
"authenticationFailedPleaseTryAgain": "Falha na autenticação. Por favor, tente novamente",
"authenticationSuccessful": "Autenticado!",
"authenticationSuccessful": "Autenticação bem-sucedida!",
"twofactorAuthenticationSuccessfullyReset": "Autenticação de dois fatores redefinida com sucesso",
"incorrectRecoveryKey": "Chave de recuperação incorreta",
"theRecoveryKeyYouEnteredIsIncorrect": "A chave de recuperação inserida está incorreta",
"enterPassword": "Inserir senha",
"enterPassword": "Insira a senha",
"selectExportFormat": "Selecione o formato para exportação",
"exportDialogDesc": "As exportações criptografadas ficarão protegidas por uma senha de sua escolha.",
"encrypted": "Criptografado",
@@ -345,7 +345,7 @@
"showLargeIcons": "Mostrar ícones grandes",
"shouldHideCode": "Ocultar códigos",
"doubleTapToViewHiddenCode": "Você pode tocar duas vezes em uma entrada para ver o código",
"focusOnSearchBar": "Foco na busca ao iniciar o app",
"focusOnSearchBar": "Foco na pesquisa ao iniciar o aplicativo",
"confirmUpdatingkey": "Você tem certeza que deseja atualizar a chave secreta?",
"minimizeAppOnCopy": "Minimizar aplicativo ao copiar",
"editCodeAuthMessage": "Autenticar para editar o código",
@@ -357,7 +357,7 @@
"@androidBiometricHint": {
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
},
"androidBiometricNotRecognized": "Não reconhecido. Tente de novo.",
"androidBiometricNotRecognized": "Não reconhecido. Tente novamente.",
"@androidBiometricNotRecognized": {
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
},
@@ -385,7 +385,7 @@
"@androidDeviceCredentialsSetupDescription": {
"description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side."
},
"goToSettings": "Ir para Ajustes",
"goToSettings": "Ir para Configurações",
"@goToSettings": {
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
},
@@ -410,13 +410,13 @@
"signOutFromOtherDevices": "Terminar sessão em outros dispositivos",
"signOutOtherBody": "Se você acha que alguém pode saber sua senha, você pode forçar todos os outros dispositivos que estão com sua conta a desconectar.",
"signOutOtherDevices": "Terminar sessão em outros dispositivos",
"doNotSignOut": "Não sair",
"doNotSignOut": "Não encerrar sessão",
"hearUsWhereTitle": "Como você ouviu sobre o Ente? (opcional)",
"hearUsExplanation": "Não rastreamos instalações do aplicativo. Seria útil se você nos contasse onde nos encontrou!",
"recoveryKeySaved": "Chave de recuperação salva na pasta Downloads!",
"waitingForBrowserRequest": "Aguardando solicitação do navegador...",
"waitingForVerification": "Esperando por verificação...",
"passkey": "Senha-mestra",
"passkey": "Chave de acesso",
"developerSettingsWarning": "Tem certeza de que deseja modificar as configurações de Desenvolvedor?",
"developerSettings": "Configurações de desenvolvedor",
"serverEndpoint": "Endpoint do servidor",
@@ -429,7 +429,7 @@
"pinnedCodeMessage": "{code} foi fixado",
"unpinnedCodeMessage": "{code} foi desafixado",
"tags": "Etiquetas",
"createNewTag": "Criar nova etiqueta",
"createNewTag": "Criar etiqueta",
"tag": "Etiqueta",
"create": "Criar",
"editTag": "Editar etiqueta",

View File

@@ -1 +0,0 @@
{}

View File

@@ -66,6 +66,8 @@ Future<void> initSystemTray() async {
void main() async {
WidgetsFlutterBinding.ensureInitialized();
initSystemTray().ignore();
if (PlatformUtil.isDesktop()) {
await windowManager.ensureInitialized();
await WindowListenerService.instance.init();
@@ -75,10 +77,8 @@ void main() async {
await windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.show();
await windowManager.focus();
initSystemTray().ignore();
});
}
await _runInForeground();
await _setupPrivacyScreen();
if (Platform.isAndroid) {
@@ -132,7 +132,7 @@ Future _runWithLogs(Function() function, {String prefix = ""}) async {
}
void _registerWindowsProtocol() {
const kWindowsScheme = 'enteauth';
const kWindowsScheme = 'ente';
// Register our protocol only on Windows platform
if (!kIsWeb && Platform.isWindows) {
WindowsProtocolHandler()

View File

@@ -42,7 +42,7 @@ class PasskeyService {
Future<void> openPasskeyPage(BuildContext context) async {
try {
final jwtToken = await getJwtToken();
final url = "https://accounts.ente.io/passkeys?token=$jwtToken";
final url = "https://accounts.ente.io/account-handoff?token=$jwtToken";
await launchUrlString(
url,
mode: LaunchMode.externalApplication,

View File

@@ -266,77 +266,32 @@ class UserService {
}
}
Future<dynamic> getTokenForPasskeySession(String sessionID) async {
try {
final response = await _dio.get(
"${_config.getHttpEndpoint()}/users/two-factor/passkeys/get-token",
queryParameters: {
"sessionID": sessionID,
},
);
return response.data;
} on DioException catch (e) {
if (e.response != null) {
if (e.response!.statusCode == 404 || e.response!.statusCode == 410) {
throw PassKeySessionExpiredError();
}
if (e.response!.statusCode == 400) {
throw PassKeySessionNotVerifiedError();
}
}
rethrow;
} catch (e, s) {
_logger.severe("unexpected error", e, s);
rethrow;
}
}
Future<void> onPassKeyVerified(BuildContext context, Map response) async {
final ProgressDialog dialog =
createProgressDialog(context, context.l10n.pleaseWait);
await dialog.show();
try {
final userPassword = _config.getVolatilePassword();
await _saveConfiguration(response);
if (userPassword == null) {
await dialog.hide();
// ignore: unawaited_futures
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (BuildContext context) {
return const PasswordReentryPage();
},
),
(route) => route.isFirst,
);
} else {
Widget page;
if (_config.getEncryptedToken() != null) {
await _config.decryptSecretsAndGetKeyEncKey(
userPassword,
_config.getKeyAttributes()!,
);
page = const HomePage();
} else {
throw Exception("unexpected response during passkey verification");
}
await dialog.hide();
final userPassword = Configuration.instance.getVolatilePassword();
if (userPassword == null) throw Exception("volatile password is null");
// ignore: unawaited_futures
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (BuildContext context) {
return page;
},
),
(route) => route.isFirst,
);
}
} catch (e) {
_logger.severe(e);
await dialog.hide();
rethrow;
await _saveConfiguration(response);
Widget page;
if (Configuration.instance.getEncryptedToken() != null) {
await Configuration.instance.decryptSecretsAndGetKeyEncKey(
userPassword,
Configuration.instance.getKeyAttributes()!,
);
page = const HomePage();
} else {
throw Exception("unexpected response during passkey verification");
}
// ignore: unawaited_futures
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (BuildContext context) {
return page;
},
),
(route) => route.isFirst,
);
}
Future<void> verifyEmail(
@@ -361,12 +316,9 @@ class UserService {
await dialog.hide();
if (response.statusCode == 200) {
Widget page;
final String passkeySessionID = response.data["passkeySessionID"];
final String twoFASessionID = response.data["twoFactorSessionID"];
if (twoFASessionID.isNotEmpty) {
page = TwoFactorAuthenticationPage(twoFASessionID);
} else if (passkeySessionID.isNotEmpty) {
page = PasskeyPage(passkeySessionID);
} else {
await _saveConfiguration(response);
if (Configuration.instance.getEncryptedToken() != null) {

View File

@@ -32,7 +32,7 @@ class CodeDisplayStore {
if (code.hasError) continue;
tags.addAll(code.display.tags);
}
return tags.toList()..sort();
return tags.toList();
}
Future<void> showDeleteTagDialog(BuildContext context, String tag) async {

View File

@@ -33,7 +33,7 @@ enum ButtonType {
Color defaultButtonColor(EnteColorScheme colorScheme) {
if (isPrimary) {
return colorScheme.primary400;
return colorScheme.primary500;
}
if (isSecondary) {
return colorScheme.fillFaint;

View File

@@ -238,8 +238,6 @@ class _HomePageState extends State<HomePage> {
title: !_showSearchBox
? const Text('Ente Auth')
: TextField(
autocorrect: false,
enableSuggestions: false,
focusNode: searchInputFocusNode,
autofocus: _searchText.isEmpty,
controller: _textController,

View File

@@ -2,14 +2,12 @@ import 'dart:convert';
import 'package:app_links/app_links.dart';
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/core/errors.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/models/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:ente_auth/utils/toast_util.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:url_launcher/url_launcher_string.dart';
@@ -43,38 +41,13 @@ class _PasskeyPageState extends State<PasskeyPage> {
Future<void> launchPasskey() async {
await launchUrlString(
"https://accounts.ente.io/passkeys/verify?"
"https://accounts.ente.io/passkeys/flow?"
"passkeySessionID=${widget.sessionID}"
"&redirect=enteauth://passkey"
"&clientPackage=io.ente.auth",
"&redirect=enteauth://passkey",
mode: LaunchMode.externalApplication,
);
}
Future<void> checkStatus() async {
late dynamic response;
try {
response = await UserService.instance
.getTokenForPasskeySession(widget.sessionID);
} on PassKeySessionNotVerifiedError {
showToast(context, context.l10n.passKeyPendingVerification);
return;
} on PassKeySessionExpiredError {
await showErrorDialog(
context,
context.l10n.loginSessionExpired,
context.l10n.loginSessionExpiredDetails,
);
Navigator.of(context).pop();
return;
} catch (e, s) {
_logger.severe("failed to check status", e, s);
showGenericErrorDialog(context: context).ignore();
return;
}
await UserService.instance.onPassKeyVerified(context, response);
}
Future<void> _handleDeeplink(String? link) async {
if (!context.mounted ||
Configuration.instance.hasConfiguredAccount() ||
@@ -86,20 +59,8 @@ class _PasskeyPageState extends State<PasskeyPage> {
}
try {
if (mounted && link.toLowerCase().startsWith("enteauth://passkey")) {
if (Configuration.instance.isLoggedIn()) {
_logger.info('ignored deeplink: already configured');
showToast(context, 'Account is already configured.');
return;
}
final parsedUri = Uri.parse(link);
final sessionID = parsedUri.queryParameters['passkeySessionID'];
if (sessionID != widget.sessionID) {
showToast(context, "Session ID mismatch");
_logger.warning('ignored deeplink: sessionID mismatch');
return;
}
final String? authResponse = parsedUri.queryParameters['response'];
String base64String = authResponse!.toString();
final String? uri = Uri.parse(link).queryParameters['response'];
String base64String = uri!.toString();
while (base64String.length % 4 != 0) {
base64String += '=';
}
@@ -157,23 +118,9 @@ class _PasskeyPageState extends State<PasskeyPage> {
const SizedBox(height: 16),
ButtonWidget(
buttonType: ButtonType.primary,
labelText: context.l10n.tryAgain,
labelText: context.l10n.verifyPasskey,
onTap: () => launchPasskey(),
),
const SizedBox(height: 16),
ButtonWidget(
buttonType: ButtonType.secondary,
labelText: context.l10n.checkStatus,
onTap: () async {
try {
await checkStatus();
} catch (e) {
debugPrint('failed to check status %e');
showGenericErrorDialog(context: context).ignore();
}
},
shouldSurfaceExecutionStates: true,
),
const Padding(padding: EdgeInsets.all(30)),
GestureDetector(
behavior: HitTestBehavior.opaque,

View File

@@ -139,10 +139,7 @@ Future<int?> _process2FasExportFile(
for (var item in decodedServices) {
var kind = item['otp']['tokenType'];
var account = item['otp']['account'] ?? '';
var issuer = item['otp']['issuer'];
if (issuer == null || (issuer as String).isEmpty) {
issuer = item['name'] ?? '';
}
var issuer = item['otp']['issuer'] ?? item['name'] ?? '';
var algorithm = item['otp']['algorithm'];
var secret = item['secret'];
var timer = item['otp']['period'];

View File

@@ -4,6 +4,7 @@ import 'dart:typed_data';
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/models/user_details.dart';
import 'package:ente_auth/services/auth_feature_flag.dart';
import 'package:ente_auth/services/local_authentication_service.dart';
import 'package:ente_auth/services/passkey_service.dart';
import 'package:ente_auth/services/user_service.dart';
@@ -65,17 +66,20 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
// We don't know if the user can disable MFA yet, so we fetch the info
UserService.instance.getUserDetailsV2().ignore();
}
final bool isInternalUser =
FeatureFlagService.instance.isInternalUserOrDebugBuild();
children.addAll([
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: CaptionedTextWidget(
title: l10n.passkey,
if (isInternalUser) sectionOptionSpacing,
if (isInternalUser)
MenuItemWidget(
captionedTextWidget: CaptionedTextWidget(
title: l10n.passkey,
),
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async => await onPasskeyClick(context),
),
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async => await onPasskeyClick(context),
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: CaptionedTextWidget(

View File

@@ -34,7 +34,7 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
"Debug" "Profile" "Release")
endif()
# Compilation settings that should be applied to most targets.
# Compilation ui.settings that should be applied to most targets.
#
# Be cautious about adding new options here, as plugins use this function by
# default. In most cases, you should add new options to specific targets instead
@@ -66,8 +66,8 @@ add_executable(${BINARY_NAME}
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
)
# Apply the standard set of build settings. This can be removed for applications
# that need different build settings.
# Apply the standard set of build ui.settings. This can be removed for applications
# that need different build ui.settings.
apply_standard_settings(${BINARY_NAME})
# Add dependency libraries. Add any application-specific dependencies here.
@@ -86,7 +86,6 @@ set_target_properties(${BINARY_NAME}
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
)
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
@@ -123,12 +122,6 @@ foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
COMPONENT Runtime)
endforeach(bundled_library)
# Copy the native assets provided by the build.dart from all packages.
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")

View File

@@ -63,7 +63,7 @@ static void my_application_activate(GApplication *application)
}
gtk_window_set_default_size(window, 1280, 720);
gtk_widget_show(GTK_WIDGET(window));
gtk_widget_realize(GTK_WIDGET(window));
g_autoptr(FlDartProject) project = fl_dart_project_new();
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
@@ -73,7 +73,6 @@ static void my_application_activate(GApplication *application)
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
gtk_widget_hide(GTK_WIDGET(window));
gtk_widget_grab_focus(GTK_WIDGET(view));
}
@@ -99,26 +98,6 @@ static gboolean my_application_local_command_line(GApplication *application, gch
return FALSE;
}
// Implements GApplication::startup.
static void my_application_startup(GApplication *application)
{
// MyApplication* self = MY_APPLICATION(object);
// Perform any actions required at application startup.
G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
}
// Implements GApplication::shutdown.
static void my_application_shutdown(GApplication *application)
{
// MyApplication* self = MY_APPLICATION(object);
// Perform any actions required at application shutdown.
G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
}
// Implements GObject::dispose.
static void my_application_dispose(GObject *object)
{
@@ -131,8 +110,6 @@ static void my_application_class_init(MyApplicationClass *klass)
{
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
G_APPLICATION_CLASS(klass)->startup = my_application_startup;
G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
}

View File

@@ -27,6 +27,3 @@ include:
- libffi.so.8
- libtiff.so.5
- libjpeg.so.8
supported_mime_type:
- x-scheme-handler/enteauth

View File

@@ -31,4 +31,4 @@ categories:
startup_notify: false
supported_mime_type:
- x-scheme-handler/enteauth
- x-scheme-handler/ente

View File

@@ -28,4 +28,4 @@ categories:
startup_notify: false
supported_mime_type:
- x-scheme-handler/enteauth
- x-scheme-handler/ente

View File

@@ -38,7 +38,6 @@
<key>CFBundleURLSchemes</key>
<array>
<string>otpauth</string>
<string>enteauth</string>
</array>
</dict>
</array>

View File

@@ -45,10 +45,10 @@ packages:
dependency: "direct main"
description:
name: archive
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
sha256: "0763b45fa9294197a2885c8567927e2830ade852e5c896fd4ab7e0e348d0f373"
url: "https://pub.dev"
source: hosted
version: "3.6.1"
version: "3.5.0"
args:
dependency: transitive
description:
@@ -117,10 +117,10 @@ packages:
dependency: transitive
description:
name: build_daemon
sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9"
sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
version: "4.0.1"
build_resolvers:
dependency: transitive
description:
@@ -133,18 +133,18 @@ packages:
dependency: "direct dev"
description:
name: build_runner
sha256: "644dc98a0f179b872f612d3eb627924b578897c629788e858157fa5e704ca0c7"
sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
url: "https://pub.dev"
source: hosted
version: "2.4.11"
version: "2.4.9"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
sha256: e3c79f69a64bdfcd8a776a3c28db4eb6e3fb5356d013ae5eb2e52007706d5dbe
sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799"
url: "https://pub.dev"
source: hosted
version: "7.3.1"
version: "7.3.0"
built_collection:
dependency: transitive
description:
@@ -293,9 +293,9 @@ packages:
dependency: "direct main"
description:
path: "packages/desktop_webview_window"
ref: main
resolved-ref: "726d8281a244d56ab36e843f0427c48de6d9cc56"
url: "https://github.com/MixinNetwork/flutter-plugins"
ref: fix-webkit-version
resolved-ref: fe2223e4edfecdbb3a97bb9e3ced73db4ae9d979
url: "https://github.com/ente-io/flutter-desktopwebview-fork"
source: git
version: "0.2.4"
device_info_plus:
@@ -415,10 +415,10 @@ packages:
dependency: "direct main"
description:
name: file_saver
sha256: d375b351e3331663abbaf99747abd72f159260c58fbbdbca9f926f02c01bdc48
sha256: bdebc720e17b3e01aba59da69b6d47020a7e5ba7d5c75bd9194f9618d5f16ef4
url: "https://pub.dev"
source: hosted
version: "0.2.13"
version: "0.2.12"
fixnum:
dependency: "direct main"
description:
@@ -444,10 +444,10 @@ packages:
dependency: "direct main"
description:
name: flutter_bloc
sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a
sha256: f0ecf6e6eb955193ca60af2d5ca39565a86b8a142452c5b24d96fb477428f4d2
url: "https://pub.dev"
source: hosted
version: "8.1.6"
version: "8.1.5"
flutter_context_menu:
dependency: "direct main"
description:
@@ -586,59 +586,59 @@ packages:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: c6b0b4c05c458e1c01ad9bcc14041dd7b1f6783d487be4386f793f47a8a4d03e
sha256: "8cf40eebf5dec866a6d1956ad7b4f7016e6c0cc69847ab946833b7d43743809f"
url: "https://pub.dev"
source: hosted
version: "2.0.20"
version: "2.0.19"
flutter_secure_storage:
dependency: "direct main"
description:
name: flutter_secure_storage
sha256: "165164745e6afb5c0e3e3fcc72a012fb9e58496fb26ffb92cf22e16a821e85d0"
sha256: ffdbb60130e4665d2af814a0267c481bcf522c41ae2e43caf69fa0146876d685
url: "https://pub.dev"
source: hosted
version: "9.2.2"
version: "9.0.0"
flutter_secure_storage_linux:
dependency: "direct overridden"
description:
path: flutter_secure_storage_linux
ref: develop
resolved-ref: cb30953edc029dc4059b72700270b4cd3a3afade
url: "https://github.com/mogol/flutter_secure_storage.git"
ref: patch-1
resolved-ref: da8ab43bc51c8c3249a261c33b27aa6f018f819b
url: "https://github.com/prateekmedia/flutter_secure_storage.git"
source: git
version: "1.2.1"
version: "1.2.0"
flutter_secure_storage_macos:
dependency: transitive
description:
name: flutter_secure_storage_macos
sha256: "1693ab11121a5f925bbea0be725abfcfbbcf36c1e29e571f84a0c0f436147a81"
sha256: bd33935b4b628abd0b86c8ca20655c5b36275c3a3f5194769a7b3f37c905369c
url: "https://pub.dev"
source: hosted
version: "3.1.2"
version: "3.0.1"
flutter_secure_storage_platform_interface:
dependency: transitive
description:
name: flutter_secure_storage_platform_interface
sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8
sha256: "0d4d3a5dd4db28c96ae414d7ba3b8422fd735a8255642774803b2532c9a61d7e"
url: "https://pub.dev"
source: hosted
version: "1.1.2"
version: "1.0.2"
flutter_secure_storage_web:
dependency: transitive
description:
name: flutter_secure_storage_web
sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9
sha256: "30f84f102df9dcdaa2241866a958c2ec976902ebdaa8883fbfe525f1f2f3cf20"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
version: "1.1.2"
flutter_secure_storage_windows:
dependency: transitive
description:
name: flutter_secure_storage_windows
sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709
sha256: "5809c66f9dd3b4b93b0a6e2e8561539405322ee767ac2f64d084e2ab5429d108"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
version: "3.0.0"
flutter_slidable:
dependency: "direct main"
description:
@@ -685,10 +685,10 @@ packages:
dependency: "direct main"
description:
name: fluttertoast
sha256: "7eae679e596a44fdf761853a706f74979f8dd3cd92cf4e23cae161fda091b847"
sha256: "81b68579e23fcbcada2db3d50302813d2371664afe6165bc78148050ab94bf66"
url: "https://pub.dev"
source: hosted
version: "8.2.6"
version: "8.2.5"
freezed_annotation:
dependency: transitive
description:
@@ -725,10 +725,10 @@ packages:
dependency: "direct main"
description:
name: gradient_borders
sha256: b1cd969552c83f458ff755aa68e13a0327d09f06c3f42f471b423b01427f21f8
sha256: "69eeaff519d145a4c6c213ada1abae386bcc8981a4970d923e478ce7ba19e309"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
version: "1.0.0"
graphs:
dependency: transitive
description:
@@ -757,10 +757,10 @@ packages:
dependency: transitive
description:
name: hashlib_codecs
sha256: a1c7b5d89ff29e81fd8e8c0b35966db4c935e149fc4ebe1ebf71e358c15863ab
sha256: "49e2a471f74b15f1854263e58c2ac11f2b631b5b12c836f9708a35397d36d626"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
version: "2.2.0"
hex:
dependency: transitive
description:
@@ -805,18 +805,18 @@ packages:
dependency: transitive
description:
name: image
sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8"
sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e"
url: "https://pub.dev"
source: hosted
version: "4.2.0"
version: "4.1.7"
intl:
dependency: "direct main"
description:
name: intl
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
url: "https://pub.dev"
source: hosted
version: "0.19.0"
version: "0.18.1"
io:
dependency: transitive
description:
@@ -853,26 +853,26 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
url: "https://pub.dev"
source: hosted
version: "10.0.4"
version: "10.0.0"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "2.0.1"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
url: "https://pub.dev"
source: hosted
version: "3.0.1"
version: "2.0.1"
lints:
dependency: "direct dev"
description:
@@ -893,18 +893,18 @@ packages:
dependency: "direct main"
description:
name: local_auth_android
sha256: "48dfb2d954da8ef6a77adfc93a29998f7729e9308eaa817e91dea4500317b2c8"
sha256: e0e5b1ea247c5a0951c13a7ee13dc1beae69750e6a2e1910d1ed6a3cd4d56943
url: "https://pub.dev"
source: hosted
version: "1.0.39"
version: "1.0.38"
local_auth_darwin:
dependency: "direct main"
description:
name: local_auth_darwin
sha256: e424ebf90d5233452be146d4a7da4bcd7a70278b67791592f3fde1bda8eef9e2
sha256: "33381a15b0de2279523eca694089393bb146baebdce72a404555d03174ebc1e9"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
version: "1.2.2"
local_auth_platform_interface:
dependency: transitive
description:
@@ -957,10 +957,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
url: "https://pub.dev"
source: hosted
version: "1.12.0"
version: "1.11.0"
mime:
dependency: transitive
description:
@@ -973,10 +973,10 @@ packages:
dependency: "direct dev"
description:
name: mocktail
sha256: "890df3f9688106f25755f26b1c60589a92b3ab91a22b8b224947ad041bf172d8"
sha256: c4b5007d91ca4f67256e720cb1b6d704e79a510183a12fa551021f652577dce6
url: "https://pub.dev"
source: hosted
version: "1.0.4"
version: "1.0.3"
modal_bottom_sheet:
dependency: "direct main"
description:
@@ -1085,18 +1085,18 @@ packages:
dependency: transitive
description:
name: path_provider_android
sha256: "9c96da072b421e98183f9ea7464898428e764bc0ce5567f27ec8693442e72514"
sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d
url: "https://pub.dev"
source: hosted
version: "2.2.5"
version: "2.2.4"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
version: "2.3.2"
path_provider_linux:
dependency: transitive
description:
@@ -1141,10 +1141,10 @@ packages:
dependency: transitive
description:
name: platform
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
url: "https://pub.dev"
source: hosted
version: "3.1.5"
version: "3.1.4"
plugin_platform_interface:
dependency: transitive
description:
@@ -1157,10 +1157,10 @@ packages:
dependency: "direct main"
description:
name: pointycastle
sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
sha256: "79fbafed02cfdbe85ef3fd06c7f4bc2cbcba0177e61b765264853d4253b21744"
url: "https://pub.dev"
source: hosted
version: "3.9.1"
version: "3.9.0"
pool:
dependency: transitive
description:
@@ -1205,10 +1205,10 @@ packages:
dependency: transitive
description:
name: pubspec_parse
sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8
sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367
url: "https://pub.dev"
source: hosted
version: "1.3.0"
version: "1.2.3"
qr:
dependency: transitive
description:
@@ -1245,18 +1245,18 @@ packages:
dependency: "direct main"
description:
name: sentry
sha256: "57514bc72d441ffdc463f498d6886aa586a2494fa467a1eb9d649c28010d7ee3"
sha256: e572d33a3ff1d69549f33ee828a8ff514047d43ca8eea4ab093d72461205aa3e
url: "https://pub.dev"
source: hosted
version: "7.20.2"
version: "7.20.1"
sentry_flutter:
dependency: "direct main"
description:
name: sentry_flutter
sha256: "9723d58470ca43a360681ddd26abb71ca7b815f706bc8d3747afd054cf639ded"
sha256: ac8cf6bb849f3560353ae33672e17b2713809a4e8de0d3cf372e9e9c42013757
url: "https://pub.dev"
source: hosted
version: "7.20.2"
version: "7.20.1"
share_plus:
dependency: "direct main"
description:
@@ -1285,18 +1285,18 @@ packages:
dependency: transitive
description:
name: shared_preferences_android
sha256: "93d0ec9dd902d85f326068e6a899487d1f65ffcd5798721a95330b26c8131577"
sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2"
url: "https://pub.dev"
source: hosted
version: "2.2.3"
version: "2.2.2"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7"
sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
version: "2.3.5"
shared_preferences_linux:
dependency: transitive
description:
@@ -1341,10 +1341,10 @@ packages:
dependency: transitive
description:
name: shelf_web_socket
sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611"
sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
version: "1.0.4"
shortid:
dependency: transitive
description:
@@ -1370,10 +1370,10 @@ packages:
dependency: transitive
description:
name: sodium_libs
sha256: "441444f6f433032bae3444c2ef5ed2cf5bc0def77f104abdff20aedcf79a7c7a"
sha256: f7f6719b7ab3e8512ce7a5ecd7bc8d865482431cdd5a07a46b55b13c152b54e1
url: "https://pub.dev"
source: hosted
version: "2.2.1+5"
version: "2.2.1+1"
source_gen:
dependency: transitive
description:
@@ -1411,10 +1411,10 @@ packages:
description:
path: sqflite
ref: HEAD
resolved-ref: "3309d399dd7d695bbfa7c05f643bb16765cef4ee"
resolved-ref: f281785e12e8b1abf2f9d41a587fc83d810724cf
url: "https://github.com/tekartik/sqflite"
source: git
version: "2.3.3+1"
version: "2.3.3"
sqflite_common:
dependency: transitive
description:
@@ -1435,18 +1435,18 @@ packages:
dependency: "direct main"
description:
name: sqlite3
sha256: b384f598b813b347c5a7e5ffad82cbaff1bec3d1561af267041e66f6f0899295
sha256: "1abbeb84bf2b1a10e5e1138c913123c8aa9d83cd64e5f9a0dd847b3c83063202"
url: "https://pub.dev"
source: hosted
version: "2.4.3"
version: "2.4.2"
sqlite3_flutter_libs:
dependency: "direct main"
description:
name: sqlite3_flutter_libs
sha256: "9f89a7e7dc36eac2035808427eba1c3fbd79e59c3a22093d8dace6d36b1fe89e"
sha256: fb2a106a2ea6042fe57de2c47074cc31539a941819c91e105b864744605da3f5
url: "https://pub.dev"
source: hosted
version: "0.5.23"
version: "0.5.21"
stack_trace:
dependency: transitive
description:
@@ -1523,10 +1523,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev"
source: hosted
version: "0.7.0"
version: "0.6.1"
timezone:
dependency: transitive
description:
@@ -1547,10 +1547,10 @@ packages:
dependency: "direct main"
description:
name: tray_manager
sha256: c9a63fd88bd3546287a7eb8ccc978d707eef82c775397af17dda3a4f4c039e64
sha256: e0ac9a88b2700f366b8629b97e8663b6ef450a2f169560a685dc167bfe9c9c29
url: "https://pub.dev"
source: hosted
version: "0.2.3"
version: "0.2.2"
tuple:
dependency: "direct main"
description:
@@ -1579,26 +1579,26 @@ packages:
dependency: "direct main"
description:
name: url_launcher
sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3"
sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e"
url: "https://pub.dev"
source: hosted
version: "6.3.0"
version: "6.2.6"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: ceb2625f0c24ade6ef6778d1de0b2e44f2db71fded235eb52295247feba8c5cf
sha256: "360a6ed2027f18b73c8d98e159dda67a61b7f2e0f6ec26e86c3ada33b0621775"
url: "https://pub.dev"
source: hosted
version: "6.3.3"
version: "6.3.1"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: "7068716403343f6ba4969b4173cbf3b84fc768042124bc2c011e5d782b24fe89"
sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5"
url: "https://pub.dev"
source: hosted
version: "6.3.0"
version: "6.2.5"
url_launcher_linux:
dependency: transitive
description:
@@ -1611,10 +1611,10 @@ packages:
dependency: transitive
description:
name: url_launcher_macos
sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de"
sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234
url: "https://pub.dev"
source: hosted
version: "3.2.0"
version: "3.1.0"
url_launcher_platform_interface:
dependency: transitive
description:
@@ -1683,10 +1683,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
url: "https://pub.dev"
source: hosted
version: "14.2.1"
version: "13.0.0"
watcher:
dependency: transitive
description:
@@ -1703,30 +1703,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.5.1"
web_socket:
dependency: transitive
description:
name: web_socket
sha256: "24301d8c293ce6fe327ffe6f59d8fd8834735f0ec36e4fd383ec7ff8a64aa078"
url: "https://pub.dev"
source: hosted
version: "0.1.5"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: a2d56211ee4d35d9b344d9d4ce60f362e4f5d1aafb988302906bd732bc731276
sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
version: "2.4.5"
win32:
dependency: "direct main"
description:
name: win32
sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4
sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb"
url: "https://pub.dev"
source: hosted
version: "5.5.1"
version: "5.5.0"
win32_registry:
dependency: transitive
description:
@@ -1739,10 +1731,10 @@ packages:
dependency: "direct main"
description:
name: window_manager
sha256: "8699323b30da4cdbe2aa2e7c9de567a6abd8a97d9a5c850a3c86dcd0b34bbfbf"
sha256: b3c895bdf936c77b83c5254bec2e6b3f066710c1f89c38b20b8acc382b525494
url: "https://pub.dev"
source: hosted
version: "0.3.9"
version: "0.3.8"
xdg_directories:
dependency: transitive
description:
@@ -1776,5 +1768,5 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.4.0 <4.0.0"
flutter: ">=3.22.0"
dart: ">=3.3.0 <4.0.0"
flutter: ">=3.19.0"

View File

@@ -1,6 +1,6 @@
name: ente_auth
description: ente two-factor authenticator
version: 3.0.12+312
version: 3.0.8+308
publish_to: none
environment:
@@ -20,8 +20,8 @@ dependencies:
convert: ^3.1.1
desktop_webview_window:
git:
url: https://github.com/MixinNetwork/flutter-plugins
ref: main
url: https://github.com/ente-io/flutter-desktopwebview-fork
ref: fix-webkit-version
path: packages/desktop_webview_window
device_info_plus: ^9.1.1
dio: ^5.4.0
@@ -64,7 +64,7 @@ dependencies:
google_nav_bar: ^5.0.5 #supported
gradient_borders: ^1.0.0
http: ^1.1.0
intl: ^0.19.0
intl: ^0.18.0
json_annotation: ^4.5.0
local_auth: ^2.2.0
local_auth_android: ^1.0.37
@@ -102,13 +102,13 @@ dependencies:
url_launcher: ^6.1.5
uuid: ^4.2.2
win32: ^5.1.1
window_manager: ^0.3.9
window_manager: ^0.3.8
dependency_overrides:
flutter_secure_storage_linux:
git:
url: https://github.com/mogol/flutter_secure_storage.git
ref: develop
url: https://github.com/prateekmedia/flutter_secure_storage.git
ref: patch-1
path: flutter_secure_storage_linux
dev_dependencies:
build_runner: ^2.1.11

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -5,8 +5,6 @@
endpoint:
api: "http://localhost:8080"
# Endpoint for the account service for passkey
accounts: "http://localhost:3001"
log:
http: false # log status code & time taken by requests

View File

@@ -71,15 +71,12 @@ func NewClient(p Params) *Client {
restClient: enteAPI,
downloadClient: resty.New().
SetRetryCount(3).
SetRetryWaitTime(10 * time.Second).
SetRetryMaxWaitTime(20 * time.Second).
SetRetryWaitTime(5 * time.Second).
SetRetryMaxWaitTime(10 * time.Second).
AddRetryCondition(func(r *resty.Response, err error) bool {
shouldRetry := r.StatusCode() == 429 || r.StatusCode() >= 500
shouldRetry := r.StatusCode() == 429 || r.StatusCode() > 500
if shouldRetry {
amxRequestID := r.Header().Get("X-Amz-Request-Id")
cfRayID := r.Header().Get("CF-Ray")
wasabiRefID := r.Header().Get("X-Wasabi-Cm-Reference-Id")
log.Printf("Retry scheduled. error statusCode: %d, X-Amz-Request-Id: %s, CF-Ray: %s, X-Wasabi-Cm-Reference-Id: %s", r.StatusCode(), amxRequestID, cfRayID, wasabiRefID)
log.Printf("retrying download due to %d code", r.StatusCode())
}
return shouldRetry
}),

View File

@@ -161,22 +161,3 @@ func (c *Client) VerifyTotp(
}
return &res, nil
}
func (c *Client) CheckPasskeyStatus(ctx context.Context,
sessionID string) (*AuthorizationResponse, error) {
var res AuthorizationResponse
r, err := c.restClient.R().
SetContext(ctx).
SetResult(&res).
Get("/users/two-factor/passkeys/get-token?sessionID=" + sessionID)
if err != nil {
return nil, err
}
if r.IsError() {
return nil, &ApiError{
StatusCode: r.StatusCode(),
Message: r.String(),
}
}
return &res, nil
}

View File

@@ -37,7 +37,6 @@ type AuthorizationResponse struct {
EncryptedToken string `json:"encryptedToken,omitempty"`
Token string `json:"token,omitempty"`
TwoFactorSessionID string `json:"twoFactorSessionID"`
PassKeySessionID string `json:"passkeySessionID"`
// SrpM2 is sent only if the user is logging via SRP
// SrpM2 is the SRP M2 value aka the proof that the server has the verifier
SrpM2 *string `json:"srpM2,omitempty"`
@@ -46,7 +45,3 @@ type AuthorizationResponse struct {
func (a *AuthorizationResponse) IsMFARequired() bool {
return a.TwoFactorSessionID != ""
}
func (a *AuthorizationResponse) IsPasskeyRequired() bool {
return a.PassKeySessionID != ""
}

View File

@@ -38,17 +38,6 @@ func GetUserInput(label string) (string, error) {
return input, nil
}
func WaitForEnter(prompt string) error {
fmt.Println(prompt)
// Create a new reader from standard input.
reader := bufio.NewReader(os.Stdin)
_, err := reader.ReadString('\n')
if err != nil {
return err
}
return nil
}
func GetAppType() api.App {
for {
app, err := GetUserInput("Enter app type (default: photos)")

View File

@@ -15,7 +15,7 @@ import (
"strings"
)
var AppVersion = "0.1.16"
var AppVersion = "0.1.14"
func main() {
cliDBPath, err := GetCLIConfigPath()
@@ -75,10 +75,6 @@ func main() {
}
return
}
if len(os.Args) == 1 {
// If no arguments are passed, show help
os.Args = append(os.Args, "help")
}
cmd.Execute(&ctrl, AppVersion)
}
@@ -89,7 +85,6 @@ func initConfig(cliConfigPath string) {
viper.AddConfigPath(".") // optionally look for config in the working directory
viper.SetDefault("endpoint.api", constants.EnteApiUrl)
viper.SetDefault("endpoint.accounts", constants.EnteAccountUrl)
viper.SetDefault("log.http", false)
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {

View File

@@ -58,10 +58,6 @@ func (c *ClICtrl) AddAccount(cxt context.Context) {
if authResponse.IsMFARequired() {
authResponse, flowErr = c.validateTOTP(cxt, authResponse)
}
if authResponse.IsPasskeyRequired() {
authResponse, flowErr = c.verifyPassKey(cxt, authResponse)
}
if authResponse.EncryptedToken == "" || authResponse.KeyAttributes == nil {
log.Fatalf("missing key attributes or token.\nNote: Please use the mobile,web or desktop app to create a new account.\nIf you are trying to login to an existing account, report a bug.")
}

View File

@@ -7,9 +7,7 @@ import (
"github.com/ente-io/cli/internal/api"
eCrypto "github.com/ente-io/cli/internal/crypto"
"github.com/ente-io/cli/pkg/model"
"github.com/ente-io/cli/utils/browser"
"github.com/ente-io/cli/utils/encoding"
"github.com/spf13/viper"
"log"
"github.com/kong/go-srp"
@@ -141,31 +139,6 @@ func (c *ClICtrl) validateTOTP(ctx context.Context, authResp *api.AuthorizationR
}
}
func (c *ClICtrl) verifyPassKey(ctx context.Context, authResp *api.AuthorizationResponse) (*api.AuthorizationResponse, error) {
if !authResp.IsPasskeyRequired() {
return authResp, nil
}
baseAccountUrl := viper.GetString("endpoint.accounts")
passkeyAuthUrl := fmt.Sprintf("%s/passkeys/verify?passkeySessionID=%s&redirect=ente-cli://passkey&clientPackage=io.ente.photos", baseAccountUrl, authResp.PassKeySessionID)
fmt.Printf("Open this url in browser to verify passkey: %s\n", passkeyAuthUrl)
err := browser.OpenURL(passkeyAuthUrl)
if err != nil {
fmt.Printf("Failed to open browser: %v\n", err)
}
for {
err = internal.WaitForEnter("Press enter once you have completed the passkey verification")
if err != nil {
return nil, err
}
totpResp, err := c.Client.CheckPasskeyStatus(ctx, authResp.PassKeySessionID)
if err != nil {
log.Printf("failed to verify %v", err)
continue
}
return totpResp, nil
}
}
func (c *ClICtrl) validateEmail(ctx context.Context, email string) (*api.AuthorizationResponse, error) {
err := c.Client.SendEmailOTP(ctx, email)
if err != nil {

View File

@@ -1,48 +0,0 @@
package browser
import (
"os/exec"
"runtime"
"strings"
)
// https://stackoverflow.com/questions/39320371/how-start-web-server-to-open-page-in-browser-in-golang
// openURL opens the specified URL in the default browser of the user.
func OpenURL(url string) error {
var cmd string
var args []string
switch runtime.GOOS {
case "windows":
cmd = "cmd"
args = []string{"/c", "start"}
case "darwin":
cmd = "open"
args = []string{url}
default: // "linux", "freebsd", "openbsd", "netbsd"
// Check if running under WSL
if isWSL() {
// Use 'cmd.exe /c start' to open the URL in the default Windows browser
cmd = "cmd.exe"
args = []string{"/c", "start", url}
} else {
// Use xdg-open on native Linux environments
cmd = "xdg-open"
args = []string{url}
}
}
if len(args) > 1 {
// args[0] is used for 'start' command argument, to prevent issues with URLs starting with a quote
args = append(args[:1], append([]string{""}, args[1:]...)...)
}
return exec.Command(cmd, args...).Start()
}
// isWSL checks if the Go program is running inside Windows Subsystem for Linux
func isWSL() bool {
releaseData, err := exec.Command("uname", "-r").Output()
if err != nil {
return false
}
return strings.Contains(strings.ToLower(string(releaseData)), "microsoft")
}

View File

@@ -1,6 +1,4 @@
package constants
const CliDataPath = "/cli-data/"
const EnteApiUrl = "https://api.ente.io"
const EnteAccountUrl = "https://account.ente.io"

View File

@@ -1,6 +1,6 @@
name: "Release"
# Build the desktop app with code from ente-io/ente and create/update a release.
# Build the ente-io/ente's desktop/rc branch and create/update a draft release.
#
# For more details, see `docs/release.md` in ente-io/ente.
@@ -17,10 +17,9 @@ on:
#
- cron: "45 2 * * 1-6"
push:
# Run when a tag matching the pattern "vd.d.d"" is pushed. Crucially for
# us, this excludes the "-rc" tags.
# Run when a tag matching the pattern "v*"" is pushed.
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
- "v*"
jobs:
release:

View File

@@ -1,17 +1,9 @@
# CHANGELOG
## v1.7.2 (Unreleased)
## v1.7.1 (Unreleased)
- .
## v1.7.1
- Support for passkeys as a second factor authentication mechanism.
- Remember the window size across app restarts.
- Revert changes to the Linux icon.
- Fix an issue causing deleted items in watched folders to not move to
uncategorized.
- Fix duplicate file uploads when initializing a folder watch (sometimes).
## v1.7.0

View File

@@ -53,24 +53,23 @@ This'll trigger the workflow and create a new pre-release. We can edit this to
add the release notes, convert it to a release. Once it is marked as latest, the
release goes live.
We are done at this point, and can now update the other pre-release that hosts
We are done at this point, and can now create a new pre-release to host
subsequent nightly builds.
1. Update `package.json` in the source repo to use version `1.x.x-rc`, and
merge these changes into `main`.
2. In the release repo, delete the existing _nightly_ pre-release, then:
2. In the release repo:
```sh
git tag 1.x.x-rc
git push origin 1.x.x-rc
```
3. Start a new run of the workflow (`gh workflow run desktop-release.yml`).
3. Once the workflow finishes and the pre-release is created, edit its
description to "Nightly builds".
Once the workflow finishes and the 1.x.x-rc pre-release is created, edit its
description to "Nightly builds". Subsequent scheduled nightly builds will update
this pre-release.
4. Delete the pre-release for the previous (already released) version.
## Workflow - Extra pre-releases

View File

@@ -6,9 +6,6 @@ files:
extraFiles:
- from: build
to: resources
protocols:
- name: Ente
schemes: ["ente"]
win:
target:
- target: nsis

View File

@@ -1,6 +1,6 @@
{
"name": "ente",
"version": "1.7.2-rc",
"version": "1.7.1-rc",
"private": true,
"description": "Desktop client for Ente Photos",
"repository": "github:ente-io/photos-desktop",
@@ -55,6 +55,6 @@
"typescript": "^5",
"typescript-eslint": "8.0.0-alpha.10"
},
"packageManager": "yarn@1.22.22",
"packageManager": "yarn@1.22.21",
"productName": "ente"
}

View File

@@ -61,103 +61,6 @@ export const allowWindowClose = (): void => {
shouldAllowWindowClose = true;
};
/**
* The app's entry point.
*
* We call this at the end of this file.
*/
const main = () => {
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
return;
}
let mainWindow: BrowserWindow | undefined;
initLogging();
logStartupBanner();
registerForEnteLinks();
// The order of the next two calls is important
setupRendererServer();
registerPrivilegedSchemes();
migrateLegacyWatchStoreIfNeeded();
/**
* Handle an open URL request, but ensuring that we have a mainWindow.
*/
const handleOpenURLEnsuringWindow = (url: string) => {
log.info(`Attempting to handle request to open URL: ${url}`);
if (mainWindow) handleEnteLinks(mainWindow, url);
else setTimeout(() => handleOpenURLEnsuringWindow(url), 1000);
};
app.on("second-instance", (_, argv: string[]) => {
// Someone tried to run a second instance, we should focus our window.
if (mainWindow) {
mainWindow.show();
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
}
// On Windows and Linux, this is how we get deeplinks.
// See: registerForEnteLinks
const url = argv.pop();
if (url) handleOpenURLEnsuringWindow(url);
});
// Emitted once, when Electron has finished initializing.
//
// Note that some Electron APIs can only be used after this event occurs.
void app.whenReady().then(() => {
void (async () => {
// Create window and prepare for the renderer.
mainWindow = createMainWindow();
// Setup IPC and streams.
const watcher = createWatcher(mainWindow);
attachIPCHandlers();
attachFSWatchIPCHandlers(watcher);
attachLogoutIPCHandler(watcher);
registerStreamProtocol();
// Configure the renderer's environment.
const webContents = mainWindow.webContents;
setDownloadPath(webContents);
allowExternalLinks(webContents);
allowAllCORSOrigins(webContents);
// Start loading the renderer.
void mainWindow.loadURL(rendererURL);
// Continue on with the rest of the startup sequence.
Menu.setApplicationMenu(await createApplicationMenu(mainWindow));
setupTrayItem(mainWindow);
setupAutoUpdater(mainWindow);
try {
await deleteLegacyDiskCacheDirIfExists();
await deleteLegacyKeysStoreIfExists();
} catch (e) {
// Log but otherwise ignore errors during non-critical startup
// actions.
log.error("Ignoring startup error", e);
}
})();
});
// This is a macOS only event. Show our window when the user activates the
// app, e.g. by clicking on its dock icon.
app.on("activate", () => mainWindow?.show());
app.on("before-quit", () => {
if (mainWindow) saveWindowBounds(mainWindow);
allowWindowClose();
});
// On macOS, this is how we get deeplinks. See: registerForEnteLinks
app.on("open-url", (_, url) => handleOpenURLEnsuringWindow(url));
};
/**
* Log a standard startup banner.
*
@@ -234,32 +137,6 @@ const registerPrivilegedSchemes = () => {
]);
};
/**
* Register a handler for deeplinks, for the "ente://" protocol.
*
* See: [Note: Passkey verification in the desktop app].
*
* Implementation notes:
* - https://www.electronjs.org/docs/latest/tutorial/launch-app-from-url-in-another-app
* - This works only when the app is packaged.
* - On Windows and Linux, we get the deeplink in the "second-instance" event.
* - On macOS, we get the deeplink in the "open-url" event.
*/
const registerForEnteLinks = () => app.setAsDefaultProtocolClient("ente");
/** Sibling of {@link registerForEnteLinks}. */
const handleEnteLinks = (mainWindow: BrowserWindow, url: string) => {
// [Note: Using deeplinks to navigate in desktop app]
//
// Both
//
// - our deeplink protocol, and
// - the protocol we're using to serve/ our bundled web app
//
// use the same scheme ("ente://"), so the URL can directly be forwarded.
mainWindow.webContents.send("openURL", url);
};
/**
* Create an return the {@link BrowserWindow} that will form our app's UI.
*
@@ -563,5 +440,79 @@ const deleteLegacyKeysStoreIfExists = async () => {
}
};
// Go for it.
const main = () => {
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
return;
}
let mainWindow: BrowserWindow | undefined;
initLogging();
logStartupBanner();
// The order of the next two calls is important
setupRendererServer();
registerPrivilegedSchemes();
migrateLegacyWatchStoreIfNeeded();
app.on("second-instance", () => {
// Someone tried to run a second instance, we should focus our window.
if (mainWindow) {
mainWindow.show();
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
}
});
// Emitted once, when Electron has finished initializing.
//
// Note that some Electron APIs can only be used after this event occurs.
void app.whenReady().then(() => {
void (async () => {
// Create window and prepare for the renderer.
mainWindow = createMainWindow();
// Setup IPC and streams.
const watcher = createWatcher(mainWindow);
attachIPCHandlers();
attachFSWatchIPCHandlers(watcher);
attachLogoutIPCHandler(watcher);
registerStreamProtocol();
// Configure the renderer's environment.
const webContents = mainWindow.webContents;
setDownloadPath(webContents);
allowExternalLinks(webContents);
allowAllCORSOrigins(webContents);
// Start loading the renderer.
void mainWindow.loadURL(rendererURL);
// Continue on with the rest of the startup sequence.
Menu.setApplicationMenu(await createApplicationMenu(mainWindow));
setupTrayItem(mainWindow);
setupAutoUpdater(mainWindow);
try {
await deleteLegacyDiskCacheDirIfExists();
await deleteLegacyKeysStoreIfExists();
} catch (e) {
// Log but otherwise ignore errors during non-critical startup
// actions.
log.error("Ignoring startup error", e);
}
})();
});
// This is a macOS only event. Show our window when the user activates the
// app, e.g. by clicking on its dock icon.
app.on("activate", () => mainWindow?.show());
app.on("before-quit", () => {
if (mainWindow) saveWindowBounds(mainWindow);
allowWindowClose();
});
};
main();

View File

@@ -46,12 +46,7 @@ import {
computeCLIPTextEmbeddingIfAvailable,
} from "./services/ml-clip";
import { computeFaceEmbeddings, detectFaces } from "./services/ml-face";
import {
encryptionKey,
lastShownChangelogVersion,
saveEncryptionKey,
setLastShownChangelogVersion,
} from "./services/store";
import { encryptionKey, saveEncryptionKey } from "./services/store";
import {
clearPendingUploads,
listZipItems,
@@ -106,19 +101,11 @@ export const attachIPCHandlers = () => {
ipcMain.handle("selectDirectory", () => selectDirectory());
ipcMain.handle("encryptionKey", () => encryptionKey());
ipcMain.handle("saveEncryptionKey", (_, encryptionKey: string) =>
saveEncryptionKey(encryptionKey),
);
ipcMain.handle("lastShownChangelogVersion", () =>
lastShownChangelogVersion(),
);
ipcMain.handle("setLastShownChangelogVersion", (_, version: number) =>
setLastShownChangelogVersion(version),
);
ipcMain.handle("encryptionKey", () => encryptionKey());
// - App update

View File

@@ -1,16 +1,12 @@
import { safeStorage } from "electron/main";
import { safeStorageStore } from "../stores/safe-storage";
import { uploadStatusStore } from "../stores/upload-status";
import { userPreferences } from "../stores/user-preferences";
import { watchStore } from "../stores/watch";
/**
* Clear all stores except user preferences.
*
* This function is useful to reset state when the user logs out. User
* preferences are preserved since they contain things tied to the person using
* the app or other machine specific state not tied to the account they were
* using inside the app.
* This is useful to reset state when the user logs out.
*/
export const clearStores = () => {
safeStorageStore.clear();
@@ -36,9 +32,3 @@ export const encryptionKey = (): string | undefined => {
const keyBuffer = Buffer.from(b64EncryptedKey, "base64");
return safeStorage.decryptString(keyBuffer);
};
export const lastShownChangelogVersion = (): number | undefined =>
userPreferences.get("lastShownChangelogVersion");
export const setLastShownChangelogVersion = (version: number) =>
userPreferences.set("lastShownChangelogVersion", version);

View File

@@ -23,12 +23,6 @@ export const createWatcher = (mainWindow: BrowserWindow) => {
const folderPaths = folderWatches().map((watch) => watch.folderPath);
const watcher = chokidar.watch(folderPaths, {
// Don't emit "add" events for matching paths when instantiating the
// watch (we do a full disk scan on launch on our own, and also getting
// the same events from the watcher causes duplicates).
ignoreInitial: true,
// Ask the watcher to wait for a the file size to stabilize before
// telling us about a new file. By default, it waits for 2 seconds.
awaitWriteFinish: true,
});

View File

@@ -9,12 +9,6 @@ interface UserPreferences {
hideDockIcon?: boolean;
skipAppVersion?: string;
muteUpdateNotificationVersion?: string;
/**
* The changelog version for which we last showed the "What's new" screen.
*
* See: [Note: Conditions for showing "What's new"]
*/
lastShownChangelogVersion?: number;
/**
* The last position and size of our app's window.
*
@@ -39,7 +33,6 @@ const userPreferencesSchema: Schema<UserPreferences> = {
hideDockIcon: { type: "boolean" },
skipAppVersion: { type: "string" },
muteUpdateNotificationVersion: { type: "string" },
lastShownChangelogVersion: { type: "number" },
windowBounds: {
properties: {
x: { type: "number" },

View File

@@ -72,26 +72,15 @@ const encryptionKey = () => ipcRenderer.invoke("encryptionKey");
const saveEncryptionKey = (encryptionKey: string) =>
ipcRenderer.invoke("saveEncryptionKey", encryptionKey);
const lastShownChangelogVersion = () =>
ipcRenderer.invoke("lastShownChangelogVersion");
const setLastShownChangelogVersion = (version: number) =>
ipcRenderer.invoke("setLastShownChangelogVersion", version);
const onMainWindowFocus = (cb: (() => void) | undefined) => {
const onMainWindowFocus = (cb?: () => void) => {
ipcRenderer.removeAllListeners("mainWindowFocus");
if (cb) ipcRenderer.on("mainWindowFocus", cb);
};
const onOpenURL = (cb: ((url: string) => void) | undefined) => {
ipcRenderer.removeAllListeners("openURL");
if (cb) ipcRenderer.on("openURL", (_, url: string) => cb(url));
};
// - App update
const onAppUpdateAvailable = (
cb: ((update: AppUpdate) => void) | undefined,
cb?: ((update: AppUpdate) => void) | undefined,
) => {
ipcRenderer.removeAllListeners("appUpdateAvailable");
if (cb) {
@@ -317,10 +306,7 @@ contextBridge.exposeInMainWorld("electron", {
logout,
encryptionKey,
saveEncryptionKey,
lastShownChangelogVersion,
setLastShownChangelogVersion,
onMainWindowFocus,
onOpenURL,
// - App update

View File

@@ -44,8 +44,7 @@ yarn dev
For an editor, VSCode is a good choice. Also install the Prettier extension for
VSCode, and set VSCode to format on save. This way the editor will automatically
format and wrap the text using the project's standard, so you can just focus on
the content. You can also format without VSCode by using the `yarn pretty`
command.
the content.
## Have fun!

View File

@@ -44,10 +44,6 @@ export const sidebar = [
link: "/photos/features/location-tags",
},
{ text: "Map", link: "/photos/features/map" },
{
text: "Passkeys",
link: "/photos/features/passkeys",
},
{
text: "Public link",
link: "/photos/features/public-link",
@@ -135,10 +131,6 @@ export const sidebar = [
text: "Files not uploading",
link: "/photos/troubleshooting/files-not-uploading",
},
{
text: "Missing thumbnails",
link: "/photos/troubleshooting/thumbnails",
},
{
text: "Sharing debug logs",
link: "/photos/troubleshooting/sharing-logs",

View File

@@ -97,6 +97,3 @@ your own instead of contacting support to ask them to delete your account.
Note that both Ente photos and Ente auth data will be deleted when you delete
your account (irrespective of which app you delete it from) since both photos
and auth use the same underlying account.
To know details of how your data is deleted, including when you delete your
account, please see https://ente.io/blog/how-ente-deletes-data/.

View File

@@ -1,61 +0,0 @@
---
title: Passkeys
description: Using passkeys as a second factor for your Ente account
---
# Passkeys
Passkeys are a new authentication mechanism that uses strong cryptography built
into devices, like Windows Hello or Apple's Touch ID. **You can use passkeys as
a second factor to secure your Ente account.**
> [!TIP]
>
> Passkeys are the colloquial term for a WebAuthn (Web Authentication)
> credentials.
>
> - More details about why and how are in the Passkeys announcement
> [blog post](https://ente.io/blog/introducing-passkeys-on-ente/).
> - And to know more technical details about how our passkey verification
> works, you can see this
> [technical note in our source code](https://github.com/ente-io/ente/blob/main/web/docs/webauthn-passkeys.md).
## Passkeys and TOTP
Ente already supports TOTP codes (in fact, we built an
[entire app](https://ente.io/auth/) to store them...). Passkeys serve as an
alternative 2FA (second factor) mechanism.
If you add a passkey to your Ente account, it will be used instead of any
existing 2FA codes that you have configured (if any).
## Enabling and disabling passkeys
Passkeys get enabled if you add one (or more) passkeys to your account.
Conversely, passkeys get disabled if you remove all your existing passkeys.
To add and remove passkeys, use the _Passkey_ option in the settings menu. This
will open up _accounts.ente.io_, where you can manage your passkeys.
## Login with passkeys
If passkeys are enabled, then _accounts.ente.io_ will automatically open when
you log into your Ente account on a new device. Here you can follow the
instructions given by the browser to verify your passkey.
> These instructions different for each browser and device, but generally they
> will ask you to use the same mechanism that you used when you created the
> passkey to verify it (scanning a QR code, using your fingerprint, pressing the
> key on your Yubikey or other security key hardware etc).
## Recovery
If you are unable to login with your passkey (e.g. if you have misplaced the
hardware key that you used to store your passkey), then you can **recover your
account by using your Ente recovery key**.
During login, press cancel on the browser dialog to verify your passkey, and
then select the "Recover two-factor" option in the error message that gets
shown. This will take you to a place where you can enter your Ente recovery key
and login into your account. Now you can go to the _Passkey_ page to delete the
lost passkey and/or add a new one.

View File

@@ -9,9 +9,6 @@ The latest version of the Ente Photos desktop app can be downloaded from
[ente.io/download](https://ente.io/download). If you're having trouble, please
see if any of the following cases apply.
- [Windows](#windows)
- [Linux](#linux)
## Windows
If the app stops with an "A JavaScript error occurred in the main process - The
@@ -20,35 +17,12 @@ start it, then you might need to install the VC++ runtime from Microsoft.
This is what the error looks like:
<div style="border: 1px solid black">
![Error when VC++ runtime is not installed](windows-vc.png){width=500px}
</div>
You can install the Microsoft VC++ redistributable runtime from here:<br/>
https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170#latest-microsoft-visual-c-redistributable-version
## Linux
### AppImage desktop integration
AppImages are not fully standalone, and they require additional steps to enable
full "desktop integration":
- Showing the app icon,
- Surfacing the app in the list of installed apps,
- Handling redirection after passkey verification.
All the ways of enabling AppImage desktop integration are mentioned in
[AppImage documentation](https://docs.appimage.org/user-guide/run-appimages.html#integrating-appimages-into-the-desktop).
For example, you can download the
[appimaged](https://github.com/probonopd/go-appimage/releases) AppImage, run it,
and then download the Ente Photos AppImage into your `~/Downloads` folder.
_appimaged_ will then pick it up automatically.
### AppImages on ARM64
## AppImages on ARM64 Linux
If you're on an ARM64 machine running Linux, and the AppImages doesn't do
anything when you run it, you will need to run the following command on your
@@ -68,7 +42,7 @@ details, see the following upstream issues:
- libz.so: cannot open shared object file with Ubuntu arm64 -
[electron-userland/electron-builder/issues/7835](https://github.com/electron-userland/electron-builder/issues/7835)
### AppImage says it requires FUSE
## AppImage says it requires FUSE
See
[docs.appimage.org](https://docs.appimage.org/user-guide/troubleshooting/fuse.html#the-appimage-tells-me-it-needs-fuse-to-run).
@@ -79,7 +53,7 @@ tl;dr; for example, on Ubuntu,
sudo apt install libfuse2
```
### Linux SUID error
## Linux SUID error
On some Linux distributions, if you run the AppImage from the CLI, it might fail
with the following error:

View File

@@ -8,18 +8,10 @@ description:
## Network Issue
If you are using VPN, please try disabling the VPN or switching your provider.
If you are using VPN, please try disabling the VPN or switching provider.
## Web / Desktop
### Disable "Faster uploads"
We use a Cloudflare proxy to speed up uploads
([blog post](https://ente.io/blog/tech/making-uploads-faster/)). However, in
some network configurations (depending on the ISP) this might prevent uploads
from going through, so if you're having trouble with uploads please try after
disabling the "Faster uploads" setting in _Preferences > Advanced_.
### Certain file types are not uploading
The desktop/web app tries to detect if a particular file is video or image. If

View File

@@ -1,16 +0,0 @@
---
title: Missing thumbnails
description:
Troubleshooting when thumbnails are not being generated when uploading
images in Ente Photos
---
# Missing thumbnails
## Black thumbnails
Users have reported an issue with Firefox which prevents the app from generating
thumbnails if the "block canvas fingerprinting" setting in Firefox is enabled
(i.e. `privacy.resistFingerprinting` is set to true in `about:config`). That
feature blocks access to the canvas, and the app needs the canvas to generate
thumbnails.

View File

@@ -11,5 +11,5 @@
"prettier": "^3",
"vitepress": "^1.0.0-rc.45"
},
"packageManager": "yarn@1.22.22"
"packageManager": "yarn@1.22.21"
}

5623
infra/staff/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -11,11 +11,23 @@
"preview": "vite preview"
},
"dependencies": {
"@date-io/date-fns": "^3.0.0",
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
"@mui/icons-material": "^5.16.0",
"@mui/lab": "^5.0.0-alpha.171",
"@mui/material": "^5.16.0",
"@mui/x-date-pickers": "^7.9.0",
"@types/react-datepicker": "^6.2.0",
"date-fns": "^3.6.0",
"react": "^18",
"react-datepicker": "^7.3.0",
"react-dom": "^18",
"react-toastify": "^10.0.5",
"zod": "^3"
},
"devDependencies": {
"@rollup/plugin-node-resolve": "^15.2.3",
"@types/react": "^18",
"@types/react-dom": "^18",
"@typescript-eslint/eslint-plugin": "^7",
@@ -31,5 +43,5 @@
"typescript": "^5",
"vite": "^5.2"
},
"packageManager": "yarn@1.22.22"
"packageManager": "yarn@1.22.21"
}

306
infra/staff/src/App.css Normal file
View File

@@ -0,0 +1,306 @@
.container {
position: relative; /* Ensure the parent is relatively positioned */
height: 100vh; /* Full viewport height */
width: 100vw; /* Full viewport width */
display: flex;
justify-content: center;
align-items: center;
}
.center-table {
display: table;
}
.input-form {
display: flex;
flex-direction: column;
align-items: center;
}
.horizontal-group {
position: absolute; /* Use absolute positioning */
top: 32px; /* 32px below the top of the page */
left: 32px; /* 32px from the leftmost edge of the page */
right: 32px;
display: flex;
align-items: center; /* Align items vertically centered */
gap: 20px; /* Adjust the gap between elements as needed */
background-color: #fafafa;
height: 104px;
width: 1375px;
border-radius: 10px;
}
.fetch-button-container button {
background-color: #00b33c;
color: white;
border: none;
width: 199px;
height: 56px;
margin-left: 20px;
}
.fetch-button-container button:hover {
background-color: #007c6c;
}
.link-text {
display: flex;
align-items: center;
padding: 0 16px; /* Add padding for better appearance */
text-decoration: none; /* Remove underline */
color: inherit; /* Inherit color from parent */
font-weight: bold; /* Make the text bold */
font-size: 40px;
}
.text-field-token {
margin-left: 70px !important; /* Adjust margin for Token field */
}
.text-field-email {
margin-left: 20px !important; /* Adjust margin for Email field */
}
.duckie-container {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.center-content {
display: flex;
justify-content: center;
align-items: center;
height: 100vh; /* Adjust as necessary */
position: relative;
}
.error-message {
color: red;
font-size: 1.5em;
}
.duckie-container {
display: flex;
justify-content: center;
align-items: center;
}
.container {
position: relative; /* Ensure the parent is relatively positioned */
height: 100vh; /* Full viewport height */
width: 100vw; /* Full viewport width */
display: flex;
flex-direction: column; /* Add this */
justify-content: center; /* Center vertically */
align-items: center; /* Center horizontally */
}
.input-form {
display: flex;
flex-direction: column;
align-items: center;
z-index: 1; /* Ensure form is on top */
}
.horizontal-group {
display: flex;
align-items: center;
gap: 20px; /* Adjust the gap between elements as needed */
background-color: #fafafa;
padding: 20px; /* Add padding around content */
border-radius: 10px;
}
.fetch-button-container button {
background-color: #00b33c;
color: white;
border: none;
width: 199px;
height: 56px;
}
.fetch-button-container button:hover {
background-color: #007c6c;
}
.link-text {
display: flex;
align-items: center;
padding: 0 16px; /* Add padding for better appearance */
text-decoration: none; /* Remove underline */
color: inherit; /* Inherit color from parent */
font-weight: bold; /* Make the text bold */
font-size: 40px;
}
.text-field-token {
margin-left: 70px !important; /* Adjust margin for Token field */
}
.text-field-email {
margin-left: 50px !important; /* Adjust margin for Email field */
}
.center-content {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.duckie-container {
display: flex;
justify-content: center;
align-items: center;
}
/* Example CSS to maintain tabs fixed position */
.tabs-container {
position: sticky;
top: 20px; /* Adjust as needed */
z-index: 1000; /* Ensure tabs are above other content */
}
.container {
position: relative; /* Ensure the parent is relatively positioned */
height: 100vh; /* Full viewport height */
width: 100vw; /* Full viewport width */
display: flex;
flex-direction: column; /* Ensure children stack vertically */
justify-content: center; /* Center vertically */
align-items: center; /* Center horizontally */
}
.input-form {
display: flex;
flex-direction: column;
align-items: center;
z-index: 1; /* Ensure form is on top */
margin-bottom: 20px; /* Add space between form and tabs */
}
.horizontal-group {
display: flex;
align-items: center;
gap: 20px; /* Adjust the gap between elements as needed */
background-color: #fafafa;
padding: 20px; /* Add padding around content */
border-radius: 10px;
}
.fetch-button-container button {
background-color: #00b33c;
color: white;
border: none;
width: 199px;
height: 56px;
}
.fetch-button-container button:hover {
background-color: #007c6c;
}
.link-text {
display: flex;
align-items: center;
padding: 0 16px; /* Add padding for better appearance */
text-decoration: none; /* Remove underline */
color: inherit; /* Inherit color from parent */
font-weight: bold; /* Make the text bold */
font-size: 40px;
}
.text-field-token {
margin-left: 70px !important; /* Adjust margin for Token field */
}
.text-field-email {
margin-left: 50px !important; /* Adjust margin for Email field */
}
.center-content {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.duckie-container {
display: flex;
justify-content: center;
align-items: center;
}
.tabs-container {
position: sticky; /* Make the tabs sticky */
top: 0; /* Stick to the top of the viewport */
z-index: 1000; /* Ensure tabs are above other content */
background-color: #fafafa; /* Optional: Add background color to tabs */
padding: 10px 20px; /* Optional: Add padding for better appearance */
border-bottom: 1px solid #ccc; /* Optional: Add bottom border */
}
.dialog-popup-container {
/* Styles for the overlay/background */
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5); /* Semi-transparent black */
display: flex;
align-items: center;
justify-content: center;
}
.dialog-popup {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
}
.dialog-popup-header h2 {
margin-bottom: 20px; /* Add space between heading and fields */
}
.dialog-popup-field {
margin-bottom: 15px;
}
.dialog-popup-field label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.dialog-popup-field input {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 3px;
}
.submit-button {
background-color: #4caf50; /* Green */
color: white;
padding: 10px 15px;
border: none;
border-radius: 3px;
cursor: pointer;
}
/* Specific styles for each field to match the image */
.name-field input,
.shortname-field input,
.code-field input {
width: calc(100% - 100px); /* Adjust as needed */
}
.active-field input {
width: 50px;
}

View File

@@ -1,224 +1,323 @@
import React, { useEffect, useState } from "react";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import CircularProgress from "@mui/material/CircularProgress";
import Tab from "@mui/material/Tab";
import Tabs from "@mui/material/Tabs";
import TextField from "@mui/material/TextField";
import * as React from "react";
import { useEffect, useState } from "react";
import "./App.css";
import type { UserData } from "./components/UserComponent";
import UserComponent from "./components/UserComponent";
import duckieimage from "./components/duckie.png";
import { apiOrigin } from "./services/support";
import S from "./utils/strings";
export const App: React.FC = () => {
const [token, setToken] = useState("");
const [email, setEmail] = useState("");
const [userData, setUserData] = useState<any>(null);
const [error, setError] = useState<string | null>(null);
// Define and export email and token variables and their setter functions
export let email = "";
export let token = "";
export const setEmail = (newEmail: string) => {
email = newEmail;
};
export const setToken = (newToken: string) => {
token = newToken;
};
export const getEmail = () => email;
export const getToken = () => token;
interface User {
ID: string;
email: string;
creationTime: number;
}
interface Subscription {
productID: string;
paymentProvider: string;
expiryTime: number;
storage: number;
}
interface Security {
isEmailMFAEnabled: boolean;
isTwoFactorEnabled: boolean;
passkeys: string; // Replace with actual passkey value if available
}
interface UserResponse {
user: User;
subscription: Subscription;
details?: {
usage?: number;
storageBonus?: number;
profileData: Security;
};
}
const App: React.FC = () => {
const [localEmail, setLocalEmail] = useState<string>(getEmail());
const [localToken, setLocalToken] = useState<string>(getToken());
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string>("");
const [fetchSuccess, setFetchSuccess] = useState<boolean>(false);
const [tabValue, setTabValue] = useState<number>(0);
const [userData, setUserData] = useState<UserData | null>(null);
useEffect(() => {
const storedToken = localStorage.getItem("token");
if (storedToken) {
setToken(storedToken);
setLocalToken(storedToken);
}
}, []);
useEffect(() => {
if (token) {
localStorage.setItem("token", token);
if (localToken) {
setToken(localToken);
localStorage.setItem("token", localToken);
} else {
localStorage.removeItem("token");
}
}, [token]);
}, [localToken]);
useEffect(() => {
if (localEmail) {
setEmail(localEmail);
localStorage.setItem("email", localEmail);
} else {
localStorage.removeItem("email");
}
}, [localEmail]);
const fetchData = async () => {
setLoading(true);
setError("");
setFetchSuccess(false);
const startTime = Date.now();
try {
const url = `${apiOrigin}/admin/user?email=${email}&token=${token}`;
const encodedEmail = encodeURIComponent(localEmail);
const encodedToken = encodeURIComponent(localToken);
const url = `${apiOrigin}/admin/user?email=${encodedEmail}&token=${encodedToken}`;
console.log(`Fetching data from URL: ${url}`);
const response = await fetch(url);
if (!response.ok) {
throw new Error("Network response was not ok");
}
const userData = await response.json();
console.log("API Response:", userData);
setUserData(userData);
setError(null);
const userDataResponse: UserResponse =
(await response.json()) as UserResponse;
console.log("API Response:", userDataResponse);
const extractedUserData: UserData = {
User: {
"User ID": userDataResponse.user.ID || "None",
Email: userDataResponse.user.email || "None",
"Creation time":
new Date(
userDataResponse.user.creationTime / 1000,
).toLocaleString() || "None",
},
Storage: {
Total: userDataResponse.subscription.storage
? userDataResponse.subscription.storage >= 1024 ** 3
? `${(userDataResponse.subscription.storage / 1024 ** 3).toFixed(2)} GB`
: `${(userDataResponse.subscription.storage / 1024 ** 2).toFixed(2)} MB`
: "None",
Consumed:
userDataResponse.details?.usage !== undefined
? userDataResponse.details.usage >= 1024 ** 3
? `${(userDataResponse.details.usage / 1024 ** 3).toFixed(2)} GB`
: `${(userDataResponse.details.usage / 1024 ** 2).toFixed(2)} MB`
: "None",
Bonus:
userDataResponse.details?.storageBonus !== undefined
? userDataResponse.details.storageBonus >= 1024 ** 3
? `${(userDataResponse.details.storageBonus / 1024 ** 3).toFixed(2)} GB`
: `${(userDataResponse.details.storageBonus / 1024 ** 2).toFixed(2)} MB`
: "None",
},
Subscription: {
"Product ID":
userDataResponse.subscription.productID || "None",
Provider:
userDataResponse.subscription.paymentProvider || "None",
"Expiry time":
new Date(
userDataResponse.subscription.expiryTime / 1000,
).toLocaleString() || "None",
},
Security: {
"Email MFA": userDataResponse.details?.profileData
.isEmailMFAEnabled
? "Enabled"
: "Disabled",
"Two factor 2FA": userDataResponse.details?.profileData
.isTwoFactorEnabled
? "Enabled"
: "Disabled",
Passkeys: "None", // Replace with actual passkey value if available
},
};
const elapsedTime = Date.now() - startTime;
const delay = Math.max(3000 - elapsedTime, 0);
setTimeout(() => {
setLoading(false);
setFetchSuccess(true);
setUserData(extractedUserData);
}, delay);
} catch (error) {
console.error("Error fetching data:", error);
setError((error as Error).message);
const elapsedTime = Date.now() - startTime;
const delay = Math.max(3000 - elapsedTime, 0);
setTimeout(() => {
setLoading(false);
setError("Invalid token or email id");
}, delay);
}
};
const renderAttributes = (data: any) => {
if (!data) return null;
const handleKeyPress = (event: React.KeyboardEvent<HTMLFormElement>) => {
if (event.key === "Enter") {
event.preventDefault();
fetchData().catch((error: unknown) =>
console.error("Fetch data error:", error),
);
}
};
let nullAttributes: string[] = [];
const rows = Object.entries(data).map(([key, value]) => {
console.log("Processing key:", key, "value:", value);
if (
typeof value === "object" &&
value !== null &&
!Array.isArray(value)
) {
return (
<React.Fragment key={key}>
<tr>
<td
colSpan={2}
style={{
fontWeight: "bold",
backgroundColor: "#f1f1f1",
padding: "10px",
}}
>
{key.toUpperCase()}
</td>
</tr>
{renderAttributes(value)}
</React.Fragment>
);
} else {
if (value === null) {
nullAttributes.push(key);
}
let displayValue: React.ReactNode;
if (key === "expiryTime" && typeof value === "number") {
displayValue = new Date(value / 1000).toLocaleString();
} else if (
key === "creationTime" &&
typeof value === "number"
) {
displayValue = new Date(value / 1000).toLocaleString();
} else if (key === "storage" && typeof value === "number") {
displayValue = `${(value / 1024 ** 3).toFixed(2)} GB`;
} else if (typeof value === "string") {
try {
const parsedValue = JSON.parse(value);
displayValue = parsedValue;
} catch (error) {
displayValue = value;
}
} else if (value === null) {
displayValue = "null";
} else if (typeof value !== "undefined") {
displayValue = value.toString();
} else {
displayValue = "undefined";
}
return (
<tr key={key}>
<td
style={{
padding: "10px",
border: "1px solid #ddd",
}}
>
{key}
</td>
<td
style={{
padding: "10px",
border: "1px solid #ddd",
}}
>
{displayValue}
</td>
</tr>
);
}
});
console.log("Attributes with null values:", nullAttributes);
return rows;
const handleTabChange = (
_event: React.SyntheticEvent,
newValue: number,
) => {
setTabValue(newValue);
};
return (
<div className="container center-table">
<h1>{S.hello}</h1>
<form className="input-form" onKeyPress={handleKeyPress}>
<div className="horizontal-group">
<a
href="https://staff.ente.sh"
target="_blank"
rel="noopener noreferrer"
className="link-text"
>
staff.ente.sh
</a>
<form className="input-form">
<div className="input-group">
<label>
Token:
<input
type="text"
value={token}
onChange={(e) => setToken(e.target.value)}
style={{
padding: "10px",
margin: "10px",
width: "100%",
<TextField
label="Token"
value={localToken}
onChange={(e) => {
setLocalToken(e.target.value);
setToken(e.target.value);
}}
size="medium"
className="text-field-token"
style={{ width: "350px" }}
/>
<TextField
label="Email"
value={localEmail}
onChange={(e) => {
setLocalEmail(e.target.value);
setEmail(e.target.value);
}}
size="medium"
className="text-field-email"
style={{ width: "350px" }}
/>
<div className="fetch-button-container">
<Button
variant="contained"
onClick={() => {
fetchData().catch((error: unknown) =>
console.error("Fetch data error:", error),
);
}}
/>
</label>
</div>
<div className="input-group">
<label>
Email id:
<input
type="text"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="fetch-button"
style={{
padding: "10px",
margin: "10px",
width: "100%",
padding: "0 16px",
}}
/>
</label>
>
FETCH
</Button>
</div>
</div>
</form>
<div className="fetch-button">
<button
onClick={fetchData}
style={{
padding: "10px 20px",
fontSize: "16px",
cursor: "pointer",
backgroundColor: "#009879",
color: "white",
border: "none",
borderRadius: "5px",
}}
>
FETCH
</button>
<div className="content-container">
{loading ? (
<CircularProgress sx={{ color: "black" }} />
) : error ? (
<div className="error-message">{error}</div>
) : fetchSuccess ? (
<>
<Box
sx={{
width: "100%",
maxWidth: "600px",
bgcolor: "#FAFAFA",
marginTop: "300px",
borderRadius: "7px",
position: "relative",
zIndex: 1000,
}}
>
<Tabs
value={tabValue}
onChange={handleTabChange}
centered
sx={{
"& .MuiTabs-indicator": {
backgroundColor: "#00B33C",
height: "5px",
borderRadius: "20px",
},
"& .MuiTab-root": {
textTransform: "none",
},
"& .Mui-selected": {
color: "black !important",
},
"& .MuiTab-root.Mui-selected": {
color: "black !important",
},
}}
>
<Tab label="User" />
<Tab label="Family" />
<Tab label="Bonuses" />
</Tabs>
</Box>
<Box
sx={{
width: "100%",
maxWidth: "600px",
mt: 4,
minHeight: "400px",
}}
>
{tabValue === 0 && (
<UserComponent userData={userData} />
)}
{tabValue === 1 && <div>Family tab content</div>}
{tabValue === 2 && <div>Bonuses tab content</div>}
</Box>
</>
) : (
<div className="duckie-container">
<img
src={duckieimage}
alt="Duckie"
className="duckie-image"
/>
</div>
)}
</div>
<br />
{error && <p style={{ color: "red" }}>{`Error: ${error}`}</p>}
{userData && (
<table
style={{
width: "100%",
borderCollapse: "collapse",
margin: "20px 0",
fontSize: "1em",
minWidth: "400px",
boxShadow: "0 0 20px rgba(0, 0, 0, 0.15)",
}}
>
<tbody>
{Object.keys(userData).map((category) => (
<React.Fragment key={category}>
<tr>
<td
colSpan={2}
style={{
fontWeight: "bold",
backgroundColor: "#f1f1f1",
padding: "10px",
}}
>
{category.toUpperCase()}
</td>
</tr>
{renderAttributes(userData[category])}
</React.Fragment>
))}
</tbody>
</table>
)}
<footer className="footer">
<p>
<a href="https://help.ente.io">help.ente.io</a>
</p>
</footer>
</div>
);
};
export default App;

View File

@@ -0,0 +1,192 @@
import CloseIcon from "@mui/icons-material/Close";
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
TextField,
} from "@mui/material";
import React, { useEffect, useState } from "react";
import { getEmail, getToken } from "../App";
import { apiOrigin } from "../services/support";
interface ErrorResponse {
message: string;
}
interface ChangeEmailProps {
open: boolean;
onClose: () => void;
}
interface UserDataResponse {
subscription: {
userID: string;
} | null;
}
const ChangeEmail: React.FC<ChangeEmailProps> = ({ open, onClose }) => {
const [newEmail, setNewEmail] = useState<string>("");
const [userID, setUserID] = useState<string>("");
useEffect(() => {
const fetchUserID = async () => {
const token = getToken();
const email = getEmail();
setNewEmail(email); // Set initial email state
const encodedEmail = encodeURIComponent(email);
const encodedToken = encodeURIComponent(token);
const url = `${apiOrigin}/admin/user?email=${encodedEmail}&token=${encodedToken}`;
try {
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
"X-AUTH-TOKEN": token,
},
});
if (!response.ok) {
throw new Error("Network response was not ok");
}
const data = (await response.json()) as UserDataResponse;
if (data.subscription) {
setUserID(data.subscription.userID); // Update userID state
} else {
throw new Error("Subscription data not found");
}
} catch (error) {
console.error("Error fetching user ID:", error);
}
};
if (open) {
fetchUserID().catch((error: unknown) =>
console.error("Error in fetchUserID:", error),
);
}
}, [open]);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setNewEmail(event.target.value); // Update newEmail state on input change
};
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const token = getToken();
const url = `${apiOrigin}/admin/user/change-email?token=${token}`;
const body = {
userID,
email: newEmail,
};
try {
const response = await fetch(url, {
method: "PUT",
headers: {
"Content-Type": "application/json",
"X-AUTH-TOKEN": token,
},
body: JSON.stringify(body),
});
if (!response.ok) {
let errorData;
try {
errorData = (await response.json()) as ErrorResponse;
} catch (error) {
console.error("Error parsing error response:", error);
}
throw new Error(
errorData?.message ?? "Network response was not ok",
);
}
console.log("Email updated successfully");
onClose();
} catch (error) {
console.error("Error updating email:", error);
}
};
const handleSubmitSync: React.FormEventHandler<HTMLFormElement> = (
event,
) => {
handleSubmit(event).catch((error: unknown) => {
console.error("Error in handleSubmit:", error);
});
};
return (
<Dialog
open={open}
onClose={onClose}
BackdropProps={{
style: {
backdropFilter: "blur(5px)",
backgroundColor: "rgba(255, 255, 255, 0.8)",
},
}}
PaperProps={{
style: {
width: "444px",
height: "300px",
},
}}
>
<DialogTitle style={{ marginBottom: "20px", marginTop: "20px" }}>
Change Email
<Button
onClick={onClose}
style={{ position: "absolute", right: 10, top: 10 }}
>
<CloseIcon style={{ color: "black" }} />
</Button>
</DialogTitle>
<DialogContent>
<form onSubmit={handleSubmitSync}>
<div style={{ marginBottom: "16px" }}>
<label
htmlFor="newEmail"
style={{
textAlign: "left",
display: "block",
marginBottom: "4px",
}}
>
Email
</label>
<TextField
id="newEmail"
name="newEmail"
value={newEmail}
onChange={handleChange}
fullWidth
/>
</div>
<DialogActions
style={{ justifyContent: "center", marginTop: "40px" }}
>
<Button
type="submit"
variant="contained"
style={{
backgroundColor: "#00B33C",
color: "white",
}}
>
Change Email
</Button>
</DialogActions>
</form>
</DialogContent>
</Dialog>
);
};
export default ChangeEmail;

View File

@@ -0,0 +1,100 @@
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
Paper,
} from "@mui/material";
import React from "react";
import { getEmail, getToken } from "../App"; // Import getEmail and getToken functions
import { apiOrigin } from "../services/support";
interface DeleteAccountProps {
open: boolean;
handleClose: () => void;
}
const DeleteAccount: React.FC<DeleteAccountProps> = ({ open, handleClose }) => {
const handleDelete = async () => {
try {
const encodedEmail = encodeURIComponent(getEmail());
console.log(encodedEmail);
const encodedToken = encodeURIComponent(getToken());
console.log(encodedToken);
const deleteUrl = `${apiOrigin}/admin/user/delete?email=${encodedEmail}&token=${encodedToken}`;
const response = await fetch(deleteUrl, { method: "DELETE" });
if (!response.ok) {
throw new Error("Failed to delete user account");
}
handleClose(); // Close dialog on successful delete
console.log("Account deleted successfully");
} catch (error) {
console.error("Error deleting user account:", error);
}
};
return (
<div>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
PaperComponent={Paper}
sx={{
width: "499px",
height: "286px",
margin: "auto",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
}}
BackdropProps={{
style: {
backgroundColor: "rgba(255, 255, 255, 0.9)", // Semi-transparent backdrop
},
}}
>
<DialogTitle id="alert-dialog-title">
{"Delete Account?"}
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
Are you sure you want to delete the account?
</DialogContentText>
</DialogContent>
<DialogActions sx={{ justifyContent: "center" }}>
<Button
onClick={handleClose}
sx={{
bgcolor: "white",
color: "black",
"&:hover": { bgcolor: "#FAFAFA" },
}}
>
Cancel
</Button>
<Button
onClick={() => {
handleDelete().catch((error: unknown) =>
console.error("Fetch data error:", error),
);
}}
sx={{
bgcolor: "#F4473D",
color: "white",
"&:hover": { bgcolor: "#E53935" },
}}
>
Delete{" "}
</Button>
</DialogActions>
</Dialog>
</div>
);
};
export default DeleteAccount;

View File

@@ -0,0 +1,156 @@
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
Paper,
} from "@mui/material";
import React, { useState } from "react";
import { getEmail, getToken } from "../App"; // Import getEmail and getToken functions
import { apiOrigin } from "../services/support";
interface UserData {
subscription?: {
userID: string;
// Add other properties as per your API response structure
};
// Add other properties as per your API response structure
}
interface Disable2FAProps {
open: boolean;
handleClose: () => void;
handleDisable2FA: () => void; // Callback to handle 2FA disablement
}
const Disable2FA: React.FC<Disable2FAProps> = ({
open,
handleClose,
handleDisable2FA,
}) => {
const [loading, setLoading] = useState(false);
const handleDisable = async () => {
try {
setLoading(true);
const email = getEmail();
const token = getToken();
if (!email) {
throw new Error("Email not found");
}
if (!token) {
throw new Error("Token not found");
}
const encodedEmail = encodeURIComponent(email);
const encodedToken = encodeURIComponent(token);
// Fetch user data
const userUrl = `${apiOrigin}/admin/user?email=${encodedEmail}&token=${encodedToken}`;
const userResponse = await fetch(userUrl);
if (!userResponse.ok) {
throw new Error("Failed to fetch user data");
}
const userData = (await userResponse.json()) as UserData;
const userId = userData.subscription?.userID;
if (!userId) {
throw new Error("User ID not found");
}
// Disable 2FA
const disableUrl = `${apiOrigin}/admin/user/disable-2fa?token=${encodedToken}`;
const body = JSON.stringify({ userId });
const disableResponse = await fetch(disableUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: body,
});
if (!disableResponse.ok) {
const errorResponse = await disableResponse.text();
throw new Error(`Failed to disable 2FA: ${errorResponse}`);
}
handleDisable2FA(); // Notify parent component of successful disable
handleClose(); // Close dialog on successful disable
console.log("2FA disabled successfully");
} catch (error) {
console.error("Error disabling 2FA:", error);
} finally {
setLoading(false);
}
};
const handleCancel = () => {
handleClose(); // Close dialog
};
return (
<div>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
PaperComponent={Paper}
sx={{
width: "499px",
height: "286px",
margin: "auto",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
}}
BackdropProps={{
style: {
backgroundColor: "rgba(255, 255, 255, 0.9)", // Semi-transparent backdrop
},
}}
>
<DialogTitle id="alert-dialog-title">
{"Disable 2FA?"}
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
Are you sure you want to disable 2FA for this account?
</DialogContentText>
</DialogContent>
<DialogActions sx={{ justifyContent: "center" }}>
<Button
onClick={handleCancel}
sx={{
bgcolor: "white",
color: "black",
"&:hover": { bgcolor: "#FAFAFA" },
}}
>
Cancel
</Button>
<Button
onClick={() => {
handleDisable().catch((error: unknown) =>
console.error(error),
);
}}
sx={{
bgcolor: "#F4473D",
color: "white",
"&:hover": { bgcolor: "#E53935" },
}}
disabled={loading}
>
{loading ? "Disabling..." : "Disable"}
</Button>
</DialogActions>
</Dialog>
</div>
);
};
export default Disable2FA;

View File

@@ -0,0 +1,367 @@
import CalendarTodayIcon from "@mui/icons-material/CalendarToday";
import CloseIcon from "@mui/icons-material/Close";
import Button from "@mui/material/Button";
import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import DialogTitle from "@mui/material/DialogTitle";
import Grid from "@mui/material/Grid";
import InputAdornment from "@mui/material/InputAdornment";
import MenuItem from "@mui/material/MenuItem";
import Select, { type SelectChangeEvent } from "@mui/material/Select";
import TextField from "@mui/material/TextField";
import React, { useEffect, useState } from "react";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import { getEmail, getToken } from "../App";
import { apiOrigin } from "../services/support";
interface Subscription {
productID: string;
paymentProvider: string;
storage: number;
originalTransactionID: string;
expiryTime: number;
userID: string;
}
interface UserDataResponse {
subscription: Subscription | null;
}
interface UpdateSubscriptionProps {
open: boolean;
onClose: () => void;
}
interface FormValues {
productId: string;
provider: string;
storage: number;
transactionId: string;
expiryTime: string | Date | null;
userId: string;
}
const UpdateSubscription: React.FC<UpdateSubscriptionProps> = ({
open,
onClose,
}) => {
const [values, setValues] = useState<FormValues>({
productId: "",
provider: "",
storage: 0,
transactionId: "",
expiryTime: "",
userId: "",
});
const [isDatePickerOpen, setIsDatePickerOpen] = useState(false);
useEffect(() => {
const fetchData = async () => {
try {
const email = getEmail();
const token = getToken();
const encodedEmail = encodeURIComponent(email);
const encodedToken = encodeURIComponent(token);
const url = `${apiOrigin}/admin/user?email=${encodedEmail}&token=${encodedToken}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error("Network response was not ok");
}
const userDataResponse =
(await response.json()) as UserDataResponse;
if (!userDataResponse.subscription) {
throw new Error("Subscription data not found");
}
const expiryTime = new Date(
userDataResponse.subscription.expiryTime / 1000,
);
setValues({
productId: userDataResponse.subscription.productID || "",
provider:
userDataResponse.subscription.paymentProvider || "",
storage:
userDataResponse.subscription.storage /
(1024 * 1024 * 1024) || 0,
transactionId:
userDataResponse.subscription.originalTransactionID ||
"",
expiryTime: expiryTime,
userId: userDataResponse.subscription.userID || "",
});
} catch (error) {
console.error("Error fetching data:", error);
}
};
fetchData().catch((error: unknown) => {
console.error("Unhandled promise rejection:", error);
});
}, []);
const handleCalendarClick = () => {
setIsDatePickerOpen(true);
};
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = event.target;
setValues({
...values,
[name]: name === "storage" ? parseInt(value, 10) : value,
});
};
const handleChangeProvider = (event: SelectChangeEvent) => {
const { name, value } = event.target;
if (name) {
setValues({
...values,
[name]: value,
});
}
};
const handleDatePickerChange = (date: Date | null) => {
setValues({
...values,
expiryTime: date,
});
setIsDatePickerOpen(false);
};
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
(async () => {
const token = getToken();
const url = `${apiOrigin}/admin/user/subscription`;
let expiryTime = null;
if (values.expiryTime instanceof Date) {
const utcExpiryTime = new Date(values.expiryTime);
expiryTime = utcExpiryTime.getTime() * 1000;
}
const body = {
userId: values.userId,
storage: values.storage * (1024 * 1024 * 1024),
expiryTime: expiryTime,
productId: values.productId,
paymentProvider: values.provider,
transactionId: values.transactionId,
};
try {
const response = await fetch(url, {
method: "PUT",
headers: {
"Content-Type": "application/json",
"X-AUTH-TOKEN": token,
},
body: JSON.stringify(body),
});
if (!response.ok) {
throw new Error("Network response was not ok");
}
console.log("Subscription updated successfully");
onClose();
} catch (error) {
console.error("Error updating subscription:", error);
}
})().catch((error: unknown) => {
console.error("Unhandled promise rejection:", error);
});
};
return (
<Dialog
open={open}
onClose={onClose}
BackdropProps={{
style: {
backdropFilter: "blur(5px)",
backgroundColor: "rgba(255, 255, 255, 0.8)",
},
}}
>
<DialogTitle style={{ marginBottom: "20px", marginTop: "20px" }}>
Update Subscription
<Button
onClick={onClose}
style={{ position: "absolute", right: 10, top: 10 }}
>
<CloseIcon style={{ color: "black" }} />
</Button>
</DialogTitle>
<DialogContent>
<form onSubmit={handleSubmit}>
<Grid container spacing={4}>
<Grid item xs={6}>
<div style={{ marginBottom: "8px" }}>
<label
htmlFor="productId"
style={{
textAlign: "left",
display: "block",
marginBottom: "4px",
}}
>
Product ID
</label>
<TextField
id="productId"
name="productId"
value={values.productId}
onChange={handleChange}
fullWidth
/>
</div>
</Grid>
<Grid item xs={6}>
<div style={{ marginBottom: "8px" }}>
<label
htmlFor="provider"
style={{
textAlign: "left",
display: "block",
marginBottom: "4px",
}}
>
Provider
</label>
<Select
id="provider"
name="provider"
value={values.provider}
onChange={handleChangeProvider}
fullWidth
style={{ textAlign: "left" }}
>
<MenuItem value="stripe">Stripe</MenuItem>
<MenuItem value="paypal">PayPal</MenuItem>
<MenuItem value="bitpay">BitPay</MenuItem>
</Select>
</div>
</Grid>
<Grid item xs={6}>
<div style={{ marginBottom: "8px" }}>
<label
htmlFor="storage"
style={{
textAlign: "left",
display: "block",
marginBottom: "4px",
}}
>
Storage (GB)
</label>
<TextField
id="storage"
name="storage"
type="number"
value={values.storage}
onChange={handleChange}
fullWidth
/>
</div>
</Grid>
<Grid item xs={6}>
<div style={{ marginBottom: "8px" }}>
<label
htmlFor="transactionId"
style={{
textAlign: "left",
display: "block",
marginBottom: "4px",
}}
>
Transaction ID
</label>
<TextField
id="transactionId"
name="transactionId"
value={values.transactionId}
onChange={handleChange}
fullWidth
/>
</div>
</Grid>
<Grid item xs={6}>
<div style={{ marginBottom: "8px" }}>
<label
htmlFor="expiryTime"
style={{
textAlign: "left",
display: "block",
marginBottom: "4px",
}}
>
Expiry Time
</label>
<TextField
id="expiryTime"
name="expiryTime"
value={
values.expiryTime instanceof Date
? values.expiryTime.toLocaleDateString(
"en-GB",
)
: ""
}
onClick={handleCalendarClick}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<CalendarTodayIcon />
</InputAdornment>
),
readOnly: true,
}}
fullWidth
/>
{isDatePickerOpen && (
<DatePicker
showYearDropdown
scrollableYearDropdown
yearDropdownItemNumber={15}
selected={
values.expiryTime instanceof Date
? values.expiryTime
: null
}
onChange={handleDatePickerChange}
onClickOutside={() =>
setIsDatePickerOpen(false)
}
withPortal
inline
/>
)}
</div>
</Grid>
</Grid>
<DialogActions style={{ justifyContent: "center" }}>
<Button
type="submit"
variant="contained"
style={{
backgroundColor: "#00B33C",
color: "white",
}}
>
Update
</Button>
</DialogActions>
</form>
</DialogContent>
</Dialog>
);
};
export default UpdateSubscription;

View File

@@ -0,0 +1,335 @@
import DeleteIcon from "@mui/icons-material/Delete";
import EditIcon from "@mui/icons-material/Edit";
import Box from "@mui/material/Box";
import Grid from "@mui/material/Grid";
import IconButton from "@mui/material/IconButton";
import Paper from "@mui/material/Paper";
import Switch from "@mui/material/Switch";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableRow from "@mui/material/TableRow";
import Typography from "@mui/material/Typography";
import * as React from "react";
import ChangeEmail from "./ChangeEmail";
import DeleteAccount from "./DeleteAccont";
import Disable2FA from "./Disable2FA";
import UpdateSubscription from "./UpdateSubscription";
export interface UserData {
User: Record<string, string>;
Storage: Record<string, string>;
Subscription: Record<string, string>;
Security: Record<string, string>;
}
interface UserComponentProps {
userData: UserData | null;
}
const UserComponent: React.FC<UserComponentProps> = ({ userData }) => {
const [deleteAccountOpen, setDeleteAccountOpen] = React.useState(false);
const [disable2FAOpen, setDisable2FAOpen] = React.useState(false);
const [twoFactorEnabled, setTwoFactorEnabled] = React.useState(false);
const [is2FADisabled, setIs2FADisabled] = React.useState(false);
const [updateSubscriptionOpen, setUpdateSubscriptionOpen] =
React.useState(false);
const [changeEmailOpen, setChangeEmailOpen] = React.useState(false); // State for ChangeEmail dialog
React.useEffect(() => {
if (userData?.Security["Two factor 2FA"] === "Enabled") {
setTwoFactorEnabled(true);
} else {
setTwoFactorEnabled(false);
}
}, [userData]);
const handleEditEmail = () => {
console.log("Edit Email clicked");
setChangeEmailOpen(true);
};
const handleCloseChangeEmail = () => {
setChangeEmailOpen(false); // Close ChangeEmail dialog
};
const handleDeleteAccountClick = () => {
setDeleteAccountOpen(true);
};
const handleCloseDeleteAccount = () => {
setDeleteAccountOpen(false);
};
const handleOpenDisable2FA = () => {
setDisable2FAOpen(true);
};
const handleCloseDisable2FA = () => {
setDisable2FAOpen(false);
};
const handleDisable2FA = () => {
setIs2FADisabled(true);
};
const handleCancelDisable2FA = () => {
setTwoFactorEnabled(true);
handleCloseDisable2FA();
};
const handleEditSubscription = () => {
setUpdateSubscriptionOpen(true);
};
const handleCloseUpdateSubscription = () => {
setUpdateSubscriptionOpen(false);
};
if (!userData) {
return null;
}
return (
<Grid container spacing={6} justifyContent="center">
{Object.entries(userData).map(([title, data]) => (
<Grid item xs={12} sm={10} md={6} key={title}>
<TableContainer
component={Paper}
variant="outlined"
sx={{
minHeight: 300,
display: "flex",
flexDirection: "column",
marginBottom: "20px",
height: "100%",
width: "100%",
padding: "13px",
"&:not(:last-child)": {
marginBottom: "40px",
},
}}
>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "16px",
width: "100%",
}}
>
<Typography
variant="h6"
component="div"
sx={{
fontWeight: "bold",
textAlign: "center",
width: "100%",
}}
>
{title}
</Typography>
{title === "User" && (
<IconButton
edge="start"
aria-label="delete"
onClick={handleDeleteAccountClick}
>
<DeleteIcon style={{ color: "" }} />
</IconButton>
)}
{title === "Subscription" && (
<IconButton
edge="end"
aria-label="edit"
onClick={handleEditSubscription}
>
<EditIcon style={{ color: "black" }} />
</IconButton>
)}
</Box>
<Table
sx={{
width: "100%",
tableLayout: "fixed",
height: "100%",
borderBottom: "none",
}}
aria-label={title}
>
<TableBody>
{Object.entries(
data as Record<string, string>,
).map(([label, value], index) => (
<TableRow key={label}>
<TableCell
component="th"
scope="row"
style={{
padding: "16px",
borderBottom:
index === 1 || index === 0
? "1px solid rgba(224, 224, 224, 1)"
: "none",
}}
>
{label}
</TableCell>
<TableCell
align="right"
style={{
padding: "10px",
borderBottom:
index === 1 || index === 0
? "1px solid rgba(224, 224, 224, 1)"
: "none",
}}
>
{label === "Email" ? (
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent:
"flex-end",
}}
>
<Typography>
{value}
</Typography>
<IconButton
edge="end"
aria-label="edit-email"
onClick={
handleEditEmail
}
>
<EditIcon
style={{
color: "black",
}}
/>
</IconButton>
</Box>
) : typeof value === "string" ? (
label === "Two factor 2FA" ? (
is2FADisabled ||
value === "Disabled" ? (
<Typography
sx={{
textAlign:
"center",
width: "100%",
paddingLeft:
"30px",
}}
>
{value}
</Typography>
) : (
<Box
sx={{
display: "flex",
alignItems:
"center",
justifyContent:
"center",
width: "100%",
}}
>
<Typography>
{value}
</Typography>
{value ===
"Enabled" && (
<Switch
checked={
twoFactorEnabled
}
onChange={(
e,
) => {
const isChecked =
e
.target
.checked;
setTwoFactorEnabled(
isChecked,
);
if (
!isChecked
) {
handleOpenDisable2FA();
}
}}
sx={{
"& .MuiSwitch-switchBase.Mui-checked":
{
color: "#00B33C",
"&:hover":
{
backgroundColor:
"rgba(0, 179, 60, 0.08)",
},
},
"& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track":
{
backgroundColor:
"#00B33C",
},
}}
/>
)}
</Box>
)
) : (
<Typography>
{value}
</Typography>
)
) : (
<Typography>
{String(value)}
</Typography>
)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Grid>
))}
{/* Render DeleteAccount dialog */}
<DeleteAccount
open={deleteAccountOpen}
handleClose={handleCloseDeleteAccount}
/>
{/* Render Disable2FA dialog */}
<Disable2FA
open={disable2FAOpen}
handleClose={handleCancelDisable2FA}
handleDisable2FA={handleDisable2FA}
/>
{/* Render UpdateSubscription dialog */}
<UpdateSubscription
open={updateSubscriptionOpen}
onClose={handleCloseUpdateSubscription}
/>
{/* Render ChangeEmail dialog */}
<ChangeEmail
open={changeEmailOpen}
onClose={handleCloseChangeEmail}
/>
</Grid>
);
};
export default UserComponent;

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@@ -1,10 +1,10 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { App } from "./App";
import App from "./App";
import "./styles/globals.css";
const root = document.getElementById("root");
if (!root) throw new Error("Could not load root element to render onto");
if (!root) throw new Error("Could not load root element to qrender onto");
ReactDOM.createRoot(root).render(
<React.StrictMode>

View File

@@ -1,6 +1,7 @@
import { z } from "zod";
export const apiOrigin = import.meta.env.VITE_ENTE_API_ORIGIN ?? "https://api.ente.io";
export const apiOrigin =
import.meta.env.VITE_ENTE_API_ORIGIN ?? "https://api.ente.io";
const UserDetails = z.object({}).passthrough();

View File

@@ -9,7 +9,9 @@ interface ImportMetaEnv {
* Override the origin (scheme://host:port) of Ente's API to connect to.
*
* Default is "https://api.ente.io".
*/
*/
readonly VITE_ENTE_API_ORIGIN: string | undefined;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,3 @@
{
"tabWidth": 4,
"proseWrap": "always"
"tabWidth": 4
}

View File

@@ -1,26 +1,23 @@
# Cloudflare Workers
Source code for our
[Cloudflare Workers](https://developers.cloudflare.com/workers/).
Source code for our [Cloudflare
Workers](https://developers.cloudflare.com/workers/).
Each worker is a self contained directory with its each `package.json`.
## Deploying
- Switch to a worker directory, e.g. `cd github-discord-notifier`.
* Switch to a worker directory, e.g. `cd github-discord-notifier`.
- Install dependencies (if needed) with `yarn`
* Install dependencies (if needed) with `yarn`
> If you have previously deployed, then you will have an old `yarn.lock`. In
> this case it is safe to delete and recreate using `rm yarn.lock && yarn`.
* Login into wrangler (if needed) using `yarn wrangler login`
- Login into wrangler (if needed) using `yarn wrangler login`
- Deploy! `yarn wrangler deploy`
* Deploy! `yarn wrangler deploy`
Wrangler is the CLI provided by Cloudflare to manage workers. Apart from
deploying, it also allows us to stream logs from running workers by using
`yarn wrangler tail`.
deploying, it also allows us to stream logs from running workers by using `yarn
wrangler tail`.
## Creating a new worker
@@ -33,12 +30,3 @@ To import an existing worker from the Cloudflare dashboard, use
```sh
npm create cloudflare@2 existing-worker-name -- --type pre-existing --existing-script existing-worker-name
```
## Logging
Attach the tail worker to your worker by adding
tail_consumers = [{ service = "tail" }]
in its `wrangler.toml`. Then any `console.(log|warn|error)` statements and
uncaught exceptions in your worker will be logged to Grafana.

View File

@@ -2,9 +2,8 @@
"name": "cast-albums",
"private": true,
"devDependencies": {
"@cloudflare/workers-types": "^4.20240614.0",
"@cloudflare/workers-types": "^4.20240314.0",
"typescript": "^5",
"wrangler": "^3"
},
"packageManager": "yarn@1.22.22"
}
}

View File

@@ -1,72 +1,50 @@
/** Proxy file and thumbnail requests for the cast web app. */
/** Proxy file and thumbnail requests from the cast web app */
export default {
async fetch(request: Request) {
switch (request.method) {
case "OPTIONS":
return handleOPTIONS(request);
case "GET":
return handleGET(request);
case "OPTIONS":
return handleOPTIONS(request);
default:
console.log(`Unsupported HTTP method ${request.method}`);
return new Response(null, { status: 405 });
throw new Error(
`HTTP 405 Method Not Allowed: ${request.method}`
);
}
},
} satisfies ExportedHandler;
const handleOPTIONS = (request: Request) => {
const origin = request.headers.get("Origin");
if (!isAllowedOrigin(origin)) console.warn("Unknown origin", origin);
return new Response("", {
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, OPTIONS",
"Access-Control-Max-Age": "86400",
"Access-Control-Allow-Headers": "X-Cast-Access-Token",
},
});
};
const isAllowedOrigin = (origin: string | null) => {
const allowed = ["cast.ente.io", "cast.ente.sh", "localhost"];
if (!origin) return false;
try {
const url = new URL(origin);
return allowed.includes(url.hostname);
} catch {
// origin is likely an invalid URL
return false;
}
};
const handleGET = async (request: Request) => {
const url = new URL(request.url);
const urlParams = new URLSearchParams(url.search);
const token =
request.headers.get("X-Cast-Access-Token") ??
urlParams.get("castToken");
const fileID = url.searchParams.get("fileID");
if (!fileID) return new Response(null, { status: 400 });
let castToken = request.headers.get("X-Cast-Access-Token");
if (!castToken) {
console.warn("Using deprecated castToken query param");
castToken = url.searchParams.get("castToken");
}
if (!castToken) {
console.error("No cast token provided");
return new Response(null, { status: 400 });
}
const fileID = urlParams.get("fileID");
const pathname = url.pathname;
const params = new URLSearchParams({ castToken });
let response = await fetch(
`https://api.ente.io/cast/files${pathname}${fileID}?${params.toString()}`
`https://api.ente.io/cast/files${pathname}${fileID}?castToken=${token}`
);
if (!response.ok) console.log("Upstream error", response.status);
response = new Response(response.body, response);
response.headers.set("Access-Control-Allow-Origin", "*");
return response;
};
const handleOPTIONS = (request: Request) => {
let corsHeaders: Record<string, string> = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,OPTIONS",
"Access-Control-Max-Age": "86400",
};
const acrh = request.headers.get("Access-Control-Request-Headers");
if (acrh) {
corsHeaders["Access-Control-Allow-Headers"] = acrh;
}
return new Response("", { headers: corsHeaders });
};

View File

@@ -1 +1 @@
{ "extends": "../tsconfig.base.json", "include": ["src"] }
{ "extends": "../tsconfig.base.json", "include": ["src/**/*.ts"] }

View File

@@ -1,11 +1,8 @@
name = "cast-albums"
main = "src/index.ts"
compatibility_date = "2024-06-14"
compatibility_date = "2024-03-14"
routes = [
{ pattern = "cast-albums.ente.io", custom_domain = true }
]
tail_consumers = [
{ service = "tail" }
]
[[routes]]
pattern = "cast-albums.ente.io"
zone_name = "ente.io"
custom_domain = true

View File

@@ -1,10 +0,0 @@
{
"name": "files",
"private": true,
"devDependencies": {
"@cloudflare/workers-types": "^4.20240614.0",
"typescript": "^5",
"wrangler": "^3"
},
"packageManager": "yarn@1.22.22"
}

View File

@@ -1,102 +0,0 @@
/** Proxy requests for files. */
export default {
async fetch(request: Request) {
switch (request.method) {
case "OPTIONS":
return handleOPTIONS(request);
case "GET":
return handleGET(request);
default:
console.log(`Unsupported HTTP method ${request.method}`);
return new Response(null, { status: 405 });
}
},
} satisfies ExportedHandler;
const handleOPTIONS = (request: Request) => {
const origin = request.headers.get("Origin");
if (!isAllowedOrigin(origin)) console.warn("Unknown origin", origin);
const headers = request.headers.get("Access-Control-Request-Headers");
if (!areAllowedHeaders(headers))
console.warn("Unknown header in list", headers);
return new Response("", {
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, OPTIONS",
"Access-Control-Max-Age": "86400",
// "Access-Control-Allow-Headers": "X-Auth-Token, X-Client-Package",
"Access-Control-Allow-Headers": "*",
},
});
};
const isAllowedOrigin = (origin: string | null) => {
if (!origin) return false;
try {
const url = new URL(origin);
const hostname = url.hostname;
return (
origin == "ente://app" /* desktop app */ ||
hostname.endsWith("ente.io") ||
hostname.endsWith("ente.sh") ||
hostname == "localhost"
);
} catch {
// `origin` is likely an invalid URL.
return false;
}
};
const areAllowedHeaders = (headers: string | null) => {
const allowed = ["x-auth-token", "x-client-package"];
if (!headers) return true;
for (const header of headers.split(",")) {
if (!allowed.includes(header.trim().toLowerCase())) return false;
}
return true;
};
const handleGET = async (request: Request) => {
const url = new URL(request.url);
// Random bots keep trying to pentest causing noise in the logs. If the
// request doesn't have a fileID, we can just safely ignore it thereafter.
const fileID = url.searchParams.get("fileID");
if (!fileID) return new Response(null, { status: 400 });
let token = request.headers.get("X-Auth-Token");
if (!token) {
console.warn("Using deprecated token query param");
token = url.searchParams.get("token");
}
if (!token) {
console.error("No token provided");
// return new Response(null, { status: 400 });
}
// We forward the auth token as a query parameter to museum. This is so that
// it does not get preserved when museum does a redirect to the presigned S3
// URL that serves the actual thumbnail.
//
// See: [Note: Passing credentials for self-hosted file fetches]
const params = new URLSearchParams();
if (token) params.set("token", token);
let response = await fetch(
`https://api.ente.io/files/download/${fileID}?${params.toString()}`,
{
headers: {
"User-Agent": request.headers.get("User-Agent") ?? "",
},
}
);
if (!response.ok) console.log("Upstream error", response.status);
response = new Response(response.body, response);
response.headers.set("Access-Control-Allow-Origin", "*");
return response;
};

View File

@@ -1 +0,0 @@
{ "extends": "../tsconfig.base.json", "include": ["src"] }

View File

@@ -1,11 +0,0 @@
name = "files"
main = "src/index.ts"
compatibility_date = "2024-06-14"
routes = [
{ pattern = "files.ente.io", custom_domain = true }
]
tail_consumers = [
{ service = "tail" }
]

View File

@@ -2,9 +2,8 @@
"name": "github-discord-notifier",
"private": true,
"devDependencies": {
"@cloudflare/workers-types": "^4.20240614.0",
"@cloudflare/workers-types": "^4.20240314.0",
"typescript": "^5",
"wrangler": "^3"
},
"packageManager": "yarn@1.22.22"
}
}

Some files were not shown because too many files have changed in this diff Show More