Compare commits

..

93 Commits

Author SHA1 Message Date
8be4246f7e chore: Vasco da Gama Bridge 3.6.0 changelog. 2023-10-11 16:09:55 +02:00
e580f89106 feat(GODT-3004): update gopenpgp and dependencies. 2023-10-11 15:29:52 +02:00
275b30e518 chore: Vasco da Gama Bridge 3.6.0 changelog. 2023-10-10 11:29:36 +02:00
bf244e5c86 fix(GODT-3003): Ensure IMAP State is reset after vault corruption
After we detect that the user has suffered the GODT-3003 bug due the
vault corruption not ensuring that a previous sync state would be
erased, we patch the gluon db directly and then reset the sync state.

After the account is added, the sync is automatically triggered and the
account state fixes itself.
2023-10-10 11:24:06 +02:00
cf9651bb94 fix(GODT-3001): Only create system labels during system label sync 2023-10-10 11:23:32 +02:00
ba65ffdbc7 chore: Umshiang Bridge 3.5.2 changelog. 2023-10-10 11:22:41 +02:00
d3582fa981 chore: Vasco da Gama Bridge 3.6.0 changelog. 2023-10-03 16:43:33 +02:00
80c852a5b2 fix(GODT-2992): fix link in 'no account view' in main window after 2FA or TOTP are cancelled.
(cherry picked from commit 1c344211d1)
2023-10-03 11:08:52 +02:00
0c212fbef4 chore: Vasco da Gama Bridge 3.6.0 changelog. 2023-10-02 16:31:07 +02:00
48d1ca1e72 fix(GODT-2989): allow to send bug report when no account connected. 2023-10-02 13:34:40 +00:00
52addb2582 feat(GODT-2960): replaced the account list with a button and label when no account is configured. 2023-09-29 17:36:23 +02:00
742d9eeef3 feat(GODT-2960): added content in empty view when there is no account. 2023-09-29 17:36:23 +02:00
55a9d4973c fix(GODT-2988): fix setup wizard KB links. 2023-09-29 15:25:30 +02:00
8402657108 fix(GODT-2968): use proper base64 encoded string even for bad password test. 2023-09-29 08:35:41 +00:00
8a6f96f9f2 fix(GODT-2965): fix multipart/mixed testdata + structure parsing steps related to this. 2023-09-29 07:08:10 +00:00
56c53e9188 fix(GODT-2932): fix syncing not being reported in GUI. 2023-09-28 12:39:24 +02:00
bb67d95669 fix(GODT-2967): tray menu entries close the setup wizard when needed. 2023-09-27 18:23:02 +02:00
50acc0dcfb feat(GODT-2725): Implement receive message step with expected structure exposed. 2023-09-27 14:17:51 +00:00
e9c73c2d0d chore: Umshiang Bridge 3.5.1 changelog. 2023-09-27 15:34:50 +02:00
07c03c6920 fix(GODT-2963): Use multi error to report file removal errors
Do not abort removing files on first error. Collect errors and try to
remove as many as possible. This would cause some state files to not be
removed on windows.
2023-09-27 11:30:46 +02:00
f4958b9b53 fix(GODT-2956): Restore old deletion rules
When unlabeling a message from trash we have to check if this message is
present in another folder before perma-deleting.
2023-09-26 13:47:09 +02:00
76f2e7fdb9 fix(GODT-2951): Negative WaitGroup Counter
Do not defer call to `wg.Done()` in `job.onJobFinished`. If there is an
error it will also call `wg.Done()`.
2023-09-26 09:45:27 +02:00
c0992e8801 fix(GODT-2590): Fix send on closed channel
Ensure periodic user tasks are terminated before the other user
services. The panic triggered due to the fact that the telemetry service
was shutdown before this periodic task.
2023-09-26 09:20:01 +02:00
cf3abaa96f fix(GODT-2949): Fix close of close channel in event service
This issue is triggered due to the `Service.Close()` call after the
go-routine for the event service exists. It is possible that during this
period a recently added subscriber with `pendingOpAdd` gets cancelled
and closed.

However, the subscriber later also enqueues a `pendingOpRemove` which
gets processed again with a call in `user.eventService.Close()` leading
to the double close panic.

This patch simply removes the `s.Close()` from the service, and leaves
the cleanup to called externally from user.Close() or user.Logout().
2023-09-26 09:08:25 +02:00
e422b28bc3 fix(GODT-2212): Preserver Header order in message building
https://github.com/ProtonMail/go-proton-api/pull/100
2023-09-25 15:05:21 +02:00
a1a5ffba5d chore: Vasco da Gama Bridge 3.6.0 changelog. 2023-09-25 12:00:10 +02:00
f8b86a76dd feat(GODT-2772): fixed missing space in error message. 2023-09-19 07:58:19 +02:00
ab1281ceee feat(GODT-2772): added final link to knowledge base articles. 2023-09-19 07:58:19 +02:00
0ab0f2f4ff feat(GODT-2772): setup wizard report knowledge base article opening event. 2023-09-19 07:58:19 +02:00
09d87023f1 feat(GODT-2772): removed web engine from deploy.
This partly reverts commit c89d206a9576499c3df29139c8df9099a053a839.
2023-09-19 07:58:19 +02:00
139ad75394 feat(GODT-2772): removed web frame. 2023-09-19 07:58:19 +02:00
c8cf90abfe feat(GODT-2772): use os browser instead of integrated one for external links (for now). 2023-09-19 07:57:59 +02:00
5d4f8f7d40 feat(GODT-2772): implemented internal help links. 2023-09-19 07:57:59 +02:00
ea26dc0e97 feat(GODT-2772): external links have an icon. 2023-09-19 07:57:59 +02:00
8d346ea511 feat(GODT-2772): removed useless extra space in button with icons. 2023-09-19 07:57:59 +02:00
44df3cfd4a feat(GODT-2772): configure email client button is highlighted
Misc minor tweaks & fixes.
2023-09-19 07:57:59 +02:00
683458e264 feat(GODT-2772): use new Thunderbird logo.
The logo is a raster image inside a SVG file, as the pure vector version does not render properly in QML or Affinity Designer.
2023-09-19 07:57:59 +02:00
36651698cb feat(GODT-2772): new illustration for client selector. 2023-09-19 07:57:59 +02:00
0c7e17701f feat(GODT-2772): HTML placeholder is not loaded from resources anymore. 2023-09-19 07:57:59 +02:00
86cd2437aa feat(GODT-2772): misc tweaks.
- Step description box tweaks and text color changes.
- Factored out some constants (margins and dimensions.
- Removed the ProtonStyle.px scaling which was useless as it was not applied everywhere.
2023-09-19 07:57:59 +02:00
53f5f9aa43 feat(GODT-2772): client selector left pane tweaks. 2023-09-19 07:57:59 +02:00
c849762445 feat(GODT-2772): placeholder for missing help content. 2023-09-19 07:57:59 +02:00
32f2c72575 feat(GODT-2772): use WebEngineView instead of WebView 2023-09-19 07:57:59 +02:00
958e1280d7 feat(GODT-2772): error handling for Apple Mail auto config. 2023-09-19 07:57:59 +02:00
df09d6d221 feat(GODT-2772): back button. 2023-09-19 07:57:59 +02:00
e0875dc928 feat(GODT-2772): placement of error message on login pages. 2023-09-19 07:57:59 +02:00
b3a5270bdc feat(GODT-2772): marked strings as translatable. 2023-09-19 07:57:59 +02:00
f617a44d28 feat(GODT-2772): link for Apple Mail manual configuration. 2023-09-19 07:57:59 +02:00
75ed3ca660 feat(GODT-2772): QML import cleanup. 2023-09-19 07:57:59 +02:00
69f3029430 feat(GODT-2772): Apple Mail profile install page. 2023-09-19 07:57:59 +02:00
1203709ab9 feat(GODT-2772): Apple Mail cert install page. 2023-09-19 07:57:59 +02:00
15c18189d3 feat(GODT-2772): client config success screen. 2023-09-19 07:57:59 +02:00
a9e95f618b feat(GODT-2772): tweaked client parameter screen. 2023-09-19 07:57:59 +02:00
272f9cf59b feat(GODT-2772): new client selector design. 2023-09-19 07:57:59 +02:00
6e86c95640 feat(GODT-2772): new login layout. 2023-09-19 07:57:59 +02:00
81afc5fb1f feat(GODT-2772): new onboarding layout. 2023-09-19 07:57:59 +02:00
53ea5e9adc feat(GODT-2772): fix aliasing in protonmail wordmark on Windows. 2023-09-19 07:57:59 +02:00
6f420f9098 feat(GODT-2772): converted setup wizard help link to button with context menu. 2023-09-19 07:57:59 +02:00
65846ff40f feat(GODT-2772): removed warning and outlook selector setup wizard pages. 2023-09-19 07:57:59 +02:00
43f7a989be feat(GODT-2771): added CLI commands for cert install/uninstall/status check on macOS. 2023-09-19 07:57:59 +02:00
452d3068f0 feat(GODT-2771): removed cert check and install on app startup on macOS. 2023-09-19 07:57:59 +02:00
69190daf3f feat(GODT-2771): macOS cert install support in bridge-gui-test + placeholder QML. 2023-09-19 07:57:59 +02:00
f57a40677e feat(GODT-2771): gRPC calls for TLS certificates. 2023-09-19 07:57:59 +02:00
2d6f42e0b5 feat(GODT-2771): improved macOS cert installation tools. 2023-09-19 07:57:59 +02:00
bccf31501d feat(GODT-2769): moved LinkLabel QML component to Proton custom component folder. 2023-09-19 07:57:59 +02:00
9b546b5412 feat(GODT-2762): adjust mac and windows qt deploy
* do not remove web engine frameworks from macos bundle
* add libs, QML files, resources, translations needed for WebView
* ship QWebEngineProcess in linux and windows builds
2023-09-19 07:57:59 +02:00
f48a60d58c feat(GODT-2762): bump version Go 1.20 Qt 6.4.3. 2023-09-19 07:57:59 +02:00
0a51c7a6b0 feat(GODT-2769): Setup Wizard QML foundations. 2023-09-19 07:57:59 +02:00
7355c7dfd6 feat(GODT-2767): unified colorScheme management. [skip-ci] 2023-09-19 07:57:59 +02:00
bb5a91ee6d feat(GODT-2767): wired bug report link + use enum for wizard stack layout. 2023-09-19 07:57:58 +02:00
ca5f7ce9f6 feat(GODT-2767): connected existing entrypoints to wizard, and moved it to a stack layout. [skip-ci] 2023-09-19 07:57:58 +02:00
ad31e6a9c5 feat(GODT-2767): pass user and username to setup wizard. 2023-09-19 07:57:58 +02:00
9ef7d133c0 feat(GODT-2767): client config page. [skip-ci] 2023-09-19 07:57:58 +02:00
83b842b19d feat(GODT-2767): per client configuration left pane + refactoring. [skip-ci] 2023-09-19 07:57:58 +02:00
df02e39fe1 feat(GODT-2767): Outlook version selector and warning screen. 2023-09-19 07:57:58 +02:00
a35c8424a3 chore: fix after rebase. 2023-09-19 07:57:58 +02:00
5d207810bd feat(GODT-2767): client selection. [skip-ci] 2023-09-19 07:57:58 +02:00
6c9d96d5e1 chore: fixed missing GoOs gRPC call in bridge-gui-tester. 2023-09-19 07:57:58 +02:00
0fc41d1966 feat(GODT-2767): unified left pane + client config left pane. [skip-ci] 2023-09-19 07:57:58 +02:00
dd5e745e37 feat(GODT-2767): login right pane. [skip-ci] 2023-09-19 07:57:58 +02:00
c8f0d7f32a feat(GODT-2767): login right pane, WIP. [skip-ci] 2023-09-19 07:57:58 +02:00
bd986901c3 feat(GODT-2767): login left pane. [skip-ci] 2023-09-19 07:57:58 +02:00
cdc19492ee feat(GODT-2762): onboarding right pane. 2023-09-19 07:57:58 +02:00
635b2a4891 feat(GODT-2762): setup wizard: onboarding left pane. 2023-09-19 07:57:58 +02:00
e5bac33a04 feat(GODT-2767): setup wizard frame. WIP [skip-cli] 2023-09-19 07:57:58 +02:00
7b96a07cf5 feat(GODT-2770): proof of concept for web view as a tool window. 2023-09-19 07:57:58 +02:00
87e79fdcba feat(GODT-2770): proof of concept for web view as overlay. 2023-09-19 07:57:58 +02:00
03c3404044 chore(GODT-2916): Split Decryption from Message Building
This helps the export tool to deal with problems arising from message
assembly after everything has been successfully encrypted.

The original behavior is still available under `DecryptAndBuildRFC822`.
2023-09-18 14:40:07 +02:00
fa794a982b feat(GODT-2597): Implement contact specific settings in integration tests. 2023-09-15 10:53:58 +00:00
cab32d5d5a chore: update changelog. 2023-09-13 10:26:24 +02:00
8e5a892c45 feat(GODT-2664): trigger QA installer. 2023-09-12 08:45:02 +00:00
50dc5c4085 chore: Umshiang Bridge 3.5.0 changelog. 2023-09-12 08:00:12 +02:00
3b58078595 fix(GODT-2929): Message dedup with different text transfer encoding
https://github.com/ProtonMail/gluon/pull/396
2023-09-11 15:44:11 +02:00
115 changed files with 6348 additions and 3429 deletions

View File

@ -71,7 +71,7 @@ stages:
- export GO111MODULE=on
- export PATH="${GOPATH}/bin:${PATH}"
- export MSYSTEM=
- export QT6DIR=/c/grrrQt/6.3.2/msvc2019_64
- export QT6DIR=/c/grrrQt/6.4.3/msvc2019_64
- export PATH=$PATH:${QT6DIR}/bin
- export PATH="/c/Program Files/Microsoft Visual Studio/2022/Community/Common7/IDE/CommonExtensions/Microsoft/CMake/CMake/bin:$PATH"
- $(git config --global -l | grep -o 'url.*gitlab.protontech.ch.*insteadof' | xargs -L 1 git config --global --unset &> /dev/null) || echo "nothing to remove"
@ -93,7 +93,7 @@ stages:
- export PATH="${GOROOT}/bin:$PATH"
- export GOPATH=~/go1.20
- export PATH="${GOPATH}/bin:$PATH"
- export QT6DIR=/opt/Qt/6.3.2/macos
- export QT6DIR=/opt/Qt/6.4.3/macos
- export PATH="${QT6DIR}/bin:$PATH"
- uname -a
cache: {}
@ -101,7 +101,7 @@ stages:
- macos-m1-bridge
.env-linux-build:
image: gitlab.protontech.ch:4567/go/bridge-internal:build-go1.20-qt6.3.2
image: gitlab.protontech.ch:4567/go/bridge-internal:build-go1.20-qt6.4.3
variables:
VCPKG_DEFAULT_BINARY_CACHE: ${CI_PROJECT_DIR}/.cache
cache:
@ -282,4 +282,18 @@ build-windows-qa:
variables:
BUILD_TAGS: "build_qa"
trigeer-qa-installer:
stage: build
needs: ["lint"]
extends:
- .rules-branch-and-MR-manual
variables:
APP: bridge
WORKFLOW: build-all
SRC_TAG: $CI_COMMIT_BRANCH
SRC_HASH: $CI_COMMIT_SHA
trigger:
project: "jcuth/bridge-release"
branch: master
# TODO: PUT BACK ALL THE JOBS! JUST DID THIS FOR NOW TO GET CI WORKING AGAIN...

View File

@ -10,7 +10,7 @@
* Windres (Windows)
* libglvnd and libsecret development files (Linux)
* pkg-config (Linux)
* cmake, ninja-build and Qt 6 are required to build the graphical user interface. On Linux,
* cmake, ninja-build and Qt 6.4.3 are required to build the graphical user interface. On Linux,
the Mesa OpenGL development files are also needed.
To enable the sending of crash reports using Sentry please set the
@ -19,7 +19,7 @@ Otherwise, the sending of crash reports will be disabled.
## Build
In order to build Bridge app with Qt interface we are using
[Qt 6.3](https://doc.qt.io/qt-6/gettingstarted.html).
[Qt 6.4.3](https://doc.qt.io/qt-6/gettingstarted.html).
Please note that qmake path must be in your `PATH` to ensure Qt to be found.
Also, before you start build **on Windows**, please unset the `MSYSTEM` variable

View File

@ -40,6 +40,7 @@ Proton Mail Bridge includes the following 3rd party software:
* [go-message](https://github.com/emersion/go-message) available under [license](https://github.com/emersion/go-message/blob/master/LICENSE)
* [go-sasl](https://github.com/emersion/go-sasl) available under [license](https://github.com/emersion/go-sasl/blob/master/LICENSE)
* [go-smtp](https://github.com/emersion/go-smtp) available under [license](https://github.com/emersion/go-smtp/blob/master/LICENSE)
* [go-vcard](https://github.com/emersion/go-vcard) available under [license](https://github.com/emersion/go-vcard/blob/master/LICENSE)
* [color](https://github.com/fatih/color) available under [license](https://github.com/fatih/color/blob/master/LICENSE)
* [sentry-go](https://github.com/getsentry/sentry-go) available under [license](https://github.com/getsentry/sentry-go/blob/master/LICENSE)
* [resty](https://github.com/go-resty/resty/v2) available under [license](https://github.com/go-resty/resty/v2/blob/master/LICENSE)
@ -83,7 +84,6 @@ Proton Mail Bridge includes the following 3rd party software:
* [go-spew](https://github.com/davecgh/go-spew) available under [license](https://github.com/davecgh/go-spew/blob/master/LICENSE)
* [go-windows](https://github.com/elastic/go-windows) available under [license](https://github.com/elastic/go-windows/blob/master/LICENSE)
* [go-textwrapper](https://github.com/emersion/go-textwrapper) available under [license](https://github.com/emersion/go-textwrapper/blob/master/LICENSE)
* [go-vcard](https://github.com/emersion/go-vcard) available under [license](https://github.com/emersion/go-vcard/blob/master/LICENSE)
* [fgprof](https://github.com/felixge/fgprof) available under [license](https://github.com/felixge/fgprof/blob/master/LICENSE)
* [go-shlex](https://github.com/flynn-archive/go-shlex) available under [license](https://github.com/flynn-archive/go-shlex/blob/master/LICENSE)
* [mimetype](https://github.com/gabriel-vasile/mimetype) available under [license](https://github.com/gabriel-vasile/mimetype/blob/master/LICENSE)
@ -123,6 +123,7 @@ Proton Mail Bridge includes the following 3rd party software:
* [codec](https://github.com/ugorji/go/codec) available under [license](https://github.com/ugorji/go/codec/blob/master/LICENSE)
* [tagparser](https://github.com/vmihailenco/tagparser/v2) available under [license](https://github.com/vmihailenco/tagparser/v2/blob/master/LICENSE)
* [smetrics](https://github.com/xrash/smetrics) available under [license](https://github.com/xrash/smetrics/blob/master/LICENSE)
* [go-ordered-json](https://gitlab.com/c0b/go-ordered-json)
* [arch](https://golang.org/x/arch) available under [license](https://cs.opensource.google/go/x/arch/+/master:LICENSE)
* [crypto](https://golang.org/x/crypto) available under [license](https://cs.opensource.google/go/x/crypto/+/master:LICENSE)
* [mod](https://golang.org/x/mod) available under [license](https://cs.opensource.google/go/x/mod/+/master:LICENSE)

View File

@ -3,6 +3,42 @@
Changelog [format](http://keepachangelog.com/en/1.0.0/)
## Vasco da Gama Bridge 3.6.0
### Added
* GODT-2762: Setup wizard.
* GODT-2772: Setup wizard content.
* GODT-2769: Setup Wizard architecture.
* GODT-2767: Setup Wizard foundations.
* GODT-2725: Implement receive message step with expected structure exposed.
### Changed
* GODT-2960: Added content in empty view when there is no account.
* GODT-2771: Cert related tools for macOS.
* GODT-2770: Proof of concept for web view as a tool window and overlay (not used).
* GODT-2916: Split Decryption from Message Building.
* GODT-2597: Implement contact specific settings in integration tests.
* GODT-2664: Trigger QA installer.
### Fixed
* GODT-2992: Fix link in 'no account view' in main window after 2FA or TOTP are cancelled.
* GODT-2989: Allow to send bug report when no account connected.
* GODT-2988: Fix setup wizard KB links.
* GODT-2968: Use proper base64 encoded string even for bad password test.
* GODT-2965: Fix multipart/mixed testdata + structure parsing steps related to this.
* GODT-2932: Fix syncing not being reported in GUI.
* GODT-2967: Tray menu entries close the setup wizard when needed.
* GODT-2212: Preserver Header order in message building.
* Fixed missing GoOs gRPC call in bridge-gui-tester.
* GODT-2929: Message dedup with different text transfer encoding.
## Umshiang Bridge 3.5.3
### Changed
* GODT-3004: Update gopenpgp and dependencies.
## Umshiang Bridge 3.5.2
### Fixed

View File

@ -11,7 +11,7 @@ ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
.PHONY: build build-gui build-nogui build-launcher versioner hasher
# Keep version hardcoded so app build works also without Git repository.
BRIDGE_APP_VERSION?=3.5.2+git
BRIDGE_APP_VERSION?=3.6.0+git
APP_VERSION:=${BRIDGE_APP_VERSION}
APP_FULL_NAME:=Proton Mail Bridge
APP_VENDOR:=Proton AG

9
go.mod
View File

@ -7,8 +7,8 @@ require (
github.com/Masterminds/semver/v3 v3.2.0
github.com/ProtonMail/gluon v0.17.1-0.20231009084701-3af0474b0b3c
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
github.com/ProtonMail/go-proton-api v0.4.1-0.20230831064234-0e3a549b3f36
github.com/ProtonMail/gopenpgp/v2 v2.7.1-proton
github.com/ProtonMail/go-proton-api v0.4.1-0.20231011132529-24b5b817ee1f
github.com/ProtonMail/gopenpgp/v2 v2.7.3-proton
github.com/PuerkitoBio/goquery v1.8.1
github.com/abiosoft/ishell v2.0.0+incompatible
github.com/allan-simon/go-singleinstance v0.0.0-20210120080615-d0997106ab37
@ -22,6 +22,7 @@ require (
github.com/emersion/go-message v0.16.0
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead
github.com/emersion/go-smtp v0.15.1-0.20221021114529-49b17434419d
github.com/emersion/go-vcard v0.0.0-20230331202150-f3d26859ccd3
github.com/fatih/color v1.13.0
github.com/getsentry/sentry-go v0.15.0
github.com/go-resty/resty/v2 v2.7.0
@ -52,7 +53,7 @@ require (
require (
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233 // indirect
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect
github.com/ProtonMail/go-srp v0.0.7 // indirect
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect
@ -68,7 +69,6 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/elastic/go-windows v1.0.1 // indirect
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 // indirect
github.com/emersion/go-vcard v0.0.0-20230331202150-f3d26859ccd3 // indirect
github.com/felixge/fgprof v0.9.3 // indirect
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
@ -108,6 +108,7 @@ require (
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.9.0 // indirect
golang.org/x/mod v0.8.0 // indirect

22
go.sum
View File

@ -28,19 +28,18 @@ github.com/ProtonMail/gluon v0.17.1-0.20231009084701-3af0474b0b3c/go.mod h1:Og5/
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4=
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
github.com/ProtonMail/go-crypto v0.0.0-20230321155629-9a39f2531310/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE=
github.com/ProtonMail/go-crypto v0.0.0-20230322105811-d73448b7e800/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE=
github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 h1:ZK3C5DtzV2nVAQTx5S5jQvMeDqWtD1By5mOoyY/xJek=
github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE=
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233 h1:bdoKdh0f66/lrgVfYlxw0aqISY/KOqXmFJyGt7rGmnc=
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7 h1:+j+Kd/DyZ/qGfMT9htAT7HxqIEbZHsatsx+m8AoV6fc=
github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4=
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
github.com/ProtonMail/go-proton-api v0.4.1-0.20230831064234-0e3a549b3f36 h1:JVMK2w90bCWayUCXJIb3wkQ5+j2P/NbnrX3BrDoLzsc=
github.com/ProtonMail/go-proton-api v0.4.1-0.20230831064234-0e3a549b3f36/go.mod h1:nS8hMGjJLgC0Iej0JMYbsI388LesEkM1Hj/jCCxQeaQ=
github.com/ProtonMail/go-proton-api v0.4.1-0.20231011132529-24b5b817ee1f h1:n0oBMAz2dJhn5+1WA6NrjkWqkZN+22FQMkPlRwNGhpU=
github.com/ProtonMail/go-proton-api v0.4.1-0.20231011132529-24b5b817ee1f/go.mod h1:ZmvQMA8hanLiD1tFsvu9+qGBcuxbIRfch/4z/nqBhXA=
github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI=
github.com/ProtonMail/go-srp v0.0.7/go.mod h1:giCp+7qRnMIcCvI6V6U3S1lDDXDQYx2ewJ6F/9wdlJk=
github.com/ProtonMail/gopenpgp/v2 v2.7.1-proton h1:YS6M20yvjCJPR1r4ADW5TPn6rahs4iAyZaACei86bEc=
github.com/ProtonMail/gopenpgp/v2 v2.7.1-proton/go.mod h1:S1lYsaGHykYpxxh2SnJL6ypcAlANKj5NRSY6HxKryKQ=
github.com/ProtonMail/gopenpgp/v2 v2.7.3-proton h1:wuAxBUU9qF2wyDVJprn/2xPDx000eol5gwlKbOUYY88=
github.com/ProtonMail/gopenpgp/v2 v2.7.3-proton/go.mod h1:omVkSsfPAhmptzPF/piMXb16wKIWUvVhZbVW7sJKh0A=
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
github.com/abiosoft/ishell v2.0.0+incompatible h1:zpwIuEHc37EzrsIYah3cpevrIc8Oma7oZPxr03tlmmw=
@ -64,6 +63,7 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm
github.com/bradenaw/juniper v0.12.0 h1:Q/7icpPQD1nH/La5DobQfNEtwyrBSiSu47jOQx7lJEM=
github.com/bradenaw/juniper v0.12.0/go.mod h1:Z2B7aJlQ7xbfWsnMLROj5t/5FQ94/MkIdKC30J4WvzI=
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
@ -399,6 +399,8 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRT
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a h1:DxppxFKRqJ8WD6oJ3+ZXKDY0iMONQDl5UTg2aTyHh8k=
gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a/go.mod h1:NREvu3a57BaK0R1+ztrEzHWiZAihohNLQ6trPxlIqZI=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
@ -417,6 +419,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
@ -464,6 +467,7 @@ golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
@ -512,6 +516,8 @@ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -519,6 +525,7 @@ golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
@ -529,6 +536,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=

View File

@ -22,7 +22,6 @@ import (
"path"
"github.com/ProtonMail/gluon/async"
"github.com/ProtonMail/proton-bridge/v3/internal/certs"
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
"github.com/ProtonMail/proton-bridge/v3/internal/locations"
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
@ -45,23 +44,6 @@ func WithVault(locations *locations.Locations, panicHandler async.PanicHandler,
"corrupt": corrupt,
}).Debug("Vault created")
// Install the certificates if needed.
if installed := encVault.GetCertsInstalled(); !installed {
logrus.Debug("Installing certificates")
certPEM, _ := encVault.GetBridgeTLSCert()
if err := certs.NewInstaller().InstallCert(certPEM); err != nil {
return fmt.Errorf("failed to install certs: %w", err)
}
if err := encVault.SetCertsInstalled(true); err != nil {
return fmt.Errorf("failed to set certs installed: %w", err)
}
logrus.Debug("Certificates successfully installed")
}
// GODT-1950: Add teardown actions (e.g. to close the vault).
return fn(encVault, insecure, corrupt)

View File

@ -34,7 +34,7 @@ const (
)
func (bridge *Bridge) ReportBug(ctx context.Context, osType, osVersion, title, description, username, email, client string, attachLogs bool) error {
var account string
var account = username
if info, err := bridge.QueryUserInfo(username); err == nil {
account = info.Username

View File

@ -23,71 +23,200 @@ package certs
#import <Foundation/Foundation.h>
#import <Security/Security.h>
// Memory management rules:
// Foundation object (Objective-C prefixed with `NS`) get ARC (Automatic Reference Counting), and do not need to be released manually.
// Core Foundation objects (C), prefixed with need to be released manually using CFRelease() unless:
// - They're obtained using a CF method containing the word Get (a.k.a. the Get Rule).
// - They're obtained using toll-free bridging from a Foundation Object (using the __bridge keyword).
int installTrustedCert(char const *bytes, unsigned long long length) {
if (length == 0) {
return errSecInvalidData;
}
NSData *der = [NSData dataWithBytes:bytes length:length];
// Step 1. Import the certificate in the keychain.
SecCertificateRef cert = SecCertificateCreateWithData(NULL, (CFDataRef) der);
NSDictionary* addQuery = @{
(id)kSecValueRef: (__bridge id) cert,
(id)kSecClass: (id)kSecClassCertificate,
};
OSStatus status = SecItemAdd((__bridge CFDictionaryRef) addQuery, NULL);
if ((errSecSuccess != status) && (errSecDuplicateItem != status)) {
CFRelease(cert);
return status;
}
// Step 2. Set the trust for the certificate.
SecPolicyRef policy = SecPolicyCreateSSL(true, NULL); // we limit our trust to SSL
NSDictionary *trustSettings = @{
(id)kSecTrustSettingsResult: [NSNumber numberWithInt:kSecTrustSettingsResultTrustRoot],
(id)kSecTrustSettingsPolicy: (__bridge id) policy,
};
status = SecTrustSettingsSetTrustSettings(cert, kSecTrustSettingsDomainUser, (__bridge CFTypeRef)(trustSettings));
CFRelease(policy);
CFRelease(cert);
return status;
//****************************************************************************************************************************************************
/// \brief Create a certificate object from DER-encoded data.
///
/// \return The certifcation. The caller is responsible for releasing the object using CFRelease.
/// \return NULL if data is not a valid DER-encoded certificate.
//****************************************************************************************************************************************************
SecCertificateRef certFromData(char const* data, uint64_t length) {
NSData *der = [NSData dataWithBytes:data length:length];
return SecCertificateCreateWithData(NULL, (__bridge CFDataRef)der);
}
int removeTrustedCert(char const *bytes, unsigned long long length) {
if (0 == length) {
return errSecInvalidData;
}
//****************************************************************************************************************************************************
/// \brief Check if a certificate is in the user's keychain.
///
/// \param[in] cert The certificate.
/// \return true iff the certificate is in the user's keychain.
//****************************************************************************************************************************************************
bool _isCertificateInKeychain(SecCertificateRef const cert) {
NSDictionary *attrs = @{
(id)kSecMatchItemList: @[(__bridge id)cert],
(id)kSecClass: (id)kSecClassCertificate,
(id)kSecReturnData: @YES
};
return errSecSuccess == SecItemCopyMatching((__bridge CFDictionaryRef)attrs, NULL);
}
NSData *der = [NSData dataWithBytes: bytes length: length];
SecCertificateRef cert = SecCertificateCreateWithData(NULL, (CFDataRef) der);
//****************************************************************************************************************************************************
/// \brief Check if a certificate is in the user's keychain.
///
/// \param[in] certData The certificate data in DER encoded format.
/// \param[in] certSize The size of the certData in bytes.
/// \return true iff the certificate is in the user's keychain.
//****************************************************************************************************************************************************
bool isCertificateInKeychain(char const* certData, uint64_t certSize) {
return _isCertificateInKeychain(certFromData(certData, certSize));
}
// Step 1. Unset the trust for the certificate.
SecPolicyRef policy = SecPolicyCreateSSL(true, NULL);
NSDictionary * trustSettings = @{
(id)kSecTrustSettingsResult: [NSNumber numberWithInt:kSecTrustSettingsResultUnspecified],
(id)kSecTrustSettingsPolicy: (__bridge id) policy,
};
OSStatus status = SecTrustSettingsSetTrustSettings(cert, kSecTrustSettingsDomainUser, (__bridge CFTypeRef)(trustSettings));
CFRelease(policy);
if (errSecSuccess != status) {
CFRelease(cert);
return status;
}
// Step 2. Remove the certificate from the keychain.
NSDictionary *query = @{ (id)kSecClass: (id)kSecClassCertificate,
(id)kSecMatchItemList: @[(__bridge id)cert],
(id)kSecMatchLimit: (id)kSecMatchLimitOne,
};
status = SecItemDelete((__bridge CFDictionaryRef) query);
//****************************************************************************************************************************************************
/// \brief Add a certificate to the user's keychain.
///
/// \param[in] cert The certificate.
/// \return The status for the operation.
//****************************************************************************************************************************************************
OSStatus _addCertificateToKeychain(SecCertificateRef const cert) {
NSDictionary* addQuery = @{
(id)kSecValueRef: (__bridge id) cert,
(id)kSecClass: (id)kSecClassCertificate,
};
return SecItemAdd((__bridge CFDictionaryRef) addQuery, NULL);
}
CFRelease(cert);
return status;
//****************************************************************************************************************************************************
/// \brief Add a certificate to the user's keychain.
///
/// \param[in] certData The certificate data in DER encoded format.
/// \param[in] certSize The size of the certData in bytes.
/// \return The status for the operation.
//****************************************************************************************************************************************************
OSStatus addCertificateToKeychain(char const* certData, uint64_t certSize) {
return _addCertificateToKeychain(certFromData(certData, certSize));
}
//****************************************************************************************************************************************************
/// \brief Add a certificate to the user's keychain.
///
/// \param[in] cert The certificate.
/// \return The status for the operation.
//****************************************************************************************************************************************************
OSStatus _removeCertificateFromKeychain(SecCertificateRef const cert) {
NSDictionary *query = @{ (id)kSecClass: (id)kSecClassCertificate,
(id)kSecMatchItemList: @[(__bridge id)cert],
(id)kSecMatchLimit: (id)kSecMatchLimitOne,
};
return SecItemDelete((__bridge CFDictionaryRef) query);
}
//****************************************************************************************************************************************************
/// \brief Add a certificate to the user's keychain.
///
/// \param[in] certData The certificate data in DER encoded format.
/// \param[in] certSize The size of the certData in bytes.
/// \return The status for the operation.
//****************************************************************************************************************************************************
OSStatus removeCertificateFromKeychain(char const* certData, uint64_t certSize) {
return _removeCertificateFromKeychain(certFromData(certData, certSize));
}
//****************************************************************************************************************************************************
/// \brief Check if a certificate is trusted in the user's keychain.
///
/// \param[in] cert The certificate.
/// \return true iff the certificate is trusted in the user's keychain.
//****************************************************************************************************************************************************
bool _isCertificateTrusted(SecCertificateRef const cert) {
CFArrayRef trustSettings = NULL;
OSStatus status = SecTrustSettingsCopyTrustSettings(cert, kSecTrustSettingsDomainUser, &trustSettings);
if (status != errSecSuccess) {
return false;
}
CFIndex count = CFArrayGetCount(trustSettings);
bool result = false;
for (CFIndex index = 0; index < count; ++index) {
CFDictionaryRef dict = (CFDictionaryRef)CFArrayGetValueAtIndex(trustSettings, index);
if (!dict) {
continue;
}
CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(dict, kSecTrustSettingsResult);
int value;
if (num && CFNumberGetValue(num, kCFNumberSInt32Type, &value) && (value == kSecTrustSettingsResultTrustRoot)) {
result = true;
break;
}
}
CFRelease(trustSettings);
return result;
}
//****************************************************************************************************************************************************
/// \brief Check if a certificate is trusted in the user's keychain.
///
/// \param[in] certData The certificate data in DER encoded format.
/// \param[in] certSize The size of the certData in bytes.
/// \return true iff the certificate is trusted in the user's keychain.
//****************************************************************************************************************************************************
bool isCertificateTrusted(char const* certData, uint64_t certSize) {
return _isCertificateTrusted(certFromData(certData, certSize));
}
//****************************************************************************************************************************************************
/// \brief Set the trust level for a certificate in the user's keychain. This call will trigger a security prompt.
///
/// \param[in] cert The certificate.
/// \param[in] trustLevel The trust level.
/// \return The status for the operation.
//****************************************************************************************************************************************************
OSStatus _setCertificateTrustLevel(SecCertificateRef const cert, int trustLevel) {
SecPolicyRef policy = SecPolicyCreateSSL(true, NULL); // we limit our trust to SSL
NSDictionary *trustSettings = @{
(id)kSecTrustSettingsResult: [NSNumber numberWithInt:trustLevel],
(id)kSecTrustSettingsPolicy: (__bridge id) policy,
};
OSStatus status = SecTrustSettingsSetTrustSettings(cert, kSecTrustSettingsDomainUser, (__bridge CFTypeRef)(trustSettings));
CFRelease(policy);
return status;
}
//****************************************************************************************************************************************************
/// \brief Set a certificate as trusted in the user's keychain. This call will trigger a security prompt.
///
/// \param[in] cert The certificate.
/// \return The status for the operation.
//****************************************************************************************************************************************************
OSStatus _setCertificateTrusted(SecCertificateRef cert) {
return _setCertificateTrustLevel(cert, kSecTrustSettingsResultTrustRoot);
}
//****************************************************************************************************************************************************
/// \brief Set a certificate as trusted in the user's keychain. This call will trigger a security prompt.
///
/// \param[in] certData The certificate data in DER encoded format.
/// \param[in] certSize The size of the certData in bytes.
/// \return The status for the operation.
//****************************************************************************************************************************************************
OSStatus setCertificateTrusted(char const* certData, uint64_t certSize) {
return _setCertificateTrusted(certFromData(certData, certSize));
}
//****************************************************************************************************************************************************
/// \brief Remove the trust level of a certificate in the user's keychain.
///
/// \param[in] cert The certificate.
/// \return The status for the operation.
//****************************************************************************************************************************************************
OSStatus _removeCertificateTrust(SecCertificateRef cert) {
return _setCertificateTrustLevel(cert, kSecTrustSettingsResultUnspecified);
}
//****************************************************************************************************************************************************
/// \brief Remove the trust level of a certificate in the user's keychain.
///
/// \param[in] certData The certificate data in DER encoded format.
/// \param[in] certSize The size of the certData in bytes.
/// \return The status for the operation.
//****************************************************************************************************************************************************
OSStatus removeCertificateTrust(char const* certData, uint64_t certSize) {
return _removeCertificateTrust(certFromData(certData, certSize));
}
*/
import "C"
@ -119,6 +248,116 @@ func certPEMToDER(certPEM []byte) ([]byte, error) {
return block.Bytes, nil
}
// wrapCGoCertCallReturningBool wrap call to a CGo function returning a bool.
// if the certificate is invalid the call will return false.
func wrapCGoCertCallReturningBool(certPEM []byte, fn func(*C.char, C.ulonglong) bool) bool {
certDER, err := certPEMToDER(certPEM)
if err != nil {
return false // error are ignored
}
buffer := C.CBytes(certDER)
defer C.free(unsafe.Pointer(buffer)) //nolint:unconvert
return fn((*C.char)(buffer), C.ulonglong(len(certDER)))
}
// wrapCGoCertCallReturningBool wrap call to a CGo function returning an error
func wrapCGoCertCallReturningError(certPEM []byte, fn func(*C.char, C.ulonglong) error) error {
certDER, err := certPEMToDER(certPEM)
if err != nil {
return err
}
buffer := C.CBytes(certDER)
defer C.free(unsafe.Pointer(buffer)) //nolint:unconvert
return fn((*C.char)(buffer), C.ulonglong(len(certDER)))
}
// isCertInKeychain returns true if the given certificate is stored in the user's keychain.
func isCertInKeychain(certPEM []byte) bool {
return wrapCGoCertCallReturningBool(certPEM, isCertInKeychainCGo)
}
func isCertInKeychainCGo(buffer *C.char, size C.ulonglong) bool {
return bool(C.isCertificateInKeychain(buffer, size))
}
// addCertToKeychain adds a certificate to the user's keychain.
// Trying to add a certificate that is already in the keychain will result in an error.
func addCertToKeychain(certPEM []byte) error {
return wrapCGoCertCallReturningError(certPEM, addCertToKeychainCGo)
}
func addCertToKeychainCGo(buffer *C.char, size C.ulonglong) error {
if errCode := C.addCertificateToKeychain(buffer, size); errCode != errSecSuccess {
return fmt.Errorf("could not add certificate to keychain (error %v)", errCode)
}
return nil
}
// removeCertFromKeychain removes a certificate from the user's keychain.
// Trying to remove a certificate that is not in the keychain will result in an error.
func removeCertFromKeychain(certPEM []byte) error {
return wrapCGoCertCallReturningError(certPEM, removeCertFromKeychainCGo)
}
func removeCertFromKeychainCGo(buffer *C.char, size C.ulonglong) error {
if errCode := C.removeCertificateFromKeychain(buffer, size); errCode != errSecSuccess {
return fmt.Errorf("could not remove certificate from keychain (error %v)", errCode)
}
return nil
}
// isCertTrusted check if a certificate is trusted in the user's keychain.
func isCertTrusted(certPEM []byte) bool {
return wrapCGoCertCallReturningBool(certPEM, isCertTrustedCGo)
}
func isCertTrustedCGo(buffer *C.char, size C.ulonglong) bool {
return bool(C.isCertificateTrusted(buffer, size))
}
// setCertTrusted sets a certificate as trusted in the user's keychain.
// This function will trigger a security prompt from the system.
func setCertTrusted(certPEM []byte) error {
return wrapCGoCertCallReturningError(certPEM, setCertTrustedCGo)
}
func setCertTrustedCGo(buffer *C.char, size C.ulonglong) error {
errCode := C.setCertificateTrusted(buffer, size)
switch errCode {
case errSecSuccess:
return nil
case errAuthorizationCanceled:
return ErrUserCanceledCertificateInstall
default:
return fmt.Errorf("could not set certificate trust in keychain (error %v)", errCode)
}
}
// removeCertTrust remove the trust level of the certificated from the user's keychain.
// This function will trigger a security prompt from the system.
func removeCertTrust(certPEM []byte) error {
return wrapCGoCertCallReturningError(certPEM, removeCertTrustCGo)
}
func removeCertTrustCGo(buffer *C.char, size C.ulonglong) error {
errCode := C.removeCertificateTrust(buffer, size)
switch errCode {
case errSecSuccess:
return nil
case errAuthorizationCanceled:
return ErrUserCanceledCertificateInstall
default:
return fmt.Errorf("could not set certificate trust in keychain (error %v)", errCode)
}
}
// installCert installs a certificate in the keychain. The certificate is added to the keychain and it is set as trusted.
// This function will trigger a security prompt from the system, unless the certificate is already trusted in the user keychain.
func installCert(certPEM []byte) error {
certDER, err := certPEMToDER(certPEM)
if err != nil {
@ -127,18 +366,24 @@ func installCert(certPEM []byte) error {
p := C.CBytes(certDER)
defer C.free(unsafe.Pointer(p)) //nolint:unconvert
buffer := (*C.char)(p)
size := C.ulonglong(len(certDER))
errCode := C.installTrustedCert((*C.char)(p), (C.ulonglong)(len(certDER)))
switch errCode {
case errSecSuccess:
return nil
case errAuthorizationCanceled:
return fmt.Errorf("the user cancelled the authorization dialog")
default:
return fmt.Errorf("could not install certification into keychain (error %v)", errCode)
if !isCertInKeychainCGo(buffer, size) {
if err := addCertToKeychainCGo(buffer, size); err != nil {
return err
}
}
if !isCertTrustedCGo(buffer, size) {
return setCertTrustedCGo(buffer, size)
}
return nil
}
// uninstallCert uninstalls a certificate in the keychain. The certificate trust is removed and the certificated is deleted from the keychain.
// This function will trigger a security prompt from the system, unless the certificate is not trusted in the user keychain.
func uninstallCert(certPEM []byte) error {
certDER, err := certPEMToDER(certPEM)
if err != nil {
@ -147,10 +392,32 @@ func uninstallCert(certPEM []byte) error {
p := C.CBytes(certDER)
defer C.free(unsafe.Pointer(p)) //nolint:unconvert
buffer := (*C.char)(p)
size := C.ulonglong(len(certDER))
if errCode := C.removeTrustedCert((*C.char)(p), (C.ulonglong)(len(certDER))); errCode != 0 {
return fmt.Errorf("could not install certificate from keychain (error %v)", errCode)
if isCertTrustedCGo(buffer, size) {
if err := removeCertTrustCGo(buffer, size); err != nil {
return err
}
}
if isCertInKeychainCGo(buffer, size) {
return removeCertFromKeychainCGo(buffer, size)
}
return nil
}
func isCertInstalled(certPEM []byte) bool {
certDER, err := certPEMToDER(certPEM)
if err != nil {
return false
}
p := C.CBytes(certDER)
defer C.free(unsafe.Pointer(p)) //nolint:unconvert
buffer := (*C.char)(p)
size := C.ulonglong(len(certDER))
return isCertInKeychainCGo(buffer, size) && isCertTrustedCGo(buffer, size)
}

View File

@ -25,20 +25,73 @@ import (
"github.com/stretchr/testify/require"
)
// This test implies human interactions to enter password and is disabled by default.
func _TestTrustedCertsDarwin(t *testing.T) { //nolint:unused
func TestCertInKeychain(t *testing.T) {
// no trust settings change is performed, so this test will not trigger an OS security prompt.
certPEM := generatePEMCertificate(t)
require.False(t, isCertInKeychain(certPEM))
require.NoError(t, addCertToKeychain(certPEM))
require.True(t, isCertInKeychain(certPEM))
require.Error(t, addCertToKeychain(certPEM))
require.True(t, isCertInKeychain(certPEM))
require.NoError(t, removeCertFromKeychain(certPEM))
require.False(t, isCertInKeychain(certPEM))
require.Error(t, removeCertFromKeychain(certPEM))
require.False(t, isCertInKeychain(certPEM))
}
// This test require human interaction (macOS security prompts), and is disabled by default.
func _TestCertificateTrust(t *testing.T) { //nolint:unused
certPEM := generatePEMCertificate(t)
require.False(t, isCertTrusted(certPEM))
require.NoError(t, addCertToKeychain(certPEM))
require.NoError(t, setCertTrusted(certPEM))
require.True(t, isCertTrusted(certPEM))
require.NoError(t, removeCertTrust(certPEM))
require.False(t, isCertTrusted(certPEM))
require.NoError(t, removeCertFromKeychain(certPEM))
}
// This test require human interaction (macOS security prompts), and is disabled by default.
func _TestInstallAndRemove(t *testing.T) { //nolint:unused
certPEM := generatePEMCertificate(t)
// fresh install
require.False(t, isCertInstalled(certPEM))
require.NoError(t, installCert(certPEM))
require.True(t, isCertInKeychain(certPEM))
require.True(t, isCertTrusted(certPEM))
require.True(t, isCertInstalled(certPEM))
require.NoError(t, uninstallCert(certPEM))
require.False(t, isCertInKeychain(certPEM))
require.False(t, isCertTrusted(certPEM))
require.False(t, isCertInstalled(certPEM))
// Install where certificate is already in Keychain, but not trusted.
require.NoError(t, addCertToKeychain(certPEM))
require.False(t, isCertInstalled(certPEM))
require.NoError(t, installCert(certPEM))
require.True(t, isCertInstalled(certPEM))
// Install where certificate is already installed
require.NoError(t, installCert(certPEM))
// Remove when certificate is not trusted.
require.NoError(t, removeCertTrust(certPEM))
require.NoError(t, uninstallCert(certPEM))
require.False(t, isCertInstalled(certPEM))
// Remove when certificate has already been removed.
require.NoError(t, uninstallCert(certPEM))
require.False(t, isCertTrusted(certPEM))
require.False(t, isCertInKeychain(certPEM))
}
func generatePEMCertificate(t *testing.T) []byte {
template, err := NewTLSTemplate()
require.NoError(t, err)
certPEM, _, err := GenerateCert(template)
require.NoError(t, err)
require.Error(t, installCert([]byte{0})) // Cannot install an invalid cert.
require.Error(t, uninstallCert(certPEM)) // Cannot uninstall a cert that is not installed.
require.NoError(t, installCert(certPEM)) // Can install a valid cert.
require.NoError(t, installCert(certPEM)) // Can install an already installed cert.
require.NoError(t, uninstallCert(certPEM)) // Can uninstall an installed cert.
require.Error(t, uninstallCert(certPEM)) // Cannot uninstall an already uninstalled cert.
require.NoError(t, installCert(certPEM)) // Can reinstall an uninstalled cert.
require.NoError(t, uninstallCert(certPEM)) // Can uninstall a reinstalled cert.
return certPEM
}

View File

@ -24,3 +24,7 @@ func installCert([]byte) error {
func uninstallCert([]byte) error {
return nil // Linux doesn't have a root cert store.
}
func isCertInstalled([]byte) bool {
return false
}

View File

@ -24,3 +24,7 @@ func installCert([]byte) error {
func uninstallCert([]byte) error {
return nil // NOTE(GODT-986): Uninstall certs from root cert store?
}
func isCertInstalled([]byte) bool {
return false
}

View File

@ -17,16 +17,50 @@
package certs
type Installer struct{}
import (
"errors"
"github.com/sirupsen/logrus"
)
var (
ErrUserCanceledCertificateInstall = errors.New("the user cancelled the authorization dialog")
)
type Installer struct {
log *logrus.Entry
}
func NewInstaller() *Installer {
return &Installer{}
return &Installer{
log: logrus.WithField("pkg", "certs"),
}
}
func (installer *Installer) InstallCert(certPEM []byte) error {
return installCert(certPEM)
installer.log.Info("Installing the Bridge TLS certificate in the OS keychain")
if err := installCert(certPEM); err != nil {
installer.log.WithError(err).Error("The Bridge TLS certificate could not be installed in the OS keychain")
return err
}
installer.log.Info("The Bridge TLS certificate was successfully installed in the OS keychain")
return nil
}
func (installer *Installer) UninstallCert(certPEM []byte) error {
return uninstallCert(certPEM)
installer.log.Info("Uninstalling the Bridge TLS certificate from the OS keychain")
if err := uninstallCert(certPEM); err != nil {
installer.log.WithError(err).Error("The Bridge TLS certificate could not be uninstalled from the OS keychain")
return err
}
installer.log.Info("The Bridge TLS certificate was successfully uninstalled from the OS keychain")
return nil
}
func (installer *Installer) IsCertInstalled(certPEM []byte) bool {
return isCertInstalled(certPEM)
}

View File

@ -42,6 +42,7 @@ void GRPCQtProxy::connectSignals() {
connect(this, &GRPCQtProxy::setIsTelemetryDisabledReceived, &settingsTab, &SettingsTab::setIsTelemetryDisabled);
connect(this, &GRPCQtProxy::setColorSchemeNameReceived, &settingsTab, &SettingsTab::setColorSchemeName);
connect(this, &GRPCQtProxy::reportBugReceived, &settingsTab, &SettingsTab::setBugReport);
connect(this, &GRPCQtProxy::installTLSCertificateReceived, &settingsTab, &SettingsTab::installTLSCertificate);
connect(this, &GRPCQtProxy::exportTLSCertificatesReceived, &settingsTab, &SettingsTab::exportTLSCertificates);
connect(this, &GRPCQtProxy::setIsStreamingReceived, &settingsTab, &SettingsTab::setIsStreaming);
connect(this, &GRPCQtProxy::setClientPlatformReceived, &settingsTab, &SettingsTab::setClientPlatform);
@ -119,6 +120,13 @@ void GRPCQtProxy::reportBug(QString const &osType, QString const &osVersion, QSt
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
void GRPCQtProxy::installTLSCertificate() {
emit installTLSCertificateReceived();
}
//****************************************************************************************************************************************************
/// \param[in] folderPath The folder path.
//****************************************************************************************************************************************************

View File

@ -45,6 +45,7 @@ public: // member functions.
void setColorSchemeName(QString const &name); ///< Forward a SetColorSchemeName call via a Qt Signal
void reportBug(QString const &osType, QString const &osVersion, QString const &emailClient, QString const &address,
QString const &description, bool includeLogs); ///< Forwards a ReportBug call via a Qt signal.
void installTLSCertificate(); ///< Forwards a InstallTLScertificate call via a Qt signal.
void exportTLSCertificates(QString const &folderPath); //< Forward an 'ExportTLSCertificates' call via a Qt signal.
void setIsStreaming(bool isStreaming); ///< Forward a isStreaming internal messages via a Qt signal.
void setClientPlatform(QString const &clientPlatform); ///< Forward a setClientPlatform call via a Qt signal.
@ -67,6 +68,7 @@ signals:
void setColorSchemeNameReceived(QString const &name); ///< Forward a SetColorScheme call via a Qt Signal
void reportBugReceived(QString const &osType, QString const &osVersion, QString const &emailClient, QString const &address,
QString const &description, bool includeLogs); ///< Signal for the ReportBug gRPC call
void installTLSCertificateReceived(); ///< Signal for the InstallTLSCertificate gRPC call.
void exportTLSCertificatesReceived(QString const &folderPath); ///< Signal for the ExportTLSCertificates gRPC call.
void setIsStreamingReceived(bool isStreaming); ///< Signal for the IsStreaming internal message.
void setClientPlatformReceived(QString const &clientPlatform); ///< Signal for the SetClientPlatform gRPC call.

View File

@ -214,6 +214,16 @@ grpc::Status GRPCService::IsTelemetryDisabled(::grpc::ServerContext *, ::google:
}
//****************************************************************************************************************************************************
/// \param[out] response The response.
/// \return The status for the call.
//****************************************************************************************************************************************************
Status GRPCService::GoOs(ServerContext *, Empty const*, StringValue *response) {
response->set_value(app().mainWindow().settingsTab().os().toStdString());
return Status::OK;
}
//****************************************************************************************************************************************************
/// \return The status for the call.
//****************************************************************************************************************************************************
@ -361,22 +371,6 @@ Status GRPCService::ReportBug(ServerContext *, ReportBugRequest const *request,
}
//****************************************************************************************************************************************************
/// \param[in] request The request
//****************************************************************************************************************************************************
Status GRPCService::ExportTLSCertificates(ServerContext *, StringValue const *request, Empty *response) {
SettingsTab &tab = app().mainWindow().settingsTab();
if (!tab.nextTLSCertExportWillSucceed()) {
qtProxy_.sendDelayedEvent(newGenericErrorEvent(grpc::TLS_CERT_EXPORT_ERROR));
}
if (!tab.nextTLSKeyExportWillSucceed()) {
qtProxy_.sendDelayedEvent(newGenericErrorEvent(grpc::TLS_KEY_EXPORT_ERROR));
}
qtProxy_.exportTLSCertificates(QString::fromStdString(request->value()));
return Status::OK;
}
//****************************************************************************************************************************************************
/// \param[in] request The request.
/// \return The status for the call.
@ -406,7 +400,7 @@ Status GRPCService::Login(ServerContext *, LoginRequest const *request, Empty *)
return Status::OK;
}
if (usersTab.nextUserTwoPasswordsRequired()) {
qtProxy_.sendDelayedEvent(newLoginTwoPasswordsRequestedEvent());
qtProxy_.sendDelayedEvent(newLoginTwoPasswordsRequestedEvent(loginUsername_));
return Status::OK;
}
@ -431,7 +425,7 @@ Status GRPCService::Login2FA(ServerContext *, LoginRequest const *request, Empty
return Status::OK;
}
if (usersTab.nextUserTwoPasswordsRequired()) {
qtProxy_.sendDelayedEvent(newLoginTwoPasswordsRequestedEvent());
qtProxy_.sendDelayedEvent(newLoginTwoPasswordsRequestedEvent(loginUsername_));
return Status::OK;
}
@ -758,9 +752,86 @@ Status GRPCService::ConfigureUserAppleMail(ServerContext *, ConfigureAppleMailRe
//****************************************************************************************************************************************************
/// \param[in] request The request
/// \param[in] writer The writer
/// \return The status for the call.
//****************************************************************************************************************************************************
Status GRPCService::ExportTLSCertificates(ServerContext *, StringValue const *request, Empty *response) {
app().log().debug(__FUNCTION__);
SettingsTab &tab = app().mainWindow().settingsTab();
if (!tab.nextTLSCertExportWillSucceed()) {
qtProxy_.sendDelayedEvent(newGenericErrorEvent(grpc::TLS_CERT_EXPORT_ERROR));
}
if (!tab.nextTLSKeyExportWillSucceed()) {
qtProxy_.sendDelayedEvent(newGenericErrorEvent(grpc::TLS_KEY_EXPORT_ERROR));
}
qtProxy_.exportTLSCertificates(QString::fromStdString(request->value()));
return Status::OK;
}
//****************************************************************************************************************************************************
/// \param[in] response The reponse.
/// \return The status for the call.
//****************************************************************************************************************************************************
Status GRPCService::IsTLSCertificateInstalled(ServerContext *, const Empty *request, BoolValue *response) {
app().log().debug(__FUNCTION__);
response->set_value(app().mainWindow().settingsTab().isTLSCertificateInstalled());
return Status::OK;
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
Status GRPCService::InstallTLSCertificate(ServerContext *, Empty const *, Empty *) {
app().log().debug(__FUNCTION__);
SPStreamEvent event;
qtProxy_.installTLSCertificate();
switch (app().mainWindow().settingsTab().nextTLSCertIntallResult()) {
case SettingsTab::TLSCertInstallResult::Success:
event = newCertificateInstallSuccessEvent();
break;
case SettingsTab::TLSCertInstallResult::Canceled:
event = newCertificateInstallCanceledEvent();
break;
default:
event = newCertificateInstallFailedEvent();
break;
}
qtProxy_.sendDelayedEvent(event);
return Status::OK;
}
//****************************************************************************************************************************************************
/// \param[in] request The request.
//****************************************************************************************************************************************************
Status GRPCService::KBArticleClicked(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) {
app().log().debug(QString("%1 - URL = %2").arg(__FUNCTION__, QString::fromStdString(request->value())));
return Status::OK;
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
Status GRPCService::ReportBugClicked(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::Empty *) {
app().log().debug(__FUNCTION__);
return Status::OK;
}
//****************************************************************************************************************************************************
/// \param[in] request The request.
//****************************************************************************************************************************************************
Status GRPCService::AutoconfigClicked(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *response) {
app().log().debug(QString("%1 - Client = %2").arg(__FUNCTION__, QString::fromStdString(request->value())));
return Status::OK;
}
//****************************************************************************************************************************************************
/// \param[in] request The request
/// \param[in] writer The writer
//****************************************************************************************************************************************************
Status GRPCService::RunEventStream(ServerContext *ctx, EventStreamRequest const *request, ServerWriter<StreamEvent> *writer) {
app().log().debug(__FUNCTION__);
{
@ -850,4 +921,3 @@ void GRPCService::finishLogin() {
qtProxy_.sendDelayedEvent(newLoginFinishedEvent(user->id(), alreadyExist));
}

View File

@ -53,6 +53,7 @@ public: // member functions.
grpc::Status IsAllMailVisible(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::BoolValue *response) override;
grpc::Status SetIsTelemetryDisabled(::grpc::ServerContext *, ::google::protobuf::BoolValue const *request, ::google::protobuf::Empty *response) override;
grpc::Status IsTelemetryDisabled(::grpc::ServerContext *, ::google::protobuf::Empty const *request, ::google::protobuf::BoolValue *response) override;
grpc::Status GoOs(::grpc::ServerContext *, ::google::protobuf::Empty const*, ::google::protobuf::StringValue *response) override;
grpc::Status TriggerReset(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::Empty *) override;
grpc::Status Version(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::StringValue *response) override;
grpc::Status LogsPath(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::StringValue *response) override;
@ -64,7 +65,6 @@ public: // member functions.
grpc::Status ColorSchemeName(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::StringValue *response) override;
grpc::Status CurrentEmailClient(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::StringValue *response) override;
grpc::Status ReportBug(::grpc::ServerContext *, ::grpc::ReportBugRequest const *request, ::google::protobuf::Empty *) override;
grpc::Status ExportTLSCertificates(::grpc::ServerContext *context, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *response) override;
grpc::Status ForceLauncher(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override;
grpc::Status SetMainExecutable(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override;
grpc::Status Login(::grpc::ServerContext *, ::grpc::LoginRequest const *request, ::google::protobuf::Empty *) override;
@ -93,6 +93,12 @@ public: // member functions.
grpc::Status LogoutUser(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override;
grpc::Status RemoveUser(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override;
grpc::Status ConfigureUserAppleMail(::grpc::ServerContext *, ::grpc::ConfigureAppleMailRequest const *request, ::google::protobuf::Empty *) override;
grpc::Status IsTLSCertificateInstalled(::grpc::ServerContext *, ::google::protobuf::Empty const*, ::google::protobuf::BoolValue *response) override;
grpc::Status InstallTLSCertificate(::grpc::ServerContext *, ::google::protobuf::Empty const*, ::google::protobuf::Empty *) override;
grpc::Status ExportTLSCertificates(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override;
grpc::Status ReportBugClicked(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::Empty *) override;
grpc::Status AutoconfigClicked(::grpc::ServerContext *context, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override;
grpc::Status KBArticleClicked(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override;
grpc::Status RunEventStream(::grpc::ServerContext *ctx, ::grpc::EventStreamRequest const *request, ::grpc::ServerWriter<::grpc::StreamEvent> *writer) override;
grpc::Status StopEventStream(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::Empty *) override;
bool sendEvent(bridgepp::SPStreamEvent const &event); ///< Queue an event for sending through the event stream.

View File

@ -285,11 +285,20 @@ void SettingsTab::setBugReport(QString const &osType, QString const &osVersion,
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
void SettingsTab::installTLSCertificate() {
ui_.labelLastTLSCertInstall->setText(QString("Last install: %1").arg(QDateTime::currentDateTime().toString(Qt::ISODateWithMs)));
ui_.checkTLSCertIsInstalled->setChecked(this->nextTLSCertIntallResult() == TLSCertInstallResult::Success);
}
//****************************************************************************************************************************************************
/// \param[in] folderPath The folder path.
//****************************************************************************************************************************************************
void SettingsTab::exportTLSCertificates(QString const &folderPath) {
ui_.labeLastTLSCertsExport->setText(QString("%1 Export to %2")
ui_.labeLastTLSCertExport->setText(QString("%1 Export to %2")
.arg(QDateTime::currentDateTime().toString(Qt::ISODateWithMs))
.arg(folderPath));
}
@ -303,6 +312,22 @@ bool SettingsTab::nextBugReportWillSucceed() const {
}
//****************************************************************************************************************************************************
/// \return the state of the 'TLS Certificate is installed' check box.
//****************************************************************************************************************************************************
bool SettingsTab::isTLSCertificateInstalled() const {
return ui_.checkTLSCertIsInstalled->isChecked();
}
//****************************************************************************************************************************************************
/// \return The value for the 'Next TLS cert install result'.
//****************************************************************************************************************************************************
SettingsTab::TLSCertInstallResult SettingsTab::nextTLSCertIntallResult() const {
return TLSCertInstallResult(ui_.comboNextTLSCertInstallResult->currentIndex());
}
//****************************************************************************************************************************************************
/// \return true if the 'Next TLS key export will succeed' check box is checked
//****************************************************************************************************************************************************
@ -505,4 +530,11 @@ void SettingsTab::resetUI() {
ui_.comboCacheError->setCurrentIndex(0);
ui_.checkAutomaticUpdate->setChecked(true);
ui_.checkTLSCertIsInstalled->setChecked(false);
ui_.comboNextTLSCertInstallResult->setCurrentIndex(0);
ui_.checkTLSCertExportWillSucceed->setChecked(true);
ui_.checkTLSKeyExportWillSucceed->setChecked(true);
ui_.labeLastTLSCertExport->setText("Last export: never");
ui_.labelLastTLSCertInstall->setText("Last install: never");
}

View File

@ -28,6 +28,13 @@
//****************************************************************************************************************************************************
class SettingsTab : public QWidget {
Q_OBJECT
public: // data types.
enum class TLSCertInstallResult {
Success = 0,
Canceled = 1,
Failure = 2
}; ///< Enumberation for the result of a TLS certificate installation.
public: // member functions.
explicit SettingsTab(QWidget *parent = nullptr); ///< Default constructor.
SettingsTab(SettingsTab const &) = delete; ///< Disabled copy-constructor.
@ -54,6 +61,8 @@ public: // member functions.
QString dependencyLicenseLink() const; ///< Get the content of the 'Dependency License Link' edit.
QString landingPageLink() const; ///< Get the content of the 'Landing Page Link' edit.
bool nextBugReportWillSucceed() const; ///< Get the status of the 'Next Bug Report Will Fail' check box.
bool isTLSCertificateInstalled() const; ///< Get the status of the 'TLS Certificate is installed' check box.
TLSCertInstallResult nextTLSCertIntallResult() const; ///< Get the value of the 'Next TLS Certificate install result' combo box.
bool nextTLSCertExportWillSucceed() const; ///< Get the status of the 'Next TLS Cert export will succeed' check box.
bool nextTLSKeyExportWillSucceed() const; ///< Get the status of the 'Next TLS Key export will succeed' check box.
QString hostname() const; ///< Get the value of the 'Hostname' edit.
@ -79,6 +88,7 @@ public slots:
void setColorSchemeName(QString const &name); ///< Set the value for the 'Use Dark Theme' check box.
void setBugReport(QString const &osType, QString const &osVersion, QString const &emailClient, QString const &address, QString const &description,
bool includeLogs); ///< Set the content of the bug report box.
void installTLSCertificate(); ///< Install the TLS certificate.
void exportTLSCertificates(QString const &folderPath); ///< Export the TLS certificates.
void setMailServerSettings(qint32 imapPort, qint32 smtpPort, bool useSSLForIMAP, bool useSSLForSMTP); ///< Change the mail server settings.
void setIsDoHEnabled(bool enabled); ///< Set the value for the 'DoH Enabled' check box.

View File

@ -370,7 +370,7 @@
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupCache_2">
<widget class="QGroupBox" name="groupCert">
<property name="minimumSize">
<size>
<width>0</width>
@ -380,34 +380,81 @@
<property name="title">
<string>TLS Certficates</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_9">
<item>
<widget class="QLabel" name="labeLastTLSCertsExport">
<layout class="QGridLayout" name="gridLayout_4" columnstretch="1,1">
<item row="0" column="0">
<widget class="QCheckBox" name="checkTLSCertIsInstalled">
<property name="text">
<string>Last Export: Never</string>
<string>Certificate is installed</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<item row="0" column="1">
<widget class="QCheckBox" name="checkTLSCertExportWillSucceed">
<property name="text">
<string>TLS certificate export will succeed</string>
<string>Certificate export will succeed</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<item row="1" column="1">
<widget class="QCheckBox" name="checkTLSKeyExportWillSucceed">
<property name="text">
<string>TLS private key export will succeed</string>
<string>Key export will succeed</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="labeLastTLSCertExport">
<property name="text">
<string>Last Export: never</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelLastTLSCertInstall">
<property name="text">
<string>Last install: never</string>
</property>
</widget>
</item>
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_14" stretch="0,1">
<item>
<widget class="QLabel" name="labelNextInstall">
<property name="text">
<string>Next install will</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboNextTLSCertInstallResult">
<item>
<property name="text">
<string>Succeed</string>
</property>
</item>
<item>
<property name="text">
<string>Be Canceled</string>
</property>
</item>
<item>
<property name="text">
<string>Fail</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>

View File

@ -40,7 +40,8 @@ using namespace bridgepp;
namespace {
QString const bugReportFile = ":qml/Resources/bug_report_flow.json";
QString const bugReportFile = ":qml/Resources/bug_report_flow.json";
QString const bridgeKBUrl = "https://proton.me/support/bridge"; ///< The URL for the root of the bridge knowledge base.
}
@ -278,6 +279,30 @@ void QMLBackend::clearAnswers() {
}
//****************************************************************************************************************************************************
/// \return true iff the Bridge TLS certificate is installed.
//****************************************************************************************************************************************************
bool QMLBackend::isTLSCertificateInstalled() {
HANDLE_EXCEPTION_RETURN_BOOL(
bool v = false;
app().grpc().isTLSCertificateInstalled(v);
return v;
)
}
//****************************************************************************************************************************************************
/// \param[in] url The URL of the knowledge base article. If empty/invalid, the home page for the Bridge knowledge base is opened.
//****************************************************************************************************************************************************
void QMLBackend::openKBArticle(QString const &url) {
HANDLE_EXCEPTION(
QString const u = url.isEmpty() ? bridgeKBUrl : url;
QDesktopServices::openUrl(u);
emit notifyKBArticleClicked(u);
)
}
//****************************************************************************************************************************************************
/// \return The value for the 'showOnStartup' property.
//****************************************************************************************************************************************************
@ -941,6 +966,15 @@ void QMLBackend::reportBug(QString const &category, QString const &description,
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
void QMLBackend::installTLSCertificate() {
HANDLE_EXCEPTION(
app().grpc().installTLSCertificate();
)
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
@ -1267,6 +1301,9 @@ void QMLBackend::connectGrpcEvents() {
connect(client, &GRPCClient::reportBugSuccess, this, &QMLBackend::bugReportSendSuccess);
connect(client, &GRPCClient::reportBugFallback, this, &QMLBackend::bugReportSendFallback);
connect(client, &GRPCClient::reportBugError, this, &QMLBackend::bugReportSendError);
connect(client, &GRPCClient::certificateInstallSuccess, this, &QMLBackend::certificateInstallSuccess);
connect(client, &GRPCClient::certificateInstallCanceled, this, &QMLBackend::certificateInstallCanceled);
connect(client, &GRPCClient::certificateInstallFailed, this, &QMLBackend::certificateInstallFailed);
connect(client, &GRPCClient::showMainWindow, [&]() { this->showMainWindow("gRPC showMainWindow event"); });
// cache events

View File

@ -64,6 +64,8 @@ public: // member functions.
Q_INVOKABLE QString getQuestionAnswer(quint8 questionId) const; ///< Get the answer for a given question.
Q_INVOKABLE QString collectAnswers(quint8 categoryId) const; ///< Collect answer for a given set of questions.
Q_INVOKABLE void clearAnswers(); ///< Clear all collected answers.
Q_INVOKABLE bool isTLSCertificateInstalled(); ///< Check if the bridge certificate is installed in the OS keychain.
Q_INVOKABLE void openKBArticle(QString const & url = QString()); ///< Open a knowledge base article.
public: // Qt/QML properties. Note that the NOTIFY-er signal is required even for read-only properties (QML warning otherwise)
Q_PROPERTY(bool showOnStartup READ showOnStartup NOTIFY showOnStartupChanged)
@ -195,6 +197,7 @@ public slots: // slot for signals received from QML -> To be forwarded to Bridge
void installUpdate() const; ///< Slot for the update install.
void triggerReset() const; ///< Slot for the triggering of reset.
void reportBug(QString const &category, QString const &description, QString const &address, QString const &emailClient, bool includeLogs) const; ///< Slot for the bug report.
void installTLSCertificate(); ///< Installs the Bridge TLS certificate in the Keychain.
void exportTLSCertificates() const; ///< Slot for the export of the TLS certificates.
void onResetFinished(); ///< Slot for the reset finish signal.
void onVersionChanged(); ///< Slot for the version change signal.
@ -231,7 +234,7 @@ signals: // Signals received from the Go backend, to be forwarded to QML
void login2FARequested(QString const &username); ///< Signal for the 'login2FARequested' gRPC stream event.
void login2FAError(QString const &errorMsg); ///< Signal for the 'login2FAError' gRPC stream event.
void login2FAErrorAbort(QString const &errorMsg); ///< Signal for the 'login2FAErrorAbort' gRPC stream event.
void login2PasswordRequested(); ///< Signal for the 'login2PasswordRequested' gRPC stream event.
void login2PasswordRequested(QString const &username); ///< Signal for the 'login2PasswordRequested' gRPC stream event.
void login2PasswordError(QString const &errorMsg); ///< Signal for the 'login2PasswordError' gRPC stream event.
void login2PasswordErrorAbort(QString const &errorMsg); ///< Signal for the 'login2PasswordErrorAbort' gRPC stream event.
void loginFinished(int index, bool wasSignedOut); ///< Signal for the 'loginFinished' gRPC stream event.
@ -268,10 +271,13 @@ signals: // Signals received from the Go backend, to be forwarded to QML
void bugReportSendSuccess(); ///< Signal for the 'bugReportSendSuccess' gRPC stream event.
void bugReportSendFallback(); ///< Signal for the 'bugReportSendFallback' gRPC stream event.
void bugReportSendError(); ///< Signal for the 'bugReportSendError' gRPC stream event.
void certificateInstallSuccess(); ///< Signal for the 'certificateInstallSuccess' gRPC stream event.
void certificateInstallCanceled(); ///< Signal for the 'certificateInstallCanceled' gRPC stream event.
void certificateInstallFailed(); /// Signal for the 'certificateInstallFailed' gRPC stream event.
void showMainWindow(); ///< Signal for the 'showMainWindow' gRPC stream event.
void hideMainWindow(); ///< Signal for the 'hideMainWindow' gRPC stream event.
void showHelp(); ///< Signal for the 'showHelp' event (from the context menu).
void showSettings(); ///< Signal for the 'showHelp' event (from the context menu).
void showSettings(); ///< Signal for the 'showSettings' event (from the context menu).
void selectUser(QString const& userID, bool forceShowWindow); ///< Signal emitted in order to selected a user with a given ID in the list.
void genericError(QString const &title, QString const &description); ///< Signal for the 'genericError' gRPC stream event.
void imapLoginWhileSignedOut(QString const& username); ///< Signal for the notification of IMAP login attempt on a signed out account.

View File

@ -5,7 +5,6 @@
<file>qml/AccountView.qml</file>
<file>qml/Banner.qml</file>
<file>qml/Bridge.qml</file>
<file>qml/bridgeqml.qmlproject</file>
<file>qml/BugCategoryView.qml</file>
<file>qml/BugQuestionView.qml</file>
<file>qml/BugReportFlow.qml</file>
@ -20,9 +19,11 @@
<file>qml/icons/ic-alert.svg</file>
<file>qml/icons/ic-apple-mail.svg</file>
<file>qml/icons/ic-arrow-left.svg</file>
<file>qml/icons/ic-bridge.svg</file>
<file>qml/icons/ic-card-identity.svg</file>
<file>qml/icons/ic-check.svg</file>
<file>qml/icons/ic-chevron-down.svg</file>
<file>qml/icons/ic-chevron-left.svg</file>
<file>qml/icons/ic-chevron-right.svg</file>
<file>qml/icons/ic-chevron-up.svg</file>
<file>qml/icons/ic-cog-wheel.svg</file>
@ -49,13 +50,18 @@
<file>qml/icons/ic-success.svg</file>
<file>qml/icons/ic-three-dots-vertical.svg</file>
<file>qml/icons/ic-trash.svg</file>
<file>qml/icons/ic-warning-orange.svg</file>
<file>qml/icons/img-client-config-selector.svg</file>
<file>qml/icons/img-client-config-success.svg</file>
<file>qml/icons/img-macos-cert-screenshot.png</file>
<file>qml/icons/img-macos-profile-screenshot.png</file>
<file>qml/icons/img-mail-clients.svg</file>
<file>qml/icons/img-mail-logo-wordmark-dark.svg</file>
<file>qml/icons/img-mail-logo-wordmark.svg</file>
<file>qml/icons/img-proton-logos.png</file>
<file>qml/icons/img-proton-logos.svg</file>
<file>qml/icons/img-splash.png</file>
<file>qml/icons/img-splash.svg</file>
<file>qml/icons/img-welcome-dark.png</file>
<file>qml/icons/img-welcome-dark.svg</file>
<file>qml/icons/img-welcome.png</file>
<file>qml/icons/img-welcome.svg</file>
<file>qml/icons/Loader_16.svg</file>
<file>qml/icons/Loader_48.svg</file>
@ -75,6 +81,7 @@
<file>qml/KeychainSettings.qml</file>
<file>qml/LocalCacheSettings.qml</file>
<file>qml/MainWindow.qml</file>
<file>qml/NoAccountView.qml</file>
<file>qml/NotificationDialog.qml</file>
<file>qml/NotificationPopups.qml</file>
<file>qml/Notifications/Notification.qml</file>
@ -90,6 +97,7 @@
<file>qml/Proton/ComboBox.qml</file>
<file>qml/Proton/Dialog.qml</file>
<file>qml/Proton/Label.qml</file>
<file>qml/Proton/LinkLabel.qml</file>
<file>qml/Proton/Menu.qml</file>
<file>qml/Proton/MenuItem.qml</file>
<file>qml/Proton/Popup.qml</file>
@ -101,14 +109,26 @@
<file>qml/Proton/TextField.qml</file>
<file>qml/Proton/Toggle.qml</file>
<file>qml/QuestionItem.qml</file>
<file>qml/Resources/bug_report_flow.json</file>
<file>qml/Resources/bug_report_flow.json</file>
<file>qml/Resources/Help/Template.html</file>
<file>qml/Resources/Help/WhyBridge.html</file>
<file>qml/Resources/Help/WhyCertificate.html</file>
<file>qml/Resources/Help/WhyProfileWarning.html</file>
<file>qml/SettingsItem.qml</file>
<file>qml/SettingsView.qml</file>
<file>qml/SetupGuide.qml</file>
<file>qml/SignIn.qml</file>
<file>qml/SetupWizard/ClientListItem.qml</file>
<file>qml/SetupWizard/LeftPane.qml</file>
<file>qml/SetupWizard/ClientConfigAppleMail.qml</file>
<file>qml/SetupWizard/ClientConfigEnd.qml</file>
<file>qml/SetupWizard/ClientConfigParameters.qml</file>
<file>qml/SetupWizard/ClientConfigSelector.qml</file>
<file>qml/SetupWizard/HelpButton.qml</file>
<file>qml/SetupWizard/SetupWizard.qml</file>
<file>qml/SetupWizard/Login.qml</file>
<file>qml/SetupWizard/Onboarding.qml</file>
<file>qml/SetupWizard/StepDescriptionBox.qml</file>
<file>qml/ConnectionModeSettings.qml</file>
<file>qml/SplashScreen.qml</file>
<file>qml/Status.qml</file>
<file>qml/WelcomeGuide.qml</file>
</qresource>
</RCC>

View File

@ -262,7 +262,7 @@ void UserList::onUsedBytesChanged(QString const &userID, qint64 usedBytes) {
void UserList::onSyncStarted(QString const &userID) {
int const index = this->rowOfUserID(userID);
if (index < 0) {
app().log().error(QString("Received onSyncStarted event for unknown userID %1").arg(userID));
app().log().error(QString("Received syncStarted event for unknown userID %1").arg(userID));
return;
}
users_[index]->setIsSyncing(true);
@ -275,7 +275,7 @@ void UserList::onSyncStarted(QString const &userID) {
void UserList::onSyncFinished(QString const &userID) {
int const index = this->rowOfUserID(userID);
if (index < 0) {
app().log().error(QString("Received onSyncFinished event for unknown userID %1").arg(userID));
app().log().error(QString("Received syncFinished event for unknown userID %1").arg(userID));
return;
}
users_[index]->setIsSyncing(false);
@ -293,7 +293,7 @@ void UserList::onSyncProgress(QString const &userID, double progress, float elap
Q_UNUSED(remainingMs)
int const index = this->rowOfUserID(userID);
if (index < 0) {
app().log().error(QString("Received onSyncFinished event for unknown userID %1").arg(userID));
app().log().error(QString("Received syncProgress event for unknown userID %1").arg(userID));
return;
}
users_[index]->setSyncProgress(progress);

View File

@ -22,7 +22,7 @@ Item {
LargeView
}
property var _spacing: 12 * ProtonStyle.px
property var _spacing: 12
property ColorScheme colorScheme
property color progressColor: {
if (!root.enabled)
@ -154,7 +154,7 @@ Item {
}
}
Item {
implicitHeight: root.type === AccountDelegate.LargeView ? 6 * ProtonStyle.px : 0
implicitHeight: root.type === AccountDelegate.LargeView ? 6 : 0
}
RowLayout {
spacing: 0
@ -222,15 +222,15 @@ Item {
}
}
Item {
implicitHeight: root.type === AccountDelegate.LargeView ? 3 * ProtonStyle.px : 0
implicitHeight: root.type === AccountDelegate.LargeView ? 3 : 0
}
Rectangle {
id: progress_bar
color: root.colorScheme.border_weak
height: 4 * ProtonStyle.px
height: 4
radius: ProtonStyle.progress_bar_radius
visible: root.user ? root.type === AccountDelegate.LargeView : false
width: 140 * ProtonStyle.px
width: 140
Rectangle {
id: progress_bar_filled

View File

@ -23,13 +23,14 @@ Item {
property int _detailsMargin: 25
property int _lineThickness: 1
property int _spacing: 20
property int _buttonSpacing: 8
property int _topMargin: 32
property ColorScheme colorScheme
property var notifications
property var user
signal showSetupGuide(var user, string address)
signal showSignIn
signal showClientConfigurator(var user, string address, bool justLoggedIn)
signal showLogin(var username)
Rectangle {
anchors.fill: parent
@ -63,7 +64,7 @@ Item {
// account delegate with action buttons
Layout.fillWidth: true
Layout.topMargin: _topMargin
spacing: _buttonSpacing
AccountDelegate {
Layout.fillWidth: true
colorScheme: root.colorScheme
@ -92,9 +93,9 @@ Item {
visible: root.user ? (root.user.state === EUserState.SignedOut) : false
onClicked: {
if (!root.user)
return;
root.showSignIn();
if (user) {
root.showLogin(user.primaryEmailOrUsername());
}
}
}
Button {
@ -118,18 +119,18 @@ Item {
}
SettingsItem {
Layout.fillWidth: true
actionText: qsTr("Configure")
actionText: qsTr("Configure email client")
colorScheme: root.colorScheme
description: qsTr("Using the mailbox details below (re)configure your client.")
showSeparator: splitMode.visible
text: qsTr("Email clients")
type: SettingsItem.Button
visible: _connected && (!root.user.splitMode) || (root.user.addresses.length === 1)
type: SettingsItem.PrimaryButton
visible: _connected && ((!root.user.splitMode) || (root.user.addresses.length === 1))
onClicked: {
if (!root.user)
return;
root.showSetupGuide(root.user, user.addresses[0]);
root.showClientConfigurator(root.user, user.addresses[0], false);
}
}
SettingsItem {
@ -165,13 +166,13 @@ Item {
}
Button {
colorScheme: root.colorScheme
secondary: true
text: qsTr("Configure")
secondary: false
text: qsTr("Configure email client")
onClicked: {
if (!root.user)
return;
root.showSetupGuide(root.user, addressSelector.displayText);
root.showClientConfigurator(root.user, addressSelector.displayText, false);
}
}
}

View File

@ -40,7 +40,6 @@ QtObject {
function onHideMainWindow() {
mainWindow.hide();
}
target: Backend
}
}

View File

@ -21,6 +21,7 @@ Rectangle {
property int _margin: 24
property ColorScheme colorScheme
property bool highlightPassword
property string hostname
property string password
property string port
@ -68,7 +69,8 @@ Rectangle {
}
ConfigurationItem {
colorScheme: root.colorScheme
label: qsTr("Password")
label: highlightPassword ? qsTr("Use this password") : qsTr("Password")
labelColor: highlightPassword ? colorScheme.signal_warning_active : colorScheme.text_norm
value: root.password
}
ConfigurationItem {

View File

@ -21,6 +21,7 @@ Item {
property var colorScheme
property string label
property string labelColor: root.colorScheme.text_norm
property string value
Layout.fillWidth: true
@ -35,9 +36,10 @@ Item {
ColumnLayout {
Label {
color: labelColor
colorScheme: root.colorScheme
text: root.label
type: Label.Body
type: Label.Body_semibold
}
TextEdit {
id: valueText

View File

@ -24,7 +24,8 @@ Item {
signal closeWindow
signal quitBridge
signal showSetupGuide(var user, string address)
signal showClientConfigurator(var user, string address, bool justLoggedIn)
signal showLogin(var username)
function selectUser(userID) {
const users = Backend.users;
@ -35,11 +36,14 @@ Item {
}
accounts.currentIndex = i;
if (user.state === EUserState.SignedOut)
showSignIn(user.primaryEmailOrUsername());
showLogin(user.primaryEmailOrUsername());
return;
}
console.error("User with ID ", userID, " was not found in the account list");
}
function showBugReport() {
rightContent.showBugReport();
}
function showHelp() {
rightContent.showHelpView();
}
@ -49,9 +53,9 @@ Item {
function showSettings() {
rightContent.showGeneralSettings();
}
function showSignIn(username) {
signIn.username = username;
rightContent.showSignIn();
function hasAccount() {
return Backend.users.count > 0
}
RowLayout {
@ -190,6 +194,41 @@ Item {
Layout.minimumHeight: 1
color: leftBar.colorScheme.border_weak
}
Item {
id: noAccountBox
Layout.fillHeight: true
Layout.fillWidth: true
Layout.topMargin: 24
visible: !hasAccount()
ColumnLayout {
anchors.fill: parent
spacing: 8
Label {
colorScheme: leftBar.colorScheme
color: colorScheme.text_weak
Layout.alignment: Qt.AlignHCenter
text: qsTr("No accounts")
}
Button {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
colorScheme: leftBar.colorScheme
text: qsTr("Add an account")
secondary: true
onClicked: root.showLogin("")
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
}
}
ListView {
id: accounts
@ -206,7 +245,7 @@ Item {
clip: true
model: Backend.users
spacing: 12
visible: hasAccount()
delegate: Item {
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
@ -233,8 +272,7 @@ Item {
if (user.state !== EUserState.SignedOut) {
rightContent.showAccount();
} else {
signIn.username = user.primaryEmailOrUsername();
rightContent.showSignIn();
showLogin(user.primaryEmailOrUsername());
}
}
}
@ -282,8 +320,7 @@ Item {
width: 36
onClicked: {
signIn.username = "";
rightContent.showSignIn();
root.showLogin("");
}
}
}
@ -323,65 +360,42 @@ Item {
function showPortSettings() {
rightContent.currentIndex = 4;
}
function showSignIn() {
rightContent.currentIndex = 1;
signIn.focus = true;
}
anchors.fill: parent
AccountView {
StackLayout {
// 0
colorScheme: root.colorScheme
notifications: root.notifications
user: {
if (accounts.currentIndex < 0)
return undefined;
if (Backend.users.count === 0)
return undefined;
return Backend.users.get(accounts.currentIndex);
}
onShowSetupGuide: function (user, address) {
root.showSetupGuide(user, address);
}
onShowSignIn: {
const user = this.user;
signIn.username = user ? user.primaryEmailOrUsername() : "";
rightContent.showSignIn();
}
}
GridLayout {
// 1 Sign In
columns: 2
Button {
id: backButton
Layout.alignment: Qt.AlignTop
Layout.leftMargin: 18
Layout.topMargin: 10
currentIndex: hasAccount() ? 1 : 0
NoAccountView {
colorScheme: root.colorScheme
horizontalPadding: 8
icon.source: "/qml/icons/ic-arrow-left.svg"
secondary: true
onClicked: {
signIn.abort();
rightContent.showAccount();
onLinkClicked: function() {
root.showLogin("")
}
}
SignIn {
id: signIn
Layout.bottomMargin: 68
Layout.fillHeight: true
Layout.fillWidth: true
Layout.leftMargin: 80 - backButton.width - 18
Layout.preferredWidth: 320
Layout.rightMargin: 80
Layout.topMargin: 68
AccountView {
colorScheme: root.colorScheme
notifications: root.notifications
user: {
if (accounts.currentIndex < 0)
return undefined;
if (Backend.users.count === 0)
return undefined;
return Backend.users.get(accounts.currentIndex);
}
onShowClientConfigurator: function (user, address, justLoggedIn) {
root.showClientConfigurator(user, address, justLoggedIn);
}
onShowLogin: function (username) {
root.showLogin(username);
}
}
}
Rectangle {
Layout.fillHeight: true
Layout.fillWidth: true
color: "#ff9900"
}
GeneralSettings {
// 2
colorScheme: root.colorScheme

View File

@ -36,8 +36,7 @@ SettingsView {
type: SettingsItem.PrimaryButton
onClicked: {
Backend.notifyKBArticleClicked("https://proton.me/support/bridge");
Qt.openUrlExternally("https://proton.me/support/bridge");
Backend.openKBArticle();
}
}
SettingsItem {
@ -104,7 +103,7 @@ SettingsView {
type: Label.Caption
onLinkActivated: function (link) {
Qt.openUrlExternally(link);
Qt.openUrlExternally(link)
}
}
}

View File

@ -17,14 +17,29 @@ import QtQuick.Layouts
import QtQuick.Controls
import Proton
import Notifications
import "SetupWizard"
ApplicationWindow {
id: root
property int _defaultHeight: 780
property int _defaultWidth: 1080
property var notifications
function layoutForUserCount(userCount) {
if (userCount === 0) {
contentLayout.currentIndex = 1;
setupWizard.showOnboarding();
return;
}
const u = Backend.users.get(0);
if (!u) {
console.trace();
return;
}
if ((userCount === 1) && (u.state === EUserState.SignedOut)) {
contentLayout.currentIndex = 1;
setupWizard.showLogin(u.primaryEmailOrUsername());
}
}
function selectUser(userID) {
contentWrapper.selectUser(userID);
}
@ -35,42 +50,42 @@ ApplicationWindow {
root.requestActivate();
}
}
function showClientConfigurator(user, address, justLoggedIn) {
contentLayout.currentIndex = 1;
setupWizard.showClientConfig(user, address, justLoggedIn);
}
function showHelp() {
contentWrapper.showHelp();
}
function showLocalCacheSettings() {
contentWrapper.showLocalCacheSettings();
}
function showLogin(username = "") {
contentLayout.currentIndex = 1;
setupWizard.showLogin(username);
}
function showSettings() {
contentWrapper.showSettings();
}
function showSetup(user, address) {
setupGuide.user = user;
setupGuide.address = address;
setupGuide.reset();
contentLayout._showSetup = !!setupGuide.user;
}
function showSignIn(username) {
if (contentLayout.currentIndex === 1)
return;
contentWrapper.showSignIn(username);
}
colorScheme: ProtonStyle.currentStyle
height: _defaultHeight
minimumWidth: _defaultWidth
height: ProtonStyle.window_default_height
minimumHeight:ProtonStyle.window_minimum_height
minimumWidth: ProtonStyle.window_minimum_width
visible: true
width: _defaultWidth
width: ProtonStyle.window_default_width
Component.onCompleted: {
layoutForUserCount(Backend.users.count);
}
// show Setup Guide on every new user
Connections {
function onRowsAboutToBeRemoved(parent, first, last) {
for (let i = first; i <= last; i++) {
const user = Backend.users.get(i);
if (setupGuide.user === user) {
setupGuide.user = null;
contentLayout._showSetup = false;
return;
if (setupWizard.user === user) {
setupWizard.closeWizard();
}
}
}
@ -83,65 +98,53 @@ ApplicationWindow {
if (user.setupGuideSeen) {
return;
}
root.showSetup(user, user.addresses[0]);
root.showClientConfigurator(user, user.addresses[0], false);
}
target: Backend.users
}
Connections {
function onLoginFinished(index, wasSignedOut) {
const user = Backend.users.get(index);
if (user && !wasSignedOut) {
root.showSetup(user, user.addresses[0]);
}
console.debug("Login finished", index);
}
function onSelectUser(userID, forceShowWindow) {
contentWrapper.selectUser(userID);
if (setupWizard.visible) {
setupWizard.closeWizard()
}
if (forceShowWindow) {
root.showAndRise();
}
}
function onShowHelp() {
root.showHelp();
if (setupWizard.visible) {
setupWizard.closeWizard()
}
root.showAndRise();
}
function onShowMainWindow() {
root.showAndRise();
}
function onShowSettings() {
if (setupWizard.visible) {
setupWizard.closeWizard()
}
root.showSettings();
root.showAndRise();
}
target: Backend
}
Connections {
function onCountChanged(count) {
layoutForUserCount(count);
}
target: Backend.users
}
StackLayout {
id: contentLayout
property bool _showSetup: false
anchors.fill: parent
currentIndex: {
// show welcome when there are no users
if (Backend.users.count === 0) {
return 1;
}
const u = Backend.users.get(0);
if (!u) {
console.trace();
console.log("empty user");
return 1;
}
if ((Backend.users.count === 1) && (u.state === EUserState.SignedOut)) {
showSignIn(u.primaryEmailOrUsername());
return 0;
}
if (contentLayout._showSetup) {
return 2;
}
return 0;
}
currentIndex: 0
ContentWrapper {
// 0
@ -160,30 +163,24 @@ ApplicationWindow {
root.close();
Backend.quit();
}
onShowSetupGuide: function (user, address) {
root.showSetup(user, address);
onShowClientConfigurator: function (user, address, justLoggedIn) {
root.showClientConfigurator(user, address, justLoggedIn);
}
onShowLogin: function (username) {
root.showLogin(username);
}
}
WelcomeGuide {
Layout.fillHeight: true
Layout.fillWidth: true // 1
colorScheme: root.colorScheme
}
SetupGuide {
// 2
id: setupGuide
SetupWizard {
id: setupWizard
Layout.fillHeight: true
Layout.fillWidth: true
colorScheme: root.colorScheme
onDismissed: {
root.showSetup(null, "");
onBugReportRequested: {
contentWrapper.showBugReport();
}
onFinished: {
// TODO: Do not close window. Trigger Backend to check that
// there is a successfully connected client. Then Backend
// should send another signal to close the setup guide.
root.showSetup(null, "");
onWizardEnded: {
contentLayout.currentIndex = 0;
}
}
}

View File

@ -0,0 +1,56 @@
// Copyright (c) 2023 Proton AG
// This file is part of Proton Mail Bridge.
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Proton
import "SetupWizard"
Rectangle {
id: root
property ColorScheme colorScheme
color: root.colorScheme.background_norm
signal linkClicked()
ColumnLayout {
anchors.fill: parent
spacing: 0
// we use the setup wizard left pane (onboarding version)
LeftPane {
Layout.alignment: Qt.AlignHCenter
Layout.fillHeight: true
Layout.preferredWidth: ProtonStyle.wizard_pane_width
colorScheme: root.colorScheme
wizard: setupWizard
Component.onCompleted: {
showOnboarding();
link1.setCallback(root.linkClicked, "Start setup", false)
}
}
Image {
id: mailLogoWithWordmark
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: ProtonStyle.wizard_window_margin
height: sourceSize.height
source: root.colorScheme.mail_logo_with_wordmark
sourceSize.height: 36
sourceSize.width: 134
width: sourceSize.width
}
}
}

View File

@ -453,7 +453,7 @@ QtObject {
brief: title
description: qsTr("Changing between split and combined address mode will require you to delete your account(s) from your email client and begin the setup process from scratch.")
group: Notifications.Group.Configuration | Notifications.Group.Dialogs
icon: "/qml/icons/ic-question-circle.svg"
icon: "./icons/ic-question-circle.svg"
title: qsTr("Enable split mode?")
type: Notification.NotificationType.Warning
@ -788,8 +788,6 @@ QtObject {
}
}
property Notification rebuildKeychain: Notification {
property var supportLink: "https://proton.me/support/bridge"
brief: title
description: qsTr("Bridge is not able to access your macOS keychain. Please consult the instructions on our support page.")
group: Notifications.Group.Dialogs | Notifications.Group.Configuration
@ -802,8 +800,7 @@ QtObject {
text: qsTr("Open the support page")
onTriggered: {
Backend.notifyKBArticleClicked(root.rebuildKeychain.supportLink);
Qt.openUrlExternally(root.rebuildKeychain.supportLink);
Backend.openKBArticle();
Backend.quit();
}
}

View File

@ -23,11 +23,13 @@ T.Button {
property bool borderless: false
property ColorScheme colorScheme
readonly property bool hasTextAndIcon: (control.text !== "") && (iconImage.source.toString().length > 0)
property bool iconOnTheLeft: false
readonly property bool isIcon: control.text === ""
property int labelType: Proton.Label.LabelType.Body
property bool loading: false
readonly property bool primary: !secondary
property alias secondary: control.flat
property bool secondaryIsOpaque: false
property alias textHorizontalAlignment: label.horizontalAlignment
property alias textVerticalAlignment: label.verticalAlignment
@ -77,7 +79,7 @@ T.Button {
if (control.loading) {
return control.colorScheme.interaction_default_hover;
}
return control.colorScheme.interaction_default;
return secondaryIsOpaque ? control.colorScheme.background_norm : control.colorScheme.interaction_default;
}
} else {
if (primary) {
@ -103,7 +105,7 @@ T.Button {
if (control.loading) {
return control.colorScheme.interaction_default_hover;
}
return control.colorScheme.interaction_default;
return secondaryIsOpaque ? control.colorScheme.background_norm : control.colorScheme.interaction_default;
}
}
}
@ -115,6 +117,7 @@ T.Button {
}
contentItem: RowLayout {
id: _contentItem
layoutDirection: iconOnTheLeft ? Qt.RightToLeft : Qt.LeftToRight
spacing: control.hasTextAndIcon ? control.spacing : 0
Proton.Label {
@ -128,12 +131,13 @@ T.Button {
return control.colorScheme.text_norm;
}
}
colorScheme: root.colorScheme
colorScheme: control.colorScheme
elide: Text.ElideRight
horizontalAlignment: Qt.AlignHCenter
opacity: control.enabled || control.loading ? 1.0 : 0.5
text: control.text
type: labelType
verticalAlignment: Text.AlignVCenter
visible: !control.isIcon
}
ColorImage {

View File

@ -48,6 +48,7 @@ QtObject {
property color interaction_weak_active
property color interaction_weak_hover
property string logo_img
property string mail_logo_with_wordmark
// Primary
property color primary_norm
@ -82,7 +83,4 @@ QtObject {
// Text
property color text_norm
property color text_weak
// Images
property string welcome_img
}

View File

@ -0,0 +1,88 @@
// Copyright (c) 2023 Proton AG
// This file is part of Proton Mail Bridge.
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
RowLayout {
id: root
property var callback: null
property ColorScheme colorScheme
property bool external: false
property string link: "#"
property string text: ""
function clear() {
root.callback = null;
root.text = "";
root.link = "";
root.external = false;
}
function link(url, text) {
return label.link(url, text);
}
function setCallback(callback, linkText, external) {
root.callback = callback;
root.text = linkText;
root.link = "#"; // Cannot be empty, otherwise the text is not an hyperlink.
root.external = external;
}
function setLink(linkURL, linkText, external) {
root.callback = null;
root.text = linkText;
root.link = linkURL;
root.external = external;
}
Label {
id: label
Layout.alignment: Qt.AlignVCenter
colorScheme: root.colorScheme
text: label.link(root.link, root.text)
type: Label.LabelType.Body
onLinkActivated: function (link) {
if ((link !== "#") && (link.length > 0)) {
Qt.openUrlExternally(link);
}
if (callback) {
callback();
}
}
}
ColorImage {
Layout.alignment: Qt.AlignVCenter
color: label.linkColor
height: sourceSize.height
source: "/qml/icons/ic-external-link.svg"
sourceSize.height: 16
sourceSize.width: 16
visible: external
width: sourceSize.width
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
label.onLinkActivated(root.link);
}
}
}
HoverHandler {
acceptedDevices: PointerDevice.Mouse
cursorShape: Qt.PointingHandCursor
enabled: true
}
}

View File

@ -20,21 +20,21 @@ import "."
QtObject {
id: root
property real account_hover_radius: 12 * root.px // px
property real account_row_radius: 12 * root.px // px
property real avatar_radius: 8 * root.px // px
property real banner_radius: 12 * root.px // px
property real big_avatar_radius: 12 * root.px // px
property int account_hover_radius: 12
property int account_row_radius: 12
property int avatar_radius: 8
property int banner_radius: 12
property int big_avatar_radius: 12
property int body_font_size: 14
property real body_letter_spacing: 0.2 * root.px
property real body_letter_spacing: 0.2
property int body_line_height: 20
property real button_radius: 8 * root.px // px
property int button_radius: 8
property int caption_font_size: 12
property real caption_letter_spacing: 0.4 * root.px
property real caption_letter_spacing: 0.4
property int caption_line_height: 16
property real card_radius: 12 * root.px // px
property real checkbox_radius: 4 * root.px // px
property real context_item_radius: 8 * root.px // px
property int card_radius: 12
property int checkbox_radius: 4
property int context_item_radius: 8
property ColorScheme currentStyle: lightStyle
property ColorScheme darkProminentStyle: ColorScheme {
id: _darkProminentStyle
@ -72,6 +72,7 @@ QtObject {
interaction_weak_active: "#6D697D"
interaction_weak_hover: "#5B576B"
logo_img: "/qml/icons/product_logos_dark.svg"
mail_logo_with_wordmark: "/qml/icons/img-mail-logo-wordmark-dark.svg"
// Primary
primary_norm: "#8A6EFF"
@ -105,9 +106,6 @@ QtObject {
// Text
text_norm: "#FFFFFF"
text_weak: "#A7A4B5"
// Images
welcome_img: "/qml/icons/img-welcome-dark.png"
}
property ColorScheme darkStyle: ColorScheme {
id: _darkStyle
@ -145,6 +143,7 @@ QtObject {
interaction_weak_active: "#6D697D"
interaction_weak_hover: "#5B576B"
logo_img: "/qml/icons/product_logos_dark.svg"
mail_logo_with_wordmark: "/qml/icons/img-mail-logo-wordmark-dark.svg"
// Primary
primary_norm: "#8A6EFF"
@ -178,11 +177,8 @@ QtObject {
// Text
text_norm: "#FFFFFF"
text_weak: "#A7A4B5"
// Images
welcome_img: "/qml/icons/img-welcome-dark.png"
}
property real dialog_radius: 12 * root.px // px
property int dialog_radius: 12
property int fontWeight_100: Font.Thin
property int fontWeight_200: Font.Light
property int fontWeight_300: Font.ExtraLight
@ -206,7 +202,7 @@ QtObject {
}
property int heading_font_size: 28
property int heading_line_height: 36
property real input_radius: 8 * root.px // px
property int input_radius: 8
property int lead_font_size: 18
property int lead_line_height: 26
property ColorScheme lightProminentStyle: ColorScheme {
@ -245,6 +241,7 @@ QtObject {
interaction_weak_active: "#8A6EFF"
interaction_weak_hover: "#6D4AFF"
logo_img: "/qml/icons/product_logos_dark.svg"
mail_logo_with_wordmark: "/qml/icons/img-mail-logo-wordmark-dark.svg"
// Primary
primary_norm: "#8A6EFF"
@ -278,9 +275,6 @@ QtObject {
// Text
text_norm: "#FFFFFF"
text_weak: "#9282D4"
// Images
welcome_img: "/qml/icons/img-welcome-dark.png"
}
// TODO: Once we will use Qt >=5.15 this should be refactored with inline components as follows:
// https://doc.qt.io/qt-5/qtqml-documents-definetypes.html#inline-components
@ -325,6 +319,7 @@ QtObject {
interaction_weak_active: "#A8A6A3"
interaction_weak_hover: "#C2BFBC"
logo_img: "/qml/icons/product_logos.svg"
mail_logo_with_wordmark: "/qml/icons/img-mail-logo-wordmark.svg"
// Primary
primary_norm: "#6D4AFF"
@ -358,13 +353,35 @@ QtObject {
// Text
text_norm: "#0C0C14"
text_weak: "#706D6B"
// Images
welcome_img: "/qml/icons/img-welcome.png"
}
property real progress_bar_radius: 3 * root.px // px
property real px: 1.00 // px
property int progress_bar_radius: 3
property int title_font_size: 20
property int title_line_height: 24
property real tooltip_radius: 8 * root.px // px
property int tooltip_radius: 8
// WebView overlay styling
property int web_view_button_width: 320
property int web_view_corner_radius: 10
property int web_view_overlay_button_vertical_margin: 10
property int web_view_overlay_horizontal_padding: 10
property int web_view_overlay_horizontal_margin: 250
property int web_view_overlay_vertical_margin: 50
property real web_view_overlay_opacity: 0.6
property int web_view_overlay_vertical_padding: web_view_corner_radius
property int web_view_overley_border_width: 1
property int window_default_height: 780
property int window_default_width: 1080
property int window_minimum_height: 650
property int window_minimum_width: window_default_width
// setup wizard constant
property int wizard_pane_bottomMargin: 92
property int wizard_pane_width: 364
property int wizard_window_margin: 40
property int wizard_spacing_extra_large: 32
property int wizard_spacing_extra_small: 4
property int wizard_spacing_large: 24
property int wizard_spacing_medium: 16
property int wizard_spacing_small: 8
}

View File

@ -238,12 +238,12 @@ FocusScope {
bottomPadding: 8
color: {
if (!control.enabled) {
return root.colorScheme.text_disabled
return root.colorScheme.text_disabled;
}
if (control.readOnly) {
return root.colorScheme.text_hint
return root.colorScheme.text_hint;
}
return root.colorScheme.text_norm
return root.colorScheme.text_norm;
}
// enforcing default focus here within component

View File

@ -28,6 +28,7 @@ CheckBox 4.0 CheckBox.qml
ComboBox 4.0 ComboBox.qml
Dialog 4.0 Dialog.qml
Label 4.0 Label.qml
LinkLabel 4.0 LinkLabel.qml
Menu 4.0 Menu.qml
MenuItem 4.0 MenuItem.qml
Popup 4.0 Popup.qml
@ -36,3 +37,4 @@ Switch 4.0 Switch.qml
TextArea 4.0 TextArea.qml
TextField 4.0 TextField.qml
Toggle 4.0 Toggle.qml
WebFrame 4.0 WebFrame.qml

View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<style>
body {font-family: sans-serif}
h1 { font-size: 1.5em; text-align: center; margin-bottom: 2em;}
p {text-align: justify; margin-bottom: 2em; }
p.standfirst { font-weight: bold;}
</style>
</head>
<body>
%1
</body>
</html>

View File

@ -0,0 +1,19 @@
<h1>Why do I need bridge?</h1>
<p class="standfirst">
Proton does not have access to the content of your messages, so it cannot share your unencrypted messages with your email client from the
Proton servers.
</p>
<p>
Email clients such as Microsoft Outlook, Mozilla Thunderbird and Apple Mail use standard protocols named IMAP and SMTP to receive and send emails.
</p>
<p>
Even though the IMAP and SMTP protocols can use secure channels (using SSL/TLS), they do not offer support for encrypted messages.
Because Proton does not have access to the content of your messages, it is not possible to configure your email client to connect directly to
Proton servers.
</p>
<p>
The key to solving this problem is Bridge. Once installed on your computer and connected to your Proton account, Bridge can access your
encrypted messages stored on the Proton servers. Bridge integrates an IMAP and a SMTP server that run on your computer and are accessible only
to applications executing on your machine. Your email client connects to these local servers and Bridge is responsible for seamlessly encrypting
and decrypting the messages that you send and receive.
</p>

View File

@ -0,0 +1,19 @@
<h1>Why do I need to install a certificate when configuring Apple Mail with Bridge?</h1>
<p class="standfirst">
Apple Mail requires a secure channel for communications with email servers, and the server needs to be acknowledged as trusted.
</p>
<p>
In order to communicate with Bridge, Apple Mail requires secure connections using SSL/TLS. This cryptographic protocol includes an identity
verification system using certificates. For publicly available servers, certificates are normally issued and digitally signed by a certificate
authority, such as Let's Encrypt. This is not possible for Bridge, as the IMAP and SMTP servers are running on your own computer, and are not
accessible from any network (local or internet).
</p>
<p>
The solution is to use a self-signed certificate. When setting up an email account where the server provides a self-signed certificate, most
email clients will issue a warning asking you whether you trust the server or not, because the certificate was not issued by a certificate
authority.
</p>
<p>
Apple Mail requires an extra step. It will simply refuse to connect if the certificate is not set as trusted. Bridge solves this by storing this
certificate in the macOS keychain. This operation requires that you provide your macOS account password.
</p>

View File

@ -0,0 +1,21 @@
<h1>Why is there a warning sign when installing the Bridge profile on macOS?</h1>
<p class="standfirst">
This warning indicates that the certificate used to secure the communication channel between Bridge and your email client is not signed by a
trusted third party.
</p>
<p>
In order to communicate with Bridge, Apple Mail requires secure connections using SSL/TLS. This cryptographic protocol includes an identity
verification system using certificates. For publicly available servers, certificates are normally issued and digitally signed by a certificate
authority, such as Let's Encrypt. This is not possible for Bridge, as the IMAP and SMTP servers are running on your own computer, and are not
accessible from any network (local or internet).
</p>
<p>
The solution is to use a self-signed certificate. When setting up an email account where the server provides a self-signed certificate, most
email clients will issue a warning asking you whether you trust the server or not, because the certificate was not issued by a certificate
authority. The client has no way of verifying that the server is who it pretends to be.
</p>
<p>
You can safely ignore this warning. The check concerns only the communication between your email client and Bridge, which occurs within your
computer. On the other end, the communication between Bridge and the Proton servers uses the HTTPS protocol, and the identity of the remote
server is verified by Bridge.
</p>

View File

@ -90,7 +90,7 @@ Item {
icon.source: root.actionIcon
loading: root.loading
secondary: root.type !== SettingsItem.PrimaryButton
text: root.actionText + (root.actionIcon !== "" ? " " : "")
text: root.actionText
visible: root.type === SettingsItem.Button || root.type === SettingsItem.PrimaryButton
onClicked: {

View File

@ -1,293 +0,0 @@
// Copyright (c) 2023 Proton AG
// This file is part of Proton Mail Bridge.
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import QtQuick.Controls.impl
import Proton
Item {
id: root
property string address
property ColorScheme colorScheme
property var user
signal dismissed
signal finished
function reset() {
guidePages.currentIndex = 0;
clientList.currentIndex = -1;
actionList.currentIndex = -1;
}
function setupAction(actionID, clientID) {
if (user) {
user.setupGuideSeen = true;
}
switch (actionID) {
case -1:
root.dismissed();
break; // dismiss
case 0 // automatic
:
if (user) {
switch (clientID) {
case 0:
root.user.configureAppleMail(root.address);
Backend.notifyAutoconfigClicked("AppleMail");
break;
}
}
root.finished();
break;
case 1 // manual
:
let clientObj = clients.get(clientID);
if (clientObj !== undefined && clientObj.link !== "") {
Qt.openUrlExternally(clientObj.link);
Backend.notifyKBArticleClicked(clientObj.link);
} else {
console.log("unexpected client index", actionID, clientID);
}
root.finished();
break;
default:
console.log("unexpected client setup action", actionID, clientID);
}
}
implicitHeight: children[0].implicitHeight
implicitWidth: children[0].implicitWidth
ListModel {
id: clients
property bool haveAutoSetup: true
property string iconSource: "/qml/icons/ic-apple-mail.svg"
property string link: "https://proton.me/support/protonmail-bridge-clients-apple-mail"
property string name: "Apple Mail"
Component.onCompleted: {
if (Backend.goos === "darwin") {
append({
"name": "Apple Mail",
"iconSource": "/qml/icons/ic-apple-mail.svg",
"haveAutoSetup": true,
"link": "https://proton.me/support/protonmail-bridge-clients-apple-mail"
});
append({
"name": "Microsoft Outlook",
"iconSource": "/qml/icons/ic-microsoft-outlook.svg",
"haveAutoSetup": false,
"link": "https://proton.me/support/protonmail-bridge-clients-macos-outlook-2019"
});
}
if (Backend.goos === "windows") {
append({
"name": "Microsoft Outlook",
"iconSource": "/qml/icons/ic-microsoft-outlook.svg",
"haveAutoSetup": false,
"link": "https://proton.me/support/protonmail-bridge-clients-windows-outlook-2019"
});
}
append({
"name": "Mozilla Thunderbird",
"iconSource": "/qml/icons/ic-mozilla-thunderbird.svg",
"haveAutoSetup": false,
"link": "https://proton.me/support/protonmail-bridge-clients-windows-thunderbird"
});
append({
"name": "Other",
"iconSource": "/qml/icons/ic-other-mail-clients.svg",
"haveAutoSetup": false,
"link": "https://proton.me/support/protonmail-bridge-configure-client"
});
}
}
Rectangle {
anchors.fill: root
color: root.colorScheme.background_norm
}
StackLayout {
id: guidePages
anchors.bottomMargin: 70
anchors.fill: parent
anchors.leftMargin: 80
anchors.rightMargin: 80
anchors.topMargin: 30
ColumnLayout {
// 0: Client selection
id: clientView
property int columnWidth: 268
Layout.fillHeight: true
spacing: 8
Label {
colorScheme: root.colorScheme
text: qsTr("Setting up email client")
type: Label.LabelType.Heading
}
Label {
color: root.colorScheme.text_weak
colorScheme: root.colorScheme
text: address
type: Label.LabelType.Lead
}
RowLayout {
Layout.topMargin: 32 - clientView.spacing
spacing: 24
ColumnLayout {
id: clientColumn
Layout.alignment: Qt.AlignTop
Label {
id: labelA
colorScheme: root.colorScheme
text: qsTr("Choose an email client")
type: Label.LabelType.Body_semibold
}
ListView {
id: clientList
Layout.fillHeight: true
model: clients
width: clientView.columnWidth
delegate: Item {
implicitHeight: clientRow.height
implicitWidth: clientRow.width
ColumnLayout {
id: clientRow
width: clientList.width
RowLayout {
Layout.bottomMargin: 12
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 12
ColorImage {
height: 36
source: model.iconSource
sourceSize.height: 36
}
Label {
Layout.leftMargin: 12
colorScheme: root.colorScheme
text: model.name
type: Label.LabelType.Body
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: root.colorScheme.border_weak
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
clientList.currentIndex = index;
if (!model.haveAutoSetup) {
root.setupAction(1, index);
}
}
}
}
highlight: Rectangle {
color: root.colorScheme.interaction_default_active
radius: ProtonStyle.context_item_radius
}
}
}
ColumnLayout {
id: actionColumn
Layout.alignment: Qt.AlignTop
visible: clientList.currentIndex >= 0 && clients.get(clientList.currentIndex).haveAutoSetup
Label {
colorScheme: root.colorScheme
text: qsTr("Choose configuration mode")
type: Label.LabelType.Body_semibold
}
ListView {
id: actionList
Layout.fillHeight: true
model: [qsTr("Configure automatically"), qsTr("Configure manually")]
width: clientView.columnWidth
delegate: Item {
implicitHeight: children[0].height
implicitWidth: children[0].width
ColumnLayout {
width: actionList.width
Label {
Layout.bottomMargin: 20
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 20
colorScheme: root.colorScheme
text: modelData
type: Label.LabelType.Body
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: root.colorScheme.border_weak
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
actionList.currentIndex = index;
root.setupAction(index, clientList.currentIndex);
}
}
}
highlight: Rectangle {
color: root.colorScheme.interaction_default_active
radius: ProtonStyle.context_item_radius
}
}
}
}
Item {
Layout.fillHeight: true
}
Button {
colorScheme: root.colorScheme
flat: true
text: qsTr("Set up later")
onClicked: {
root.setupAction(-1, -1);
if (user) {
user.setupGuideSeen = true;
}
root.dismissed();
}
}
}
}
}

View File

@ -0,0 +1,274 @@
// Copyright (c) 2023 Proton AG
// This file is part of Proton Mail Bridge.
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
Item {
id: root
enum Screen {
CertificateInstall,
ProfileInstall
}
property var wizard
signal appleMailAutoconfigCertificateInstallPageShown
signal appleMailAutoconfigProfileInstallPageShow
function showAutoconfig() {
if (Backend.isTLSCertificateInstalled()) {
showProfileInstall();
} else {
showCertificateInstall();
}
}
function showCertificateInstall() {
certificateInstall.reset();
stack.currentIndex = ClientConfigAppleMail.Screen.CertificateInstall;
appleMailAutoconfigCertificateInstallPageShown();
}
function showProfileInstall() {
profileInstall.reset();
stack.currentIndex = ClientConfigAppleMail.Screen.ProfileInstall;
appleMailAutoconfigProfileInstallPageShow();
}
StackLayout {
id: stack
anchors.fill: parent
// stack index 0
Item {
id: certificateInstall
property string errorString: ""
property bool showBugReportLink: false
property bool waitingForCert: false
function clearError() {
errorString = "";
showBugReportLink = false;
}
function reset() {
waitingForCert = false;
clearError();
}
Layout.fillHeight: true
Layout.fillWidth: true
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: ProtonStyle.wizard_spacing_large
Connections {
function onCertificateInstallCanceled() {
certificateInstall.waitingForCert = false;
certificateInstall.errorString = qsTr("Apple Mail cannot be configured if you do not install the certificate. Please retry.");
certificateInstall.showBugReportLink = false;
}
function onCertificateInstallFailed() {
certificateInstall.waitingForCert = false;
certificateInstall.errorString = qsTr("An error occurred while installing the certificate.");
certificateInstall.showBugReportLink = true;
}
function onCertificateInstallSuccess() {
certificateInstall.reset();
root.showAutoconfig();
}
target: Backend
}
ColumnLayout {
Layout.fillWidth: true
spacing: ProtonStyle.wizard_spacing_medium
Label {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
colorScheme: wizard.colorScheme
horizontalAlignment: Text.AlignHCenter
text: qsTr("Install the bridge certificate")
type: Label.LabelType.Title
wrapMode: Text.WordWrap
}
Label {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
color: colorScheme.text_weak
colorScheme: wizard.colorScheme
horizontalAlignment: Text.AlignHCenter
text: qsTr("After clicking on the button below, a system pop-up will ask you for your credentials, please enter your macOS user credentials (not your Proton accounts) and validate.")
type: Label.LabelType.Body
wrapMode: Text.WordWrap
}
}
Image {
Layout.alignment: Qt.AlignHCenter
height: 182
opacity: certificateInstall.waitingForCert ? 0.3 : 1.0
source: "/qml/icons/img-macos-cert-screenshot.png"
width: 140
}
ColumnLayout {
Layout.fillWidth: true
spacing: ProtonStyle.wizard_spacing_medium
Button {
Layout.fillWidth: true
colorScheme: wizard.colorScheme
enabled: !certificateInstall.waitingForCert
loading: certificateInstall.waitingForCert
text: qsTr("Install the certificate")
onClicked: {
certificateInstall.clearError();
certificateInstall.waitingForCert = true;
Backend.installTLSCertificate();
}
}
Button {
Layout.fillWidth: true
colorScheme: wizard.colorScheme
enabled: !certificateInstall.waitingForCert
secondary: true
text: qsTr("Cancel")
onClicked: {
wizard.closeWizard();
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: ProtonStyle.wizard_spacing_small
RowLayout {
Layout.fillWidth: true
spacing: ProtonStyle.wizard_spacing_extra_small
ColorImage {
color: wizard.colorScheme.signal_danger
height: errorLabel.lineHeight
source: "/qml/icons/ic-exclamation-circle-filled.svg"
sourceSize.height: errorLabel.lineHeight
visible: certificateInstall.errorString.length > 0
}
Label {
id: errorLabel
Layout.fillWidth: true
color: wizard.colorScheme.signal_danger
colorScheme: wizard.colorScheme
horizontalAlignment: Text.AlignHCenter
text: certificateInstall.errorString
type: Label.LabelType.Body_semibold
wrapMode: Text.WordWrap
}
}
LinkLabel {
Layout.alignment: Qt.AlignHCenter
callback: wizard.showBugReport
colorScheme: wizard.colorScheme
link: "#"
text: qsTr("Report the problem")
visible: certificateInstall.showBugReportLink
}
}
}
}
}
// stack index 1
Item {
id: profileInstall
property bool profilePaneLaunched: false
function reset() {
profilePaneLaunched = false;
}
Layout.fillHeight: true
Layout.fillWidth: true
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: ProtonStyle.wizard_spacing_large
ColumnLayout {
Layout.fillWidth: true
spacing: ProtonStyle.wizard_spacing_medium
Label {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
colorScheme: wizard.colorScheme
horizontalAlignment: Text.AlignHCenter
text: qsTr("Install the profile")
type: Label.LabelType.Title
wrapMode: Text.WordWrap
}
Label {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
color: colorScheme.text_weak
colorScheme: wizard.colorScheme
horizontalAlignment: Text.AlignHCenter
text: qsTr("A system pop-up will appear. Double click on the entry with your email, and click Install in the dialog that appears.")
type: Label.LabelType.Body
wrapMode: Text.WordWrap
}
}
Image {
Layout.alignment: Qt.AlignHCenter
height: 102
source: "/qml/icons/img-macos-profile-screenshot.png"
width: 364
}
ColumnLayout {
Layout.fillWidth: true
spacing: ProtonStyle.wizard_spacing_medium
Button {
Layout.fillWidth: true
colorScheme: wizard.colorScheme
text: profileInstall.profilePaneLaunched ? qsTr("I have installed the profile") : qsTr("Install the profile")
onClicked: {
if (profileInstall.profilePaneLaunched) {
wizard.showClientConfigEnd();
} else {
wizard.user.configureAppleMail(wizard.address);
profileInstall.profilePaneLaunched = true;
}
}
}
Button {
Layout.fillWidth: true
colorScheme: wizard.colorScheme
secondary: true
text: qsTr("Cancel")
onClicked: {
wizard.closeWizard();
}
}
}
}
}
}
}

View File

@ -0,0 +1,99 @@
// Copyright (c) 2023 Proton AG
// This file is part of Proton Mail Bridge.
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
Rectangle {
id: root
property ColorScheme colorScheme: wizard.colorScheme
property var wizard
clip: true
color: colorScheme.background_norm
Item {
id: centeredContainer
anchors.bottom: parent.bottom
anchors.bottomMargin: 84
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 32
clip: true
width: ProtonStyle.wizard_pane_width
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: ProtonStyle.wizard_spacing_medium
Image {
Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: sourceSize.height
Layout.preferredWidth: sourceSize.width
source: "/qml/icons/img-client-config-success.svg"
sourceSize.height: 104
sourceSize.width: 190
}
Label {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
colorScheme: wizard.colorScheme
horizontalAlignment: Text.AlignHCenter
text: qsTr("Congratulations! You're all setup")
type: Label.LabelType.Title
wrapMode: Text.WordWrap
}
Label {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
color: colorScheme.text_weak
colorScheme: wizard.colorScheme
horizontalAlignment: Text.AlignHCenter
text: wizard.address
type: Label.LabelType.Body
wrapMode: Text.WordWrap
}
Label {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
colorScheme: wizard.colorScheme
horizontalAlignment: Text.AlignHCenter
text: qsTr("Your client has been configured. While complete synchronization might take some time, you can already send encrypted emails.")
type: Label.LabelType.Body
wrapMode: Text.WordWrap
}
Button {
Layout.fillWidth: true
colorScheme: root.colorScheme
text: qsTr("Done")
onClicked: wizard.closeWizard()
}
}
}
Image {
id: mailLogoWithWordmark
anchors.bottom: parent.bottom
anchors.bottomMargin: 32
anchors.horizontalCenter: parent.horizontalCenter
height: 36
source: root.colorScheme.mail_logo_with_wordmark
sourceSize.height: height
sourceSize.width: width
width: 134
}
}

View File

@ -0,0 +1,163 @@
// Copyright (c) 2023 Proton AG
// This file is part of Proton Mail Bridge.
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import ".."
Rectangle {
id: root
property ColorScheme colorScheme: wizard.colorScheme
readonly property bool genericClient: SetupWizard.Client.Generic === wizard.client
property var wizard
clip: true
color: colorScheme.background_weak
Item {
id: centeredContainer
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
width: 640
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: ProtonStyle.wizard_spacing_medium
Label {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
colorScheme: wizard.colorScheme
horizontalAlignment: Text.AlignHCenter
text: qsTr("Configure %1").arg(wizard.clientName())
type: Label.LabelType.Title
wrapMode: Text.WordWrap
}
Rectangle {
Layout.fillWidth: true
border.color: colorScheme.border_norm
border.width: 1
color: "transparent"
height: childrenRect.height + 2 * ProtonStyle.wizard_spacing_medium
radius: 12
RowLayout {
anchors.left: parent.left
anchors.margins: ProtonStyle.wizard_spacing_medium
anchors.right: parent.right
anchors.top: parent.top
spacing: ProtonStyle.wizard_spacing_small
Label {
Layout.fillHeight: true
Layout.fillWidth: true
colorScheme: wizard.colorScheme
horizontalAlignment: Text.AlignLeft
text: (SetupWizard.Client.MicrosoftOutlook === wizard.client) ? qsTr("Are you unsure about your Outlook version or do you need assistance in configuring Outlook?") : qsTr("Do you need assistance in configuring %1?".arg(wizard.clientName()))
type: Label.LabelType.Body
verticalAlignment: Text.AlignVCenter
wrapMode: Text.WordWrap
}
Button {
colorScheme: root.colorScheme
icon.source: "/qml/icons/ic-external-link.svg"
text: qsTr("Open guide")
onClicked: function () {
Backend.openKBArticle(wizard.setupGuideLink());
}
}
}
}
Rectangle {
Layout.fillWidth: true
border.color: colorScheme.signal_warning
border.width: 1
color: "transparent"
height: childrenRect.height + 2 * ProtonStyle.wizard_spacing_medium
radius: ProtonStyle.banner_radius
RowLayout {
anchors.left: parent.left
anchors.margins: ProtonStyle.wizard_spacing_medium
anchors.right: parent.right
anchors.top: parent.top
spacing: ProtonStyle.wizard_spacing_medium
ColorImage {
id: image
height: 36
source: "/qml/icons/ic-warning-orange.svg"
sourceSize.height: height
sourceSize.width: width
width: height
}
Label {
Layout.fillHeight: true
Layout.fillWidth: true
colorScheme: wizard.colorScheme
horizontalAlignment: Text.AlignLeft
text: qsTr("Copy paste the provided configuration parameters. Use the password below (not your Proton password), when adding your Proton account to %1.".arg(wizard.clientName()))
type: Label.LabelType.Body
verticalAlignment: Text.AlignVCenter
wrapMode: Text.WordWrap
}
}
}
RowLayout {
id: configuration
Layout.fillWidth: true
spacing: ProtonStyle.wizard_spacing_extra_large
Configuration {
Layout.fillWidth: true
colorScheme: wizard.colorScheme
highlightPassword: true
hostname: Backend.hostname
password: wizard.user ? wizard.user.password : ""
port: Backend.imapPort.toString()
security: Backend.useSSLForIMAP ? "SSL" : "STARTTLS"
title: "IMAP"
username: wizard.address
}
Configuration {
Layout.fillWidth: true
colorScheme: wizard.colorScheme
highlightPassword: true
hostname: Backend.hostname
password: wizard.user ? wizard.user.password : ""
port: Backend.smtpPort.toString()
security: Backend.useSSLForSMTP ? "SSL" : "STARTTLS"
title: "SMTP"
username: wizard.address
}
}
Button {
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: 304
colorScheme: root.colorScheme
secondary: true
secondaryIsOpaque: true
text: qsTr("Continue")
onClicked: wizard.showClientConfigEnd()
}
}
}
}

View File

@ -0,0 +1,101 @@
// Copyright (c) 2023 Proton AG
// This file is part of Proton Mail Bridge.
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
Item {
id: root
readonly property bool onMacOS: (Backend.goos === "darwin")
readonly property bool onWindows: (Backend.goos === "windows")
property var wizard
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: ProtonStyle.wizard_spacing_medium
Label {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
Layout.topMargin: ProtonStyle.wizard_spacing_medium
colorScheme: wizard.colorScheme
horizontalAlignment: Qt.AlignHCenter
text: qsTr("Select your email client")
type: Label.LabelType.Title
wrapMode: Text.WordWrap
}
ClientListItem {
Layout.fillWidth: true
colorScheme: wizard.colorScheme
iconSource: "/qml/icons/ic-apple-mail.svg"
text: "Apple Mail"
visible: root.onMacOS
onClicked: {
wizard.client = SetupWizard.Client.AppleMail;
wizard.showAppleMailAutoConfig();
}
}
ClientListItem {
Layout.fillWidth: true
colorScheme: wizard.colorScheme
iconSource: "/qml/icons/ic-microsoft-outlook.svg"
text: "Microsoft Outlook"
visible: root.onMacOS || root.onWindows
onClicked: {
wizard.client = SetupWizard.Client.MicrosoftOutlook;
wizard.showClientParams();
}
}
ClientListItem {
Layout.fillWidth: true
colorScheme: wizard.colorScheme
iconSource: "/qml/icons/ic-mozilla-thunderbird.svg"
text: "Mozilla Thunderbird"
onClicked: {
wizard.client = SetupWizard.Client.MozillaThunderbird;
wizard.showClientParams();
}
}
ClientListItem {
Layout.fillWidth: true
colorScheme: wizard.colorScheme
iconSource: "/qml/icons/ic-other-mail-clients.svg"
text: qsTr("Other")
onClicked: {
wizard.client = SetupWizard.Client.Generic;
wizard.showClientParams();
}
}
Button {
Layout.fillWidth: true
Layout.topMargin: 20
colorScheme: wizard.colorScheme
secondary: true
secondaryIsOpaque: true
text: qsTr("Setup later")
onClicked: {
root.wizard.closeWizard();
}
}
}
}

View File

@ -0,0 +1,70 @@
// Copyright (c) 2023 Proton AG
// This file is part of Proton Mail Bridge.
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
Rectangle {
id: root
property ColorScheme colorScheme
property string iconSource
property string text
signal clicked
border.color: colorScheme.border_norm
border.width: 1
color: {
if (mouseArea.pressed) {
return colorScheme.interaction_default_active;
}
if (mouseArea.containsMouse) {
return colorScheme.interaction_default_hover;
}
return colorScheme.background_norm;
}
height: 68
radius: ProtonStyle.banner_radius
RowLayout {
anchors.fill: parent
anchors.margins: ProtonStyle.wizard_spacing_medium
ColorImage {
height: sourceSize.height
source: iconSource
sourceSize.height: 36
}
Label {
Layout.fillWidth: true
Layout.leftMargin: 12
colorScheme: root.colorScheme
horizontalAlignment: Text.AlignLeft
text: root.text
type: Label.LabelType.Body
verticalAlignment: Text.AlignVCenter
}
}
MouseArea {
id: mouseArea
acceptedButtons: Qt.LeftButton
anchors.fill: parent
hoverEnabled: true
onClicked: {
root.clicked();
}
}
}

View File

@ -0,0 +1,65 @@
// Copyright (c) 2023 Proton AG
// This file is part of Proton Mail Bridge.
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
Button {
id: root
property var wizard
readonly property int _iconPadding: 8 // The SVG image we use has internal padding that we need to compensate for alignment.
readonly property int _iconSize: 24
anchors.bottom: parent.bottom
anchors.bottomMargin: ProtonStyle.wizard_window_margin - _iconPadding
anchors.right: parent.right
anchors.rightMargin: ProtonStyle.wizard_window_margin - _iconPadding
colorScheme: wizard.colorScheme
horizontalPadding: 0
icon.color: wizard.colorScheme.text_weak
icon.height: _iconSize
icon.source: "/qml/icons/ic-question-circle.svg"
icon.width: _iconSize
verticalPadding: 0
onClicked: {
menu.popup(-menu.width + root.width, -menu.height);
}
Menu {
id: menu
colorScheme: root.colorScheme
modal: true
MenuItem {
id: getHelpItem
colorScheme: root.colorScheme
text: qsTr("Get help")
onClicked: {
Backend.openKBArticle();
}
}
MenuItem {
id: reportAProblemItem
colorScheme: root.colorScheme
text: qsTr("Report a problem")
onClicked: {
wizard.showBugReport();
}
}
}
}

View File

@ -0,0 +1,127 @@
// Copyright (c) 2023 Proton AG
// This file is part of Proton Mail Bridge.
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
Item {
id: root
property int iconHeight
property string iconSource
property int iconWidth
property var wizard
property ColorScheme colorScheme
property var _colorScheme: wizard ? wizard.colorScheme : colorScheme
property var link1: linkLabel1
property var link2: linkLabel2
function showAppleMailAutoconfigCertificateInstall() {
showAppleMailAutoconfigCommon();
descriptionLabel.text = qsTr("Apple Mail configuration is mostly automated, but in order to work, Bridge needs to install a certificate in your keychain.");
linkLabel1.setCallback(function() { Backend.openKBArticle("https://proton.me/support/apple-mail-certificate"); }, qsTr("Why is this certificate needed?"), true);
linkLabel2.clear();
}
function showAppleMailAutoconfigCommon() {
titleLabel.text = "";
linkLabel1.clear();
linkLabel2.clear();
iconSource = wizard.clientIconSource();
iconHeight = 80;
iconWidth = 80;
}
function showAppleMailAutoconfigProfileInstall() {
showAppleMailAutoconfigCommon();
descriptionLabel.text = qsTr("The final step before you can start using Apple Mail is to install the Bridge server profile in the system preferences.\n\nAdding a server profile is necessary to ensure that your Mac can receive and send Proton Mails.");
linkLabel1.setCallback(function() { Backend.openKBArticle("https://proton.me/support/macos-certificate-warning"); }, qsTr("Why is there a yellow warning sign?"), true);
linkLabel2.setCallback(wizard.showClientParams, qsTr("Configure Apple Mail manually"), false);
}
function showClientSelector(newAccount = true) {
titleLabel.text = "";
descriptionLabel.text = newAccount ? qsTr("Bridge is now connected to Proton, and has already started downloading your messages. Lets now connect your email client to Bridge.") : qsTr("Lets connect your email client to Bridge.");
linkLabel1.clear();
linkLabel2.clear();
iconSource = "/qml/icons/img-client-config-selector.svg";
iconHeight = 104;
iconWidth = 266;
}
function showLogin() {
showOnboarding();
}
function showLogin2FA() {
showOnboarding();
}
function showLoginMailboxPassword() {
showOnboarding();
}
function showOnboarding() {
titleLabel.text = (Backend.users.count === 0) ? qsTr("Welcome to\nProton Mail Bridge") : qsTr("Add a Proton Mail account");
descriptionLabel.text = qsTr("Bridge is the gateway between your Proton account and your email client. It runs in the background and encrypts and decrypts your messages seamlessly. ");
linkLabel1.setCallback(function() { Backend.openKBArticle("https://proton.me/support/why-you-need-bridge"); }, qsTr("Why do I need Bridge?"), true);
linkLabel2.clear();
root.iconSource = "/qml/icons/img-welcome.svg";
root.iconHeight = 148;
root.iconWidth = 265;
}
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: ProtonStyle.wizard_spacing_medium
Image {
id: icon
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
Layout.preferredHeight: root.iconHeight
Layout.preferredWidth: root.iconWidth
source: root.iconSource
sourceSize.height: root.iconHeight
sourceSize.width: root.iconWidth
}
Label {
id: titleLabel
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
colorScheme: _colorScheme
horizontalAlignment: Text.AlignHCenter
text: ""
type: Label.LabelType.Heading
visible: text.length !== 0
wrapMode: Text.WordWrap
}
Label {
id: descriptionLabel
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
colorScheme: _colorScheme
horizontalAlignment: Text.AlignHCenter
text: ""
type: Label.LabelType.Body
wrapMode: Text.WordWrap
}
LinkLabel {
id: linkLabel1
Layout.alignment: Qt.AlignHCenter
colorScheme: _colorScheme
visible: (text !== "")
}
LinkLabel {
id: linkLabel2
Layout.alignment: Qt.AlignHCenter
colorScheme: _colorScheme
visible: (text !== "")
}
}
}

View File

@ -0,0 +1,477 @@
// Copyright (c) 2023 Proton AG
// This file is part of Proton Mail Bridge.
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
FocusScope {
id: root
enum RootStack {
Login,
TOTP,
MailboxPassword
}
property alias currentIndex: stackLayout.currentIndex
property alias username: usernameTextField.text
property var wizard
signal loginAbort(string username, bool wasSignedOut)
function abort() {
root.reset();
loginAbort(usernameTextField.text, false);
Backend.loginAbort(usernameTextField.text);
}
function reset(clearUsername = false) {
stackLayout.currentIndex = Login.RootStack.Login;
loginLayout.reset(clearUsername);
totpLayout.reset();
mailboxPasswordLayout.reset();
if (username.length === 0) {
usernameTextField.forceActiveFocus();
} else {
passwordTextField.forceActiveFocus();
}
}
StackLayout {
id: stackLayout
function loginFailed() {
signInButton.loading = false;
usernameTextField.enabled = true;
usernameTextField.error = true;
passwordTextField.enabled = true;
passwordTextField.error = true;
}
anchors.fill: parent
Connections {
function onLogin2FAError(_) {
console.assert(stackLayout.currentIndex === Login.RootStack.TOTP, "Unexpected login2FAError");
twoFAButton.loading = false;
twoFactorPasswordTextField.enabled = true;
twoFactorPasswordTextField.error = true;
twoFactorPasswordTextField.errorString = qsTr("Your code is incorrect");
twoFactorPasswordTextField.focus = true;
}
function onLogin2FAErrorAbort(_) {
console.assert(stackLayout.currentIndex === Login.RootStack.TOTP, "Unexpected login2FAErrorAbort");
root.reset();
errorLabel.text = qsTr("Incorrect login credentials. Please try again.");
}
function onLogin2FARequested(username) {
console.assert(stackLayout.currentIndex === Login.RootStack.Login, "Unexpected login2FARequested");
twoFactorUsernameLabel.text = username;
stackLayout.currentIndex = Login.RootStack.TOTP;
twoFactorPasswordTextField.focus = true;
}
function onLogin2PasswordError(_) {
console.assert(stackLayout.currentIndex === Login.RootStack.MailboxPassword, "Unexpected login2PasswordError");
secondPasswordButton.loading = false;
secondPasswordTextField.enabled = true;
secondPasswordTextField.error = true;
secondPasswordTextField.errorString = qsTr("Your mailbox password is incorrect");
secondPasswordTextField.focus = true;
}
function onLogin2PasswordErrorAbort(_) {
console.assert(stackLayout.currentIndex === Login.RootStack.MailboxPassword, "Unexpected login2PasswordErrorAbort");
root.reset();
errorLabel.text = qsTr("Incorrect login credentials. Please try again.");
}
function onLogin2PasswordRequested(username) {
console.assert(stackLayout.currentIndex === Login.RootStack.Login || stackLayout.currentIndex === Login.RootStack.TOTP, "Unexpected login2PasswordRequested");
stackLayout.currentIndex = Login.RootStack.MailboxPassword;
mailboxPasswordUsernameLabel.text = username;
secondPasswordTextField.focus = true;
}
function onLoginAlreadyLoggedIn(_) {
stackLayout.currentIndex = Login.RootStack.Login;
root.reset();
}
function onLoginConnectionError(_) {
if (stackLayout.currentIndex === Login.RootStack.Login) {
stackLayout.loginFailed();
}
}
function onLoginFinished(_) {
stackLayout.currentIndex = Login.RootStack.Login;
root.reset();
}
function onLoginFreeUserError() {
console.assert(stackLayout.currentIndex === Login.RootStack.Login, "Unexpected loginFreeUserError");
stackLayout.loginFailed();
}
function onLoginUsernamePasswordError(errorMsg) {
console.assert(stackLayout.currentIndex === Login.RootStack.Login, "Unexpected loginUsernamePasswordError");
stackLayout.loginFailed();
if (errorMsg !== "")
errorLabel.text = errorMsg;
else
errorLabel.text = qsTr("Incorrect login credentials");
}
target: Backend
}
Item {
ColumnLayout {
id: loginLayout
function clearErrors() {
usernameTextField.error = false;
usernameTextField.errorString = "";
passwordTextField.error = false;
passwordTextField.errorString = "";
errorLabel.text = "";
}
function reset(clearUsername = false) {
signInButton.loading = false;
errorLabel.text = "";
usernameTextField.enabled = true;
usernameTextField.focus = true;
if (clearUsername) {
usernameTextField.text = "";
}
passwordTextField.enabled = true;
passwordTextField.text = "";
clearErrors();
}
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: ProtonStyle.wizard_spacing_medium
ColumnLayout {
Layout.fillWidth: true
spacing: ProtonStyle.wizard_spacing_small
Label {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
colorScheme: wizard.colorScheme
horizontalAlignment: Text.AlignHCenter
text: qsTr("Sign in")
type: Label.LabelType.Title
}
Label {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
color: wizard.colorScheme.text_weak
colorScheme: wizard.colorScheme
horizontalAlignment: Text.AlignHCenter
text: qsTr("Enter your Proton Account details.")
type: Label.LabelType.Body
}
}
RowLayout {
Layout.fillWidth: true
spacing: 0
ColorImage {
color: wizard.colorScheme.signal_danger
height: errorLabel.lineHeight
source: "/qml/icons/ic-exclamation-circle-filled.svg"
sourceSize.height: errorLabel.lineHeight
visible: errorLabel.text.length > 0
}
Label {
id: errorLabel
Layout.fillWidth: true
Layout.leftMargin: 4
color: wizard.colorScheme.signal_danger
colorScheme: wizard.colorScheme
type: Label.LabelType.Caption_semibold
wrapMode: Text.WordWrap
}
}
TextField {
id: usernameTextField
Layout.fillWidth: true
colorScheme: wizard.colorScheme
focus: true
label: qsTr("Email or username")
validateOnEditingFinished: false
validator: function (str) {
if (str.length === 0) {
return qsTr("Enter email or username");
}
}
onAccepted: passwordTextField.forceActiveFocus()
onTextChanged: {
loginLayout.clearErrors();
}
}
TextField {
id: passwordTextField
Layout.fillWidth: true
colorScheme: wizard.colorScheme
echoMode: TextInput.Password
label: qsTr("Password")
validateOnEditingFinished: false
validator: function (str) {
if (str.length === 0) {
return qsTr("Enter password");
}
}
onAccepted: signInButton.checkAndSignIn()
onTextChanged: {
loginLayout.clearErrors();
}
}
Button {
id: signInButton
function checkAndSignIn() {
usernameTextField.validate();
passwordTextField.validate();
if (usernameTextField.error || passwordTextField.error) {
return;
}
usernameTextField.enabled = false;
passwordTextField.enabled = false;
loading = true;
Backend.login(usernameTextField.text, Qt.btoa(passwordTextField.text));
}
Layout.fillWidth: true
colorScheme: wizard.colorScheme
enabled: !loading
text: loading ? qsTr("Signing in") : qsTr("Sign in")
onClicked: {
checkAndSignIn();
}
}
Button {
Layout.fillWidth: true
colorScheme: wizard.colorScheme
enabled: !signInButton.loading
secondary: true
secondaryIsOpaque: true
text: qsTr("Cancel")
onClicked: {
root.abort();
}
}
LinkLabel {
Layout.alignment: Qt.AlignHCenter
colorScheme: wizard.colorScheme
external: true
link: "https://proton.me/mail/pricing"
text: qsTr("Create or upgrade your account")
}
}
}
Item {
ColumnLayout {
id: totpLayout
function reset() {
twoFAButton.loading = false;
twoFactorPasswordTextField.enabled = true;
twoFactorPasswordTextField.error = false;
twoFactorPasswordTextField.errorString = "";
twoFactorPasswordTextField.text = "";
}
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: ProtonStyle.wizard_spacing_medium
ColumnLayout {
Layout.fillWidth: true
spacing: ProtonStyle.wizard_spacing_small
Label {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
colorScheme: wizard.colorScheme
horizontalAlignment: Text.AlignHCenter
text: qsTr("Two-factor authentication")
type: Label.LabelType.Title
}
Label {
id: twoFactorUsernameLabel
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
color: wizard.colorScheme.text_weak
colorScheme: wizard.colorScheme
horizontalAlignment: Text.AlignHCenter
text: ""
type: Label.LabelType.Body
}
}
Label {
id: descriptionLabel
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
colorScheme: wizard.colorScheme
horizontalAlignment: Text.AlignHCenter
text: qsTr("You have enabled two-factor authentication. Please enter the 6-digit code provided by your authenticator application.")
type: Label.LabelType.Body
wrapMode: Text.WordWrap
}
TextField {
id: twoFactorPasswordTextField
Layout.fillWidth: true
colorScheme: wizard.colorScheme
label: qsTr("Two-factor code")
validateOnEditingFinished: false
validator: function (str) {
if (str.length === 0) {
return qsTr("Enter the 6-digit code");
}
}
onAccepted: {
twoFAButton.onClicked();
}
onTextChanged: {
if (text.length >= 6) {
twoFAButton.onClicked();
}
}
}
Button {
id: twoFAButton
Layout.fillWidth: true
colorScheme: wizard.colorScheme
enabled: !loading
text: loading ? qsTr("Authenticating") : qsTr("Authenticate")
onClicked: {
twoFactorPasswordTextField.validate();
if (twoFactorPasswordTextField.error) {
return;
}
twoFactorPasswordTextField.enabled = false;
loading = true;
Backend.login2FA(usernameTextField.text, Qt.btoa(twoFactorPasswordTextField.text));
}
}
Button {
Layout.fillWidth: true
colorScheme: wizard.colorScheme
enabled: !twoFAButton.loading
secondary: true
secondaryIsOpaque: true
text: qsTr("Cancel")
onClicked: {
root.abort();
}
}
}
}
Item {
ColumnLayout {
id: mailboxPasswordLayout
function reset() {
secondPasswordButton.loading = false;
secondPasswordTextField.enabled = true;
secondPasswordTextField.error = false;
secondPasswordTextField.errorString = "";
secondPasswordTextField.text = "";
}
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: ProtonStyle.wizard_spacing_medium
ColumnLayout {
Layout.fillWidth: true
spacing: ProtonStyle.wizard_spacing_small
Label {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
colorScheme: wizard.colorScheme
horizontalAlignment: Text.AlignHCenter
text: qsTr("Unlock your mailbox")
type: Label.LabelType.Title
}
Label {
id: mailboxPasswordUsernameLabel
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
color: wizard.colorScheme.text_weak
colorScheme: wizard.colorScheme
horizontalAlignment: Text.AlignHCenter
text: ""
type: Label.LabelType.Body
}
}
Label {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
colorScheme: wizard.colorScheme
horizontalAlignment: Text.AlignHCenter
text: qsTr("You have secured your account with a separate mailbox password.")
type: Label.LabelType.Body
wrapMode: Text.WordWrap
}
TextField {
id: secondPasswordTextField
Layout.fillWidth: true
colorScheme: wizard.colorScheme
echoMode: TextInput.Password
label: qsTr("Mailbox password")
validateOnEditingFinished: false
validator: function (str) {
if (str.length === 0) {
return qsTr("Enter password");
}
}
onAccepted: {
secondPasswordButton.onClicked();
}
}
Button {
id: secondPasswordButton
Layout.fillWidth: true
colorScheme: wizard.colorScheme
enabled: !loading
text: loading ? qsTr("Unlocking") : qsTr("Unlock")
onClicked: {
secondPasswordTextField.validate();
if (secondPasswordTextField.error) {
return;
}
secondPasswordTextField.enabled = false;
loading = true;
Backend.login2Password(usernameTextField.text, Qt.btoa(secondPasswordTextField.text));
}
}
Button {
Layout.fillWidth: true
colorScheme: wizard.colorScheme
enabled: !secondPasswordButton.loading
secondary: true
secondaryIsOpaque: true
text: qsTr("Cancel")
onClicked: {
root.abort();
}
}
}
}
}
}

View File

@ -0,0 +1,52 @@
// Copyright (c) 2023 Proton AG
// This file is part of Proton Mail Bridge.
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
Item {
id: root
property var wizard
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: ProtonStyle.wizard_spacing_large
StepDescriptionBox {
colorScheme: wizard.colorScheme
description: qsTr("Connect Bridge to your Proton account")
icon: "/qml/icons/ic-bridge.svg"
iconSize: 48
title: qsTr("Step 1")
}
StepDescriptionBox {
colorScheme: wizard.colorScheme
description: qsTr("Connect your email client to Bridge")
icon: "/qml/icons/img-mail-clients.svg"
iconSize: 48
title: qsTr("Step 2")
}
Button {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
colorScheme: wizard.colorScheme
text: qsTr("Start setup")
onClicked: wizard.showLogin()
}
}
}

View File

@ -0,0 +1,300 @@
// Copyright (c) 2023 Proton AG
// This file is part of Proton Mail Bridge.
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
Item {
id: root
enum Client {
AppleMail,
MicrosoftOutlook,
MozillaThunderbird,
Generic
}
enum ContentStack {
Onboarding,
Login,
ClientConfigSelector,
ClientConfigAppleMail
}
enum RootStack {
TwoPanesView,
ClientConfigParameters,
ClientConfigEnd
}
property string address
property var backAction: null
property int client
property ColorScheme colorScheme
property var user
signal bugReportRequested
signal wizardEnded
function _showClientConfig() {
showClientConfig(root.user, root.address, false);
}
function clientIconSource() {
switch (client) {
case SetupWizard.Client.AppleMail:
return "/qml/icons/ic-apple-mail.svg";
case SetupWizard.Client.MicrosoftOutlook:
return "/qml/icons/ic-microsoft-outlook.svg";
case SetupWizard.Client.MozillaThunderbird:
return "/qml/icons/ic-mozilla-thunderbird.svg";
case SetupWizard.Client.Generic:
return "/qml/icons/ic-other-mail-clients.svg";
default:
console.error("Unknown mail client " + client);
return "/qml/icons/ic-other-mail-clients.svg";
}
}
function clientName() {
switch (client) {
case SetupWizard.Client.AppleMail:
return "Apple Mail";
case SetupWizard.Client.MicrosoftOutlook:
return "Outlook";
case SetupWizard.Client.MozillaThunderbird:
return "Thunderbird";
case SetupWizard.Client.Generic:
return qsTr("your email client");
default:
console.error("Unknown mail client " + client);
return qsTr("your email client");
}
}
function closeWizard() {
wizardEnded();
}
function setupGuideLink() {
switch (client) {
case SetupWizard.Client.AppleMail:
return "https://proton.me/support/protonmail-bridge-clients-apple-mail";
case SetupWizard.Client.MicrosoftOutlook:
return (Backend.goos === "darwin") ? "https://proton.me/support/protonmail-bridge-clients-macos-outlook-2019" : "https://proton.me/support/protonmail-bridge-clients-windows-outlook-2019";
case SetupWizard.Client.MozillaThunderbird:
return "https://proton.me/support/protonmail-bridge-clients-windows-thunderbird";
default:
return "https://proton.me/support/protonmail-bridge-configure-client";
}
}
function showAppleMailAutoConfig() {
backAction = _showClientConfig;
rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView;
rightContent.currentIndex = SetupWizard.ContentStack.ClientConfigAppleMail;
clientConfigAppleMail.showAutoconfig(); // This will trigger signals that will display the appropriate left content.
}
function showBugReport() {
closeWizard();
bugReportRequested();
}
function showClientConfig(user, address, justLoggedIn) {
backAction = null;
root.user = user;
root.address = address;
rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView;
leftContent.showClientSelector(justLoggedIn);
rightContent.currentIndex = SetupWizard.ContentStack.ClientConfigSelector;
}
function showClientConfigEnd() {
backAction = null;
rootStackLayout.currentIndex = SetupWizard.RootStack.ClientConfigEnd;
}
function showClientParams() {
backAction = _showClientConfig;
rootStackLayout.currentIndex = SetupWizard.RootStack.ClientConfigParameters;
}
function showLogin(username = "") {
backAction = null;
rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView;
root.address = "";
leftContent.showLogin();
rightContent.currentIndex = SetupWizard.ContentStack.Login;
login.username = username;
login.reset(false);
}
function showOnboarding() {
backAction = null;
rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView;
root.address = "";
root.user = null;
leftContent.showOnboarding();
rightContent.currentIndex = SetupWizard.ContentStack.Onboarding;
}
Connections {
function onLoginFinished(userIndex, wasSignedOut) {
if (wasSignedOut) {
closeWizard();
return;
}
let user = Backend.users.get(userIndex);
let address = user ? user.addresses[0] : "";
showClientConfig(user, address, true);
}
target: Backend
}
StackLayout {
id: rootStackLayout
anchors.fill: parent
// rootStackLayout index 0
RowLayout {
Layout.fillHeight: true
Layout.fillWidth: true
spacing: 0
Rectangle {
id: leftHalf
Layout.fillHeight: true
Layout.fillWidth: true
color: root.colorScheme.background_norm
LeftPane {
id: leftContent
anchors.bottom: parent.bottom
anchors.bottomMargin: ProtonStyle.wizard_pane_bottomMargin
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: ProtonStyle.wizard_window_margin
clip: true
width: ProtonStyle.wizard_pane_width
wizard: root
Connections {
function onAppleMailAutoconfigCertificateInstallPageShown() {
leftContent.showAppleMailAutoconfigCertificateInstall();
}
function onAppleMailAutoconfigProfileInstallPageShow() {
leftContent.showAppleMailAutoconfigProfileInstall();
}
target: clientConfigAppleMail
}
Connections {
function onLogin2FARequested() {
leftContent.showLogin2FA();
}
function onLogin2PasswordRequested() {
leftContent.showLoginMailboxPassword();
}
target: Backend
}
}
Image {
id: mailLogoWithWordmark
anchors.bottom: parent.bottom
anchors.bottomMargin: ProtonStyle.wizard_window_margin
anchors.horizontalCenter: parent.horizontalCenter
height: sourceSize.height
source: root.colorScheme.mail_logo_with_wordmark
sourceSize.height: 36
sourceSize.width: 134
width: sourceSize.width
}
}
Rectangle {
id: rightHalf
Layout.fillHeight: true
Layout.fillWidth: true
color: root.colorScheme.background_weak
StackLayout {
id: rightContent
anchors.bottom: parent.bottom
anchors.bottomMargin: ProtonStyle.wizard_pane_bottomMargin
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: ProtonStyle.wizard_window_margin
clip: true
currentIndex: 0
width: ProtonStyle.wizard_pane_width
// rightContent stack index 0
Onboarding {
wizard: root
}
// rightContent tack index 1
Login {
id: login
wizard: root
onLoginAbort: {
root.closeWizard();
}
}
// rightContent stack index 2
ClientConfigSelector {
id: clientConfigSelector
wizard: root
}
// rightContent stack index 3
ClientConfigAppleMail {
id: clientConfigAppleMail
wizard: root
}
}
}
}
// rootStackLayout index 1
ClientConfigParameters {
id: clientConfigParameters
Layout.fillHeight: true
Layout.fillWidth: true
wizard: root
}
// rootStackLayout index 2
ClientConfigEnd {
id: clientConfigEnd
Layout.fillHeight: true
Layout.fillWidth: true
wizard: root
}
}
HelpButton {
wizard: root
}
Button {
id: backButton
anchors.left: parent.left
anchors.leftMargin: ProtonStyle.wizard_window_margin
anchors.top: parent.top
anchors.topMargin: ProtonStyle.wizard_window_margin
colorScheme: root.colorScheme
icon.source: "/qml/icons/ic-chevron-left.svg"
iconOnTheLeft: true
secondary: true
secondaryIsOpaque: true
spacing: ProtonStyle.wizard_spacing_small
text: qsTr("Back")
visible: backAction != null
onClicked: {
if (backAction) {
backAction();
}
}
}
}

View File

@ -0,0 +1,59 @@
// Copyright (c) 2023 Proton AG
// This file is part of Proton Mail Bridge.
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
RowLayout {
id: root
property ColorScheme colorScheme
property string description
property string icon
property int iconSize: 64
property string title
spacing: ProtonStyle.wizard_spacing_large
Image {
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Layout.preferredHeight: iconSize
Layout.preferredWidth: iconSize
mipmap: true
source: root.icon
}
ColumnLayout {
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillWidth: true
spacing: ProtonStyle.wizard_spacing_small
Label {
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillHeight: false
Layout.fillWidth: true
colorScheme: root.colorScheme
text: root.title
type: Label.LabelType.Body_bold
}
Label {
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillHeight: true
Layout.fillWidth: true
colorScheme: root.colorScheme
text: root.description
type: Label.LabelType.Body
verticalAlignment: Text.AlignTop
}
}
}

View File

@ -1,413 +0,0 @@
// Copyright (c) 2023 Proton AG
// This file is part of Proton Mail Bridge.
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import QtQuick.Controls.impl
import Proton
FocusScope {
id: root
property ColorScheme colorScheme
property alias currentIndex: stackLayout.currentIndex
property alias username: usernameTextField.text
function abort() {
root.reset();
Backend.loginAbort(usernameTextField.text);
}
function reset() {
stackLayout.currentIndex = 0;
loginNormalLayout.reset();
login2FALayout.reset();
login2PasswordLayout.reset();
}
implicitHeight: children[0].implicitHeight
implicitWidth: children[0].implicitWidth
state: "Page 1"
states: [
State {
name: "Page 1"
PropertyChanges {
currentIndex: 0
target: stackLayout
}
},
State {
name: "Page 2"
PropertyChanges {
currentIndex: 1
target: stackLayout
}
},
State {
name: "Page 3"
PropertyChanges {
currentIndex: 2
target: stackLayout
}
}
]
StackLayout {
id: stackLayout
function loginFailed() {
signInButton.loading = false;
usernameTextField.enabled = true;
usernameTextField.error = true;
passwordTextField.enabled = true;
passwordTextField.error = true;
}
anchors.fill: parent
Connections {
function onLogin2FAError(_) {
console.assert(stackLayout.currentIndex === 1, "Unexpected login2FAError");
twoFAButton.loading = false;
twoFactorPasswordTextField.enabled = true;
twoFactorPasswordTextField.error = true;
twoFactorPasswordTextField.errorString = qsTr("Your code is incorrect");
twoFactorPasswordTextField.focus = true;
}
function onLogin2FAErrorAbort(_) {
console.assert(stackLayout.currentIndex === 1, "Unexpected login2FAErrorAbort");
root.reset();
errorLabel.text = qsTr("Incorrect login credentials. Please try again.");
}
function onLogin2FARequested(username) {
console.assert(stackLayout.currentIndex === 0, "Unexpected login2FARequested");
twoFactorUsernameLabel.text = username;
stackLayout.currentIndex = 1;
twoFactorPasswordTextField.focus = true;
}
function onLogin2PasswordError(_) {
console.assert(stackLayout.currentIndex === 2, "Unexpected login2PasswordError");
secondPasswordButton.loading = false;
secondPasswordTextField.enabled = true;
secondPasswordTextField.error = true;
secondPasswordTextField.errorString = qsTr("Your mailbox password is incorrect");
secondPasswordTextField.focus = true;
}
function onLogin2PasswordErrorAbort(_) {
console.assert(stackLayout.currentIndex === 2, "Unexpected login2PasswordErrorAbort");
root.reset();
errorLabel.text = qsTr("Incorrect login credentials. Please try again.");
}
function onLogin2PasswordRequested() {
console.assert(stackLayout.currentIndex === 0 || stackLayout.currentIndex === 1, "Unexpected login2PasswordRequested");
stackLayout.currentIndex = 2;
secondPasswordTextField.focus = true;
}
function onLoginAlreadyLoggedIn(_) {
stackLayout.currentIndex = 0;
root.reset();
}
function onLoginConnectionError(_) {
if (stackLayout.currentIndex === 0) {
stackLayout.loginFailed();
}
}
function onLoginFinished(_) {
stackLayout.currentIndex = 0;
root.reset();
}
function onLoginFreeUserError() {
console.assert(stackLayout.currentIndex === 0, "Unexpected loginFreeUserError");
stackLayout.loginFailed();
}
function onLoginUsernamePasswordError(errorMsg) {
console.assert(stackLayout.currentIndex === 0, "Unexpected loginUsernamePasswordError");
stackLayout.loginFailed();
if (errorMsg !== "")
errorLabel.text = errorMsg;
else
errorLabel.text = qsTr("Incorrect login credentials");
}
target: Backend
}
ColumnLayout {
id: loginNormalLayout
function reset() {
signInButton.loading = false;
errorLabel.text = "";
usernameTextField.enabled = true;
usernameTextField.error = false;
usernameTextField.errorString = "";
usernameTextField.focus = true;
passwordTextField.enabled = true;
passwordTextField.error = false;
passwordTextField.errorString = "";
passwordTextField.text = "";
}
spacing: 0
Label {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 16
colorScheme: root.colorScheme
text: qsTr("Sign in")
type: Label.LabelType.Title
}
Label {
id: subTitle
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 8
color: root.colorScheme.text_weak
colorScheme: root.colorScheme
text: qsTr("Enter your Proton Account details.")
type: Label.LabelType.Body
}
RowLayout {
Layout.fillWidth: true
Layout.topMargin: 36
spacing: 0
visible: errorLabel.text.length > 0
ColorImage {
color: root.colorScheme.signal_danger
height: errorLabel.lineHeight
source: "/qml/icons/ic-exclamation-circle-filled.svg"
sourceSize.height: errorLabel.lineHeight
}
Label {
id: errorLabel
Layout.fillWidth: true
Layout.leftMargin: 4
color: root.colorScheme.signal_danger
colorScheme: root.colorScheme
type: root.error ? Label.LabelType.Caption_semibold : Label.LabelType.Caption
wrapMode: Text.WordWrap
}
}
TextField {
id: usernameTextField
Layout.fillWidth: true
Layout.topMargin: 24
colorScheme: root.colorScheme
focus: true
label: qsTr("Email or username")
validateOnEditingFinished: false
validator: function (str) {
if (str.length === 0) {
return qsTr("Enter email or username");
}
}
onAccepted: passwordTextField.forceActiveFocus()
onTextChanged: {
// remove "invalid username / password error"
if (error || errorLabel.text.length > 0) {
errorLabel.text = "";
usernameTextField.error = false;
passwordTextField.error = false;
}
}
}
TextField {
id: passwordTextField
Layout.fillWidth: true
Layout.topMargin: 8
colorScheme: root.colorScheme
echoMode: TextInput.Password
label: qsTr("Password")
validateOnEditingFinished: false
validator: function (str) {
if (str.length === 0) {
return qsTr("Enter password");
}
}
onAccepted: signInButton.checkAndSignIn()
onTextChanged: {
// remove "invalid username / password error"
if (error || errorLabel.text.length > 0) {
errorLabel.text = "";
usernameTextField.error = false;
passwordTextField.error = false;
}
}
}
Button {
id: signInButton
function checkAndSignIn() {
usernameTextField.validate();
passwordTextField.validate();
if (usernameTextField.error || passwordTextField.error) {
return;
}
usernameTextField.enabled = false;
passwordTextField.enabled = false;
loading = true;
Backend.login(usernameTextField.text, Qt.btoa(passwordTextField.text));
}
Layout.fillWidth: true
Layout.topMargin: 24
colorScheme: root.colorScheme
enabled: !loading
text: loading ? qsTr("Signing in") : qsTr("Sign in")
onClicked: {
checkAndSignIn();
}
}
Label {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 24
colorScheme: root.colorScheme
text: link("https://proton.me/mail/pricing", qsTr("Create or upgrade your account"))
textFormat: Text.StyledText
type: Label.LabelType.Body
onLinkActivated: {
Qt.openUrlExternally(link);
}
}
}
ColumnLayout {
id: login2FALayout
function reset() {
twoFAButton.loading = false;
twoFactorPasswordTextField.enabled = true;
twoFactorPasswordTextField.error = false;
twoFactorPasswordTextField.errorString = "";
twoFactorPasswordTextField.text = "";
}
spacing: 0
Label {
Layout.alignment: Qt.AlignCenter
Layout.topMargin: 16
colorScheme: root.colorScheme
text: qsTr("Two-factor authentication")
type: Label.LabelType.Heading
}
Label {
id: twoFactorUsernameLabel
Layout.alignment: Qt.AlignCenter
Layout.topMargin: 8
color: root.colorScheme.text_weak
colorScheme: root.colorScheme
type: Label.LabelType.Lead
}
TextField {
id: twoFactorPasswordTextField
Layout.fillWidth: true
Layout.topMargin: 32
assistiveText: qsTr("Enter the 6-digit code")
colorScheme: root.colorScheme
label: qsTr("Two-factor code")
validateOnEditingFinished: false
validator: function (str) {
if (str.length === 0) {
return qsTr("Enter the 6-digit code");
}
}
onAccepted: {
twoFAButton.onClicked();
}
onTextChanged: {
if (text.length >= 6) {
twoFAButton.onClicked();
}
}
}
Button {
id: twoFAButton
Layout.fillWidth: true
Layout.topMargin: 24
colorScheme: root.colorScheme
enabled: !loading
text: loading ? qsTr("Authenticating") : qsTr("Authenticate")
onClicked: {
twoFactorPasswordTextField.validate();
if (twoFactorPasswordTextField.error) {
return;
}
twoFactorPasswordTextField.enabled = false;
loading = true;
Backend.login2FA(usernameTextField.text, Qt.btoa(twoFactorPasswordTextField.text));
}
}
}
ColumnLayout {
id: login2PasswordLayout
function reset() {
secondPasswordButton.loading = false;
secondPasswordTextField.enabled = true;
secondPasswordTextField.error = false;
secondPasswordTextField.errorString = "";
secondPasswordTextField.text = "";
}
spacing: 0
Label {
Layout.alignment: Qt.AlignCenter
Layout.topMargin: 16
colorScheme: root.colorScheme
text: qsTr("Unlock your mailbox")
type: Label.LabelType.Heading
}
TextField {
id: secondPasswordTextField
Layout.fillWidth: true
Layout.topMargin: 8 + implicitHeight + 24 + subTitle.implicitHeight
colorScheme: root.colorScheme
echoMode: TextInput.Password
label: qsTr("Mailbox password")
validateOnEditingFinished: false
validator: function (str) {
if (str.length === 0) {
return qsTr("Enter password");
}
}
onAccepted: {
secondPasswordButton.onClicked();
}
}
Button {
id: secondPasswordButton
Layout.fillWidth: true
Layout.topMargin: 24
colorScheme: root.colorScheme
enabled: !loading
text: loading ? qsTr("Unlocking") : qsTr("Unlock")
onClicked: {
secondPasswordTextField.validate();
if (secondPasswordTextField.error) {
return;
}
secondPasswordTextField.enabled = false;
loading = true;
Backend.login2Password(usernameTextField.text, Qt.btoa(secondPasswordTextField.text));
}
}
}
}
}

View File

@ -1,245 +0,0 @@
// Copyright (c) 2023 Proton AG
// This file is part of Proton Mail Bridge.
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Proton
Item {
id: root
property ColorScheme colorScheme
implicitHeight: children[0].implicitHeight
implicitWidth: children[0].implicitWidth
RowLayout {
anchors.fill: parent
spacing: 0
states: [
State {
name: "Page 1"
PropertyChanges {
currentIndex: 0
target: signInItem
}
},
State {
name: "Page 2"
PropertyChanges {
currentIndex: 1
target: signInItem
}
},
State {
name: "Page 3"
PropertyChanges {
currentIndex: 2
target: signInItem
}
}
]
Rectangle {
Layout.fillHeight: true
Layout.fillWidth: true
color: root.colorScheme.background_norm
implicitHeight: children[0].implicitHeight
implicitWidth: children[0].implicitWidth
visible: signInItem.currentIndex === 0
GridLayout {
anchors.fill: parent
columnSpacing: 0
columns: 3
rowSpacing: 0
// top margin
Item {
Layout.columnSpan: 3
Layout.fillWidth: true
// Using binding component here instead of direct binding to avoid binding loop during construction of element
Binding on Layout.preferredHeight {
value: (parent.height - welcomeContentItem.height) / 4
}
}
// left margin
Item {
Layout.fillWidth: true
Layout.maximumWidth: 80
Layout.minimumWidth: 48
Layout.preferredHeight: welcomeContentItem.height
}
ColumnLayout {
id: welcomeContentItem
Layout.fillWidth: true
spacing: 0
Image {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 16
source: colorScheme.welcome_img
sourceSize.height: 148
sourceSize.width: 264
}
Label {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
Layout.topMargin: 16
colorScheme: root.colorScheme
horizontalAlignment: Text.AlignHCenter
text: qsTr("Welcome to\nProton Mail Bridge")
type: Label.LabelType.Heading
}
Label {
id: longTextLabel
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
Layout.preferredWidth: 320
Layout.topMargin: 16
colorScheme: root.colorScheme
horizontalAlignment: Text.AlignHCenter
text: qsTr("Add your Proton Mail account to securely access and manage your messages in your favorite email client. Bridge runs in the background and encrypts and decrypts your messages seamlessly.")
type: Label.LabelType.Body
wrapMode: Text.WordWrap
}
}
// Right margin
Item {
Layout.fillWidth: true
Layout.maximumWidth: 80
Layout.minimumWidth: 48
Layout.preferredHeight: welcomeContentItem.height
}
// bottom margin
Item {
Layout.columnSpan: 3
Layout.fillHeight: true
Layout.fillWidth: true
implicitHeight: children[0].implicitHeight + children[0].anchors.bottomMargin + children[0].anchors.topMargin
implicitWidth: children[0].implicitWidth
Image {
id: logoImage
anchors.bottom: parent.bottom
anchors.bottomMargin: 48
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 48
source: colorScheme.logo_img
sourceSize.height: 25
sourceSize.width: 200
}
}
}
}
Rectangle {
Layout.fillHeight: true
Layout.fillWidth: true
color: (signInItem.currentIndex == 0) ? root.colorScheme.background_weak : root.colorScheme.background_norm
implicitHeight: children[0].implicitHeight
implicitWidth: children[0].implicitWidth
RowLayout {
anchors.fill: parent
spacing: 0
Item {
Layout.fillHeight: true
Layout.fillWidth: true
Layout.preferredWidth: signInItem.currentIndex == 0 ? 0 : parent.width / 4
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
Button {
anchors.bottom: parent.bottom
anchors.bottomMargin: 80
anchors.left: parent.left
anchors.leftMargin: 80
anchors.rightMargin: 80
anchors.topMargin: 80
colorScheme: root.colorScheme
secondary: true
text: qsTr("Back")
visible: signInItem.currentIndex != 0
onClicked: {
signInItem.abort();
}
}
}
GridLayout {
Layout.fillHeight: true
Layout.fillWidth: true
columnSpacing: 0
columns: 3
rowSpacing: 0
// top margin
Item {
Layout.columnSpan: 3
Layout.fillWidth: true
// Using binding component here instead of direct binding to avoid binding loop during construction of element
Binding on Layout.preferredHeight {
value: (parent.height - signInItem.height) / 4
}
}
// left margin
Item {
Layout.fillWidth: true
Layout.maximumWidth: 80
Layout.minimumWidth: 48
Layout.preferredHeight: signInItem.height
}
SignIn {
id: signInItem
Layout.fillWidth: true
Layout.preferredWidth: 320
colorScheme: root.colorScheme
focus: true
username: Backend.users.count === 1 && Backend.users.get(0) && (Backend.users.get(0).state === EUserState.SignedOut) ? Backend.users.get(0).username : ""
}
// Right margin
Item {
Layout.fillWidth: true
Layout.maximumWidth: 80
Layout.minimumWidth: 48
Layout.preferredHeight: signInItem.height
}
// bottom margin
Item {
Layout.columnSpan: 3
Layout.fillHeight: true
Layout.fillWidth: true
}
}
Item {
Layout.fillHeight: true
Layout.preferredWidth: signInItem.currentIndex === 0 ? 0 : parent.width / 4
}
}
}
}
}

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 128 128" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1.6,0,0,1.6,9.6,5.6)">
<path d="M34,0C15.497,0 0.5,14.852 0.5,33.176L0.5,66.572C0.5,70.122 3.406,73 6.991,73L61.702,73C64.903,73 67.5,70.428 67.5,67.258L67.5,33.176C67.5,14.855 52.503,0 34,0ZM53.283,32.992L38.641,45.207C35.998,47.413 32.133,47.413 29.49,45.207L14.848,32.992C14.848,22.625 23.334,14.221 33.802,14.221L34.329,14.221C44.796,14.221 53.283,22.625 53.283,32.992Z" style="fill:rgb(109,74,255);fill-rule:nonzero;"/>
<path d="M34,0C15.497,0 0.5,14.852 0.5,33.176L0.5,66.572C0.5,70.122 3.406,73 6.991,73L61.702,73C64.903,73 67.5,70.428 67.5,67.258L67.5,33.176C67.5,14.855 52.503,0 34,0ZM53.283,32.992L38.641,45.207C35.998,47.413 32.133,47.413 29.49,45.207L14.848,32.992C14.848,22.625 23.334,14.221 33.802,14.221L34.329,14.221C44.796,14.221 53.283,22.625 53.283,32.992Z" style="fill:url(#_Linear1);fill-rule:nonzero;"/>
<path d="M38.664,45.355C37.138,46.579 33.168,48.292 29.504,45.355C25.841,42.418 18.237,35.924 14.893,33.045L14.911,33.045L14.848,32.992C14.848,22.625 23.334,14.221 33.802,14.221L34.329,14.221C44.796,14.221 53.283,22.625 53.283,32.992L53.219,33.045L53.276,33.045L53.276,73L61.702,73C64.903,73 67.5,70.428 67.5,67.258L67.5,33.176C67.5,14.855 52.503,0 34,0C15.497,0 0.5,14.852 0.5,33.176L0.5,35.274L21.217,52.914C22.744,54.354 26.757,56.37 30.595,52.914C34.433,49.459 37.574,46.435 38.664,45.355Z" style="fill:url(#_Radial2);fill-rule:nonzero;"/>
</g>
<defs>
<linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(9.85984,-28.1373,28.1373,9.85984,2.89896,81.4231)"><stop offset="0" style="stop-color:rgb(40,176,232);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(197,183,255);stop-opacity:0"/></linearGradient>
<radialGradient id="_Radial2" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-63.0498,-56.1536,45.8629,-51.4953,63.5497,79.0473)"><stop offset="0" style="stop-color:rgb(226,219,255);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(109,74,255);stop-opacity:1"/></radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.8363 13.87C11.0407 13.6842 11.0557 13.368 10.87 13.1637L6.17573 8L10.87 2.83633C11.0557 2.632 11.0407 2.31578 10.8363 2.13003C10.632 1.94427 10.3158 1.95933 10.13 2.16366L5.13003 7.66366C4.95666 7.85437 4.95666 8.14562 5.13003 8.33633L10.13 13.8363C10.3158 14.0407 10.632 14.0557 10.8363 13.87Z" fill="#0C0C14"/>
</svg>

After

Width:  |  Height:  |  Size: 469 B

View File

@ -1,3 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M14 2L13.2427 2L13.2427 1.99998L13.2426 2L13 2L9 2V3H12.2426L5.76613 9.47651L6.47324 10.1836L13 3.65686V7H14V3V2ZM2 2H5V3H3L3 13L13 13V11H14V14H13H3H2V13V3V2Z" fill="white"/>
<path d="M12 4.72447L12 8.5C12 8.77614 11.7761 9 11.5 9C11.2239 9 11 8.77614 11 8.5V5.70711L5.35355 11.3536C5.15829 11.5488 4.84171 11.5488 4.64645 11.3536C4.45118 11.1583 4.45118 10.8417 4.64645 10.6464L10.2929 5H7.50002C7.22387 5 7.00002 4.77614 7.00002 4.5C7.00002 4.22386 7.22387 4 7.50002 4L11.2761 3.99999C11.3175 3.99994 11.3769 3.99987 11.4328 4.00452C11.5821 3.98436 11.7388 4.03167 11.8536 4.14645C11.9683 4.26124 12.0157 4.41796 11.9955 4.56731C12.0001 4.62322 12.0001 4.68283 12 4.72447Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3 1C1.89543 1 1 1.89543 1 3V13C1 14.1046 1.89543 15 3 15H13C14.1046 15 15 14.1046 15 13V3C15 1.89543 14.1046 1 13 1H3ZM13 2H3C2.44772 2 2 2.44772 2 3V13C2 13.5523 2.44772 14 3 14H13C13.5523 14 14 13.5523 14 13V3C14 2.44772 13.5523 2 13 2Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 327 B

After

Width:  |  Height:  |  Size: 924 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 436 KiB

View File

@ -0,0 +1,11 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.9864 4.0315C14.3445 1.75773 17.6381 1.75773 18.9961 4.0315L31.232 24.5182C32.6254 26.8512 30.9445 29.8129 28.2272 29.8129H3.75539C1.03801 29.8129 -0.642844 26.8512 0.75053 24.5182L12.9864 4.0315Z" fill="url(#paint0_linear_4081_29778)"/>
<path d="M14.5387 12.1944C14.5432 11.3954 15.1922 10.75 15.9912 10.75C16.7903 10.75 17.4392 11.3954 17.4437 12.1944L17.4829 19.25C17.4875 20.0771 16.8183 20.75 15.9912 20.75C15.1641 20.75 14.4949 20.0771 14.4995 19.25L14.5387 12.1944Z" fill="white"/>
<path d="M17.4912 23.75C17.4912 24.5784 16.8196 25.25 15.9912 25.25C15.1628 25.25 14.4912 24.5784 14.4912 23.75C14.4912 22.9216 15.1628 22.25 15.9912 22.25C16.8196 22.25 17.4912 22.9216 17.4912 23.75Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_4081_29778" x1="15.9913" y1="2.09253" x2="15.9913" y2="42.104" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFB800"/>
<stop offset="1" stop-color="#FF8419"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,151 @@
<svg width="270" height="104" viewBox="0 0 270 104" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M179.366 102.5H3.05083C1.33939 102.5 0 101.161 0 99.4493V97.7378C0 96.0264 1.33939 94.687 3.05083 94.687H179.366C181.077 94.687 182.417 96.0264 182.417 97.7378V99.4493C182.417 101.161 181.003 102.5 179.366 102.5Z" fill="#B0D4E5"/>
<path d="M164.229 94.6867H16.6963V9.42924C16.6963 4.51816 20.7144 0.5 25.6255 0.5H155.3C160.211 0.5 164.229 4.51816 164.229 9.42924V94.6867Z" fill="url(#paint0_radial_4147_15491)"/>
<path d="M157.311 94.6862H24.3628V10.8426C24.3628 9.65203 25.3301 8.68469 26.5207 8.68469H155.079C156.269 8.68469 157.237 9.65203 157.237 10.8426V94.6862H157.311Z" fill="url(#paint1_linear_4147_15491)"/>
<rect opacity="0.5" x="29" y="66" width="123" height="24" rx="3" fill="white"/>
<rect x="29" y="14" width="123" height="48" rx="3" fill="white"/>
<path d="M46.686 70.0266H55.3141C57.3414 70.0266 58.9735 71.6587 58.9735 73.686V82.3141C58.9735 84.3414 57.3414 85.9734 55.3141 85.9734H46.686C44.6587 85.9734 43.0266 84.3414 43.0266 82.3141V73.686C43.0266 71.6587 44.6587 70.0266 46.686 70.0266Z" fill="url(#paint2_linear_4147_15491)"/>
<path d="M46.5205 74.5615C46.4365 74.5615 46.3573 74.576 46.2824 74.6058L47.783 76.1506L49.3001 77.7232L49.3278 77.7564L49.4164 77.845L49.505 77.9391L50.8062 79.2735C50.8279 79.287 50.8907 79.3452 50.9397 79.3697C51.0029 79.4013 51.0714 79.4304 51.142 79.4329C51.2182 79.4356 51.296 79.4138 51.3646 79.3805C51.4159 79.3555 51.4387 79.3197 51.4984 79.2735L53.0045 77.7176L55.9945 74.639C55.9003 74.5879 55.796 74.5615 55.6844 74.5615H46.5205ZM46.0609 74.7497C45.9009 74.9014 45.8007 75.1293 45.8007 75.3865V80.4585C45.8007 80.6667 45.8676 80.8559 45.9779 81.0011L46.1883 80.8018L47.7553 79.2791L49.1451 77.9336L49.1174 77.9003L47.5947 76.3333L46.072 74.7608L46.0609 74.7497ZM56.1938 74.7996L53.1927 77.9003L53.165 77.928L54.6102 79.3289L56.1772 80.8516L56.2714 80.9402C56.3557 80.8048 56.4043 80.6381 56.4043 80.4585V75.3865C56.4043 75.1572 56.3248 74.9492 56.1938 74.7996ZM49.3223 78.1163L47.938 79.4618L46.3655 80.9845L46.1661 81.1783C46.2712 81.246 46.3908 81.2891 46.5205 81.2891H55.6844C55.8403 81.2891 55.9818 81.2291 56.0997 81.134L56 81.0343L54.4275 79.5116L52.9823 78.1163L51.6811 79.4563C51.6107 79.503 51.5636 79.5547 51.4949 79.5865C51.3842 79.6377 51.2629 79.681 51.141 79.6791C51.0187 79.6773 50.8988 79.6294 50.7891 79.5755C50.7341 79.5484 50.7047 79.5215 50.6401 79.4673L49.3223 78.1163Z" fill="white"/>
<g clip-path="url(#clip0_4147_15491)">
<path d="M126.645 73.4143H126.646C127.138 71.6949 129.281 70.8555 131.465 70.8555C132.974 70.8555 134.329 71.3334 135.258 72.0918C134.682 72.1203 134.135 72.2329 133.634 72.4146C134.385 72.6933 135.029 73.1225 135.507 73.6535C135.19 73.5987 134.861 73.5699 134.524 73.5699C134.488 73.5699 134.452 73.5702 134.415 73.5709C135.283 74.8295 135.792 76.3554 135.792 78C135.792 82.3135 132.295 85.8103 127.981 85.8103C123.734 85.8103 120.171 82.2543 120.171 78C120.171 77.3281 120.261 76.6341 120.434 75.9837C120.479 75.8471 120.543 75.7161 120.627 75.6681C120.732 75.6081 120.828 75.787 120.844 75.8452C120.958 76.275 121.112 76.6906 121.302 77.0887C121.286 76.1969 121.667 75.3848 122.191 74.6821C122.54 74.2138 122.864 73.7796 123.014 72.5271C123.024 72.443 123.103 72.3825 123.184 72.4089C124.321 72.7812 124.929 74.6748 124.834 76.2583C125.463 76.3481 125.46 75.6919 125.46 75.6919C125.259 75.0747 125.393 73.9275 126.643 73.4143H126.645Z" fill="url(#paint3_linear_4147_15491)"/>
<path opacity="0.9" d="M135.536 76.011C135.726 80.3067 132.195 84.0103 127.888 84.0103C123.857 84.0103 120.554 80.8945 120.255 76.9398C120.203 77.3052 120.174 77.6783 120.171 78.0576C120.202 82.2895 123.757 85.8103 127.981 85.8103C132.295 85.8103 135.792 82.3135 135.792 78C135.792 77.3126 135.703 76.646 135.536 76.011Z" fill="url(#paint4_radial_4147_15491)"/>
<g style="mix-blend-mode:screen">
<path d="M127.792 73.9835C127.707 73.8346 127.319 73.6144 127.149 73.576C127.792 71.5174 131.066 70.8854 133.07 71.2497C133.904 71.4012 134.942 71.8559 135.258 72.0918C134.33 71.3334 132.975 70.8555 131.466 70.8555C129.282 70.8555 127.138 71.6949 126.647 73.4143H126.645H126.643C125.393 73.9275 125.26 75.0751 125.46 75.6922C125.653 74.9561 126.571 74.0514 127.792 73.9835Z" fill="url(#paint5_radial_4147_15491)"/>
</g>
<path d="M130.061 72.5619C128.306 72.9071 127.733 73.0199 127.146 73.5781C127.805 71.8322 129.489 71.4785 131.494 72.2742C130.942 72.3884 130.47 72.4814 130.061 72.5619Z" fill="url(#paint6_linear_4147_15491)"/>
<path d="M120.595 75.7446C120.116 77.7069 120.486 80.0134 122.664 81.949C122.015 81.2402 121.224 78.6226 122.971 76.7525C123.088 76.6265 123.291 76.7192 123.297 76.8914C123.441 80.7778 126.577 83.1514 130.192 82.7076C129.072 82.6446 125.367 81.3471 128.123 80.8337C129.564 80.5654 131.822 80.1446 131.822 78.1182C131.822 74.8328 129.282 73.8722 127.742 74.0151C126.688 74.113 125.749 74.7819 125.46 75.6916C125.571 76.0503 125.129 76.3014 124.834 76.2593C124.929 74.6758 124.321 72.7812 123.184 72.4088C123.103 72.3825 123.024 72.443 123.014 72.5271C122.864 73.7796 122.54 74.2137 122.191 74.6821C121.667 75.3848 121.286 76.1968 121.302 77.0887C121.112 76.6906 120.958 76.2749 120.844 75.8452C120.831 75.797 120.761 75.6626 120.677 75.6562C120.631 75.6528 120.607 75.6974 120.595 75.7446Z" fill="url(#paint7_radial_4147_15491)"/>
<g style="mix-blend-mode:screen">
<path d="M127.156 81.0327C129.277 82.7547 133.542 81.4637 133.542 77.2765C131.82 79.8867 129.627 81.687 127.156 81.0327Z" fill="url(#paint8_linear_4147_15491)"/>
</g>
<g style="mix-blend-mode:screen">
<path d="M122.971 76.7525C123.015 76.7045 123.072 76.6881 123.126 76.6956C121.563 78.6023 122.824 81.9512 123.689 82.7739C123.738 82.9109 122.868 82.1985 122.749 82.0316C122.09 81.4731 121.147 78.7053 122.971 76.7525Z" fill="url(#paint9_linear_4147_15491)"/>
</g>
<path d="M127.982 81.1904C130.103 81.1904 131.823 79.7872 131.823 78.0562C131.823 76.3252 130.103 74.922 127.982 74.922C126.172 74.922 124.14 76.0994 124.14 78.102C124.141 81.1965 127.411 82.9768 130.197 82.7073C129.988 82.683 128.682 82.6136 127.799 81.6152C127.72 81.5252 127.582 81.3682 127.644 81.2661C127.707 81.1641 127.879 81.1904 127.982 81.1904Z" fill="url(#paint10_linear_4147_15491)"/>
<path opacity="0.6" d="M131.404 76.6322L128.369 79.5368C128.099 79.7282 127.812 79.7423 127.529 79.5685L124.552 76.6427C124.636 76.5075 124.732 76.3779 124.838 76.2549C124.945 76.3552 125.049 76.4525 125.15 76.5474C125.932 77.28 126.564 77.8721 127.455 78.6325C127.858 78.9758 127.983 78.969 128.377 78.6325C129.396 77.7623 130.142 77.1038 131.111 76.2385C131.22 76.3633 131.318 76.4948 131.404 76.6322Z" fill="white"/>
<mask id="mask0_4147_15491" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="124" y="76" width="8" height="7">
<path d="M131.823 78.0561C131.823 79.7871 130.103 81.1903 127.982 81.1903C127.88 81.1903 127.707 81.164 127.645 81.266C127.582 81.3681 127.72 81.5251 127.8 81.6151C128.629 82.5532 129.832 82.6711 130.148 82.7021C130.168 82.7041 130.185 82.7057 130.198 82.7072C127.411 82.9767 124.142 81.1964 124.141 78.1019C124.141 77.5512 124.294 77.0629 124.554 76.6443L127.546 79.3617C127.759 79.5551 128.118 79.5551 128.331 79.3617L131.379 76.5928C131.663 77.0296 131.823 77.5276 131.823 78.0561Z" fill="white"/>
</mask>
<g mask="url(#mask0_4147_15491)">
<path opacity="0.7" d="M132.96 74.4413H123.251V83.244H132.96V74.4413Z" fill="url(#paint11_linear_4147_15491)"/>
<g filter="url(#filter0_f_4147_15491)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M127.091 79.3802C126.525 78.7914 124.831 76.8357 124.831 76.8357L124.962 76.8413L127.614 78.8223C127.813 78.9667 128.096 78.9652 128.293 78.8189L130.894 76.8439L131.032 76.8331C131.032 76.8331 129.393 78.7426 128.765 79.3717C128.138 80.0007 127.656 79.969 127.091 79.3802Z" fill="#458FCD"/>
</g>
</g>
<path d="M128.52 73.3543C128.931 73.2248 128.895 72.8179 128.895 72.8179C128.895 72.8179 128.69 72.5759 128.282 72.71C127.901 72.8356 127.842 73.107 127.842 73.107C127.842 73.107 128.05 73.5023 128.52 73.3543Z" fill="white"/>
</g>
<path d="M96.2971 71H87.7017C87.3166 71 86.9998 71.3169 86.9998 71.702V72.5L91.8444 74L96.9991 72.5V71.702C96.9991 71.3169 96.6822 71 96.2971 71Z" fill="#0364B8"/>
<path d="M97.8241 78.7025C97.8973 78.4723 97.9558 78.2377 97.9991 78C97.9991 77.8811 97.9356 77.7709 97.8326 77.7115L97.8261 77.708L97.8241 77.7071L92.4054 74.62C92.382 74.6049 92.3578 74.591 92.3329 74.5785C92.1231 74.4745 91.8763 74.4745 91.6665 74.5785C91.6416 74.591 91.6174 74.6048 91.594 74.62L86.1753 77.7071L86.1733 77.708L86.1668 77.7115C86.0639 77.7709 86.0003 77.8811 86.0004 78C86.0436 78.2377 86.1021 78.4723 86.1753 78.7025L91.9209 82.905L97.8241 78.7025Z" fill="#0A2767"/>
<path d="M93.9993 72.5H90.4995L89.489 74.0001L90.4995 75.5L93.9993 78.5H96.999V75.5L93.9993 72.5Z" fill="#28A8EA"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M86.9998 72.5H90.4995V75.5H86.9998V72.5Z" fill="#0078D4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M93.9993 72.5H96.9991V75.5H93.9993V72.5Z" fill="#50D9FF"/>
<path d="M93.9993 78.5L90.4995 75.5H86.9998V78.5L90.4995 81.5L95.9152 82.384L93.9993 78.5Z" fill="#0364B8"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M90.4995 75.5H93.9993V78.5H90.4995V75.5Z" fill="#0078D4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M86.9998 78.5H90.4995V81.5H86.9998V78.5Z" fill="#064A8C"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M93.9993 78.5H96.9991V81.5H93.9993V78.5Z" fill="#0078D4"/>
<path d="M92.0949 82.6091L86.1982 78.3091L86.4452 77.8746C86.4452 77.8746 91.8174 80.9346 91.8993 80.9806C91.9673 81.0079 92.0436 81.0057 92.1099 80.9746C92.1863 80.9316 97.5755 77.8596 97.5755 77.8596L97.8235 78.2941L92.0949 82.6091Z" fill="#0A2767" fill-opacity="0.498039"/>
<path d="M97.8325 78.2886L97.826 78.2926L97.8245 78.2936L92.4059 81.3806C92.1872 81.5216 91.9103 81.5388 91.6759 81.4261L93.5632 83.9566L97.69 84.8551V84.8571C97.8846 84.7164 98 84.4903 98 84.2501V78.0001C98 78.119 97.9364 78.2292 97.8335 78.2886H97.8325Z" fill="#1490DF"/>
<path d="M97.999 84.2502V83.8813L93.0078 81.0372L92.4053 81.3802C92.1868 81.5212 91.9098 81.5385 91.6754 81.4257L93.5628 83.9563L97.6895 84.8547V84.8567C97.8841 84.7159 97.9995 84.4899 97.9995 84.2497L97.999 84.2502Z" fill="black" fill-opacity="0.047059"/>
<path d="M97.974 84.4417L92.5028 81.3247L92.4053 81.3802C92.1868 81.5212 91.9098 81.5385 91.6754 81.4257L93.5628 83.9562L97.6895 84.8547V84.8567C97.8295 84.7552 97.9303 84.6083 97.9745 84.4412L97.974 84.4417Z" fill="black" fill-opacity="0.098039"/>
<path d="M86.1746 78.2951V78.2901H86.1697L86.1546 78.2801C86.0576 78.2204 85.9987 78.114 85.9996 78.0001V84.2511C85.9996 84.662 86.3378 85.0001 86.7487 85.0001H97.249C97.3114 84.9995 97.3736 84.9911 97.434 84.9751C97.4653 84.9697 97.4956 84.9595 97.524 84.9451C97.5346 84.9441 97.5448 84.9406 97.554 84.9351C97.5948 84.9184 97.6335 84.8966 97.669 84.8701L97.689 84.8551L86.1746 78.2951Z" fill="#28A8EA"/>
<path d="M90.9995 82.3337V74.1672C90.9984 73.802 90.6982 73.5018 90.333 73.5007H87.0152V77.2287L86.1747 77.7077L86.1728 77.7087L86.1663 77.7122C86.0633 77.7717 85.9998 77.8819 85.9998 78.0007V83.0022V83.0002H90.333C90.6982 82.9992 90.9984 82.6989 90.9995 82.3337Z" fill="black" fill-opacity="0.098039"/>
<path d="M90.4995 82.8336V74.6671C90.4984 74.3019 90.1982 74.0018 89.8331 74.0006H87.0152V77.2286L86.1747 77.7076L86.1728 77.7086L86.1663 77.7121C86.0633 77.7716 85.9998 77.8818 85.9998 78.0006V83.5022V83.5001H89.8331C90.1982 83.499 90.4984 83.1989 90.4995 82.8336ZM90.4995 81.8336V74.6671C90.4984 74.3019 90.1982 74.0018 89.8331 74.0006H87.0152V77.2286L86.1747 77.7076L86.1728 77.7086L86.1663 77.7121C86.0633 77.7716 85.9998 77.8818 85.9998 78.0006V82.5021V82.5001H89.8331C90.1982 82.499 90.4984 82.1989 90.4995 81.8336ZM89.9995 81.8336V74.6671C89.9984 74.3019 89.6983 74.0018 89.3331 74.0006H87.0152V77.2286L86.1747 77.7076L86.1728 77.7086L86.1663 77.7121C86.0633 77.7716 85.9998 77.8818 85.9998 78.0006V82.5021V82.5001H89.3331C89.6983 82.499 89.9984 82.1989 89.9995 81.8336Z" fill="black" fill-opacity="0.2"/>
<path d="M82.6665 74.0002H89.3325C89.6981 74.0002 89.999 74.3011 89.999 74.6667V81.3332C89.999 81.6988 89.6981 81.9997 89.3325 81.9997H82.6665C82.3009 81.9997 82 81.6988 82 81.3332V74.6667C82 74.3011 82.3009 74.0002 82.6665 74.0002Z" fill="#0078D4"/>
<path d="M83.9334 76.7341C84.1105 76.3567 84.3964 76.0409 84.7543 75.8272C85.1509 75.6001 85.6025 75.487 86.0593 75.5002C86.4823 75.4909 86.8999 75.5981 87.2662 75.8102C87.6108 76.0154 87.8884 76.3165 88.0651 76.6767C88.2575 77.0735 88.3535 77.5102 88.3451 77.9512C88.3545 78.4121 88.2556 78.8688 88.0567 79.2847C87.8761 79.6576 87.5901 79.9696 87.2342 80.1817C86.8537 80.4004 86.4205 80.5106 85.9817 80.5002C85.5495 80.5105 85.1227 80.402 84.7479 80.1867C84.4005 79.981 84.1196 79.6796 83.9389 79.3187C83.7452 78.9275 83.6481 78.4956 83.6559 78.0592C83.6473 77.6022 83.7424 77.1492 83.9339 76.7341H83.9334ZM84.8083 78.8632C84.9028 79.1018 85.063 79.3089 85.2703 79.4601C85.4813 79.6079 85.7343 79.6842 85.9917 79.6777C86.266 79.6885 86.5365 79.6099 86.7622 79.4537C86.967 79.3024 87.123 79.0942 87.2107 78.8552C87.3093 78.5887 87.3579 78.3062 87.3542 78.0222C87.3572 77.7356 87.3116 77.4505 87.2192 77.1792C87.1377 76.935 86.9873 76.7194 86.7862 76.5587C86.5665 76.3946 86.2967 76.3113 86.0228 76.3227C85.7597 76.3159 85.5011 76.3925 85.2843 76.5417C85.0731 76.6935 84.9093 76.9023 84.8123 77.1437C84.5977 77.6964 84.5965 78.3097 84.8088 78.8632H84.8083Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M37 24C37 22.3431 38.3431 21 40 21H42C43.6569 21 45 22.3431 45 24V26C45 27.6569 43.6569 29 42 29H40C38.3431 29 37 27.6569 37 26V24ZM47 22.5C47 21.6716 47.6716 21 48.5 21H89.5C90.3284 21 91 21.6716 91 22.5C91 23.3284 90.3284 24 89.5 24H48.5C47.6716 24 47 23.3284 47 22.5ZM48.5 26C47.6716 26 47 26.6716 47 27.5C47 28.3284 47.6716 29 48.5 29H81.5C82.3284 29 83 28.3284 83 27.5C83 26.6716 82.3284 26 81.5 26H48.5ZM37 36C37 34.3431 38.3431 33 40 33H42C43.6569 33 45 34.3431 45 36V38C45 39.6569 43.6569 41 42 41H40C38.3431 41 37 39.6569 37 38V36ZM40 45C38.3431 45 37 46.3431 37 48V50C37 51.6569 38.3431 53 40 53H42C43.6569 53 45 51.6569 45 50V48C45 46.3431 43.6569 45 42 45H40ZM47 34.5C47 33.6716 47.6716 33 48.5 33H89.5C90.3284 33 91 33.6716 91 34.5C91 35.3284 90.3284 36 89.5 36H48.5C47.6716 36 47 35.3284 47 34.5ZM48.5 45C47.6716 45 47 45.6716 47 46.5C47 47.3284 47.6716 48 48.5 48H89.5C90.3284 48 91 47.3284 91 46.5C91 45.6716 90.3284 45 89.5 45H48.5ZM47 39.5C47 38.6716 47.6716 38 48.5 38H81.5C82.3284 38 83 38.6716 83 39.5C83 40.3284 82.3284 41 81.5 41H48.5C47.6716 41 47 40.3284 47 39.5ZM48.5 50C47.6716 50 47 50.6716 47 51.5C47 52.3284 47.6716 53 48.5 53H81.5C82.3284 53 83 52.3284 83 51.5C83 50.6716 82.3284 50 81.5 50H48.5Z" fill="#B0D4E5"/>
<circle cx="176" cy="53.5" r="2" fill="#B0D4E5"/>
<circle cx="184" cy="53.5" r="2" fill="#B0D4E5"/>
<circle cx="192" cy="53.5" r="2" fill="#B0D4E5"/>
<circle cx="200" cy="53.5" r="2" fill="#B0D4E5"/>
<path d="M233.817 30.5C223.274 30.5 214.729 39.0451 214.729 49.5874V68.8014C214.729 70.8441 216.385 72.5 218.428 72.5H249.601C251.424 72.5 252.904 71.0203 252.904 69.1965V49.5874C252.904 39.0465 244.359 30.5 233.817 30.5ZM244.804 49.4814L236.461 56.5096C234.955 57.7787 232.753 57.7787 231.247 56.5096L222.904 49.4814C222.904 43.5172 227.74 38.6817 233.704 38.6817H234.004C239.968 38.6817 244.804 43.5172 244.804 49.4814Z" fill="#6D4AFF"/>
<path d="M233.817 30.5C223.274 30.5 214.729 39.0451 214.729 49.5874V68.8014C214.729 70.8441 216.385 72.5 218.428 72.5H249.601C251.424 72.5 252.904 71.0203 252.904 69.1965V49.5874C252.904 39.0465 244.359 30.5 233.817 30.5ZM244.804 49.4814L236.461 56.5096C234.955 57.7787 232.753 57.7787 231.247 56.5096L222.904 49.4814C222.904 43.5172 227.74 38.6817 233.704 38.6817H234.004C239.968 38.6817 244.804 43.5172 244.804 49.4814Z" fill="url(#paint12_linear_4147_15491)"/>
<g filter="url(#filter1_i_4147_15491)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M236.474 56.5948C235.604 57.2989 233.343 58.2847 231.255 56.5948C229.168 54.9048 224.835 51.1687 222.93 49.5119H222.94L222.904 49.4814C222.904 43.5172 227.74 38.6817 233.704 38.6817H234.004C239.968 38.6817 244.804 43.5172 244.804 49.4814L244.767 49.5119H244.8V72.5H249.601C251.424 72.5 252.904 71.0203 252.904 69.1965V49.5874C252.904 39.0465 244.359 30.5 233.817 30.5C223.274 30.5 214.729 39.0451 214.729 49.5874V50.7946L226.533 60.9439C227.403 61.7723 229.69 62.9321 231.877 60.9439C234.064 58.9557 235.853 57.2161 236.474 56.5948Z" fill="url(#paint13_radial_4147_15491)"/>
</g>
<circle cx="254.278" cy="27.7778" r="15.2778" fill="url(#paint14_linear_4147_15491)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M260.702 24.1676C261.113 24.5761 261.116 25.2412 260.707 25.653L253.128 33.2918C252.93 33.4906 252.662 33.6024 252.382 33.6024C252.102 33.6024 251.834 33.4906 251.636 33.2918L247.758 29.383C247.349 28.9713 247.352 28.3062 247.764 27.8976C248.175 27.489 248.84 27.4916 249.249 27.9034L252.382 31.0608L259.216 24.1733C259.625 23.7615 260.29 23.759 260.702 24.1676Z" fill="white"/>
<defs>
<filter id="filter0_f_4147_15491" x="111.934" y="63.936" width="31.9953" height="28.7937" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="6.44854" result="effect1_foregroundBlur_4147_15491"/>
</filter>
<filter id="filter1_i_4147_15491" x="214.137" y="30.5" width="38.7676" height="43.6537" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="-0.592742" dy="1.65375"/>
<feGaussianBlur stdDeviation="4.44556"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.462745 0 0 0 0 0.337255 0 0 0 0 1 0 0 0 0.24 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_4147_15491"/>
</filter>
<radialGradient id="paint0_radial_4147_15491" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(160.195 7) rotate(145.641) scale(59.357 92.9759)">
<stop stop-color="#292842"/>
<stop offset="1" stop-color="#38385F"/>
</radialGradient>
<linearGradient id="paint1_linear_4147_15491" x1="21.9037" y1="100.487" x2="165.819" y2="14.2507" gradientUnits="userSpaceOnUse">
<stop stop-color="#35168C"/>
<stop offset="0.317708" stop-color="#FF5454"/>
<stop offset="0.46875" stop-color="#FFDD64"/>
<stop offset="0.677083" stop-color="#BCE6FF"/>
<stop offset="0.911458" stop-color="#6983EF"/>
<stop offset="1" stop-color="#2395FF"/>
</linearGradient>
<linearGradient id="paint2_linear_4147_15491" x1="51.1117" y1="85.9094" x2="51.1272" y2="70.2191" gradientUnits="userSpaceOnUse">
<stop stop-color="#70EFFF"/>
<stop offset="1" stop-color="#5770FF"/>
</linearGradient>
<linearGradient id="paint3_linear_4147_15491" x1="122.882" y1="73.4298" x2="133.892" y2="83.6998" gradientUnits="userSpaceOnUse">
<stop stop-color="#1B91F3"/>
<stop offset="1" stop-color="#0B68CB"/>
</linearGradient>
<radialGradient id="paint4_radial_4147_15491" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(122.862 76.9635) rotate(66.5179) scale(6.60435 6.32678)">
<stop offset="0.525579" stop-color="#0B4186" stop-opacity="0"/>
<stop offset="1" stop-color="#0B4186" stop-opacity="0.45"/>
</radialGradient>
<radialGradient id="paint5_radial_4147_15491" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(128.183 74.2511) rotate(-127.994) scale(1.41191 2.33636)">
<stop stop-color="#EF3ACC" stop-opacity="0"/>
<stop offset="1" stop-color="#EF3ACC" stop-opacity="0.64"/>
</radialGradient>
<linearGradient id="paint6_linear_4147_15491" x1="125.946" y1="76.0588" x2="129.907" y2="71.6484" gradientUnits="userSpaceOnUse">
<stop stop-color="#0F5DB0"/>
<stop offset="1" stop-color="#0F5DB0" stop-opacity="0"/>
</linearGradient>
<radialGradient id="paint7_radial_4147_15491" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(125.036 82.2554) rotate(-64.2627) scale(10.3034 12.7276)">
<stop offset="0.0160882" stop-color="#094188"/>
<stop offset="0.967387" stop-color="#0B4186" stop-opacity="0"/>
</radialGradient>
<linearGradient id="paint8_linear_4147_15491" x1="132.89" y1="79.2522" x2="131.048" y2="83.7752" gradientUnits="userSpaceOnUse">
<stop stop-color="#E247C4" stop-opacity="0"/>
<stop offset="1" stop-color="#E247C4" stop-opacity="0.64"/>
</linearGradient>
<linearGradient id="paint9_linear_4147_15491" x1="121.466" y1="75.1958" x2="123.089" y2="81.7279" gradientUnits="userSpaceOnUse">
<stop offset="0.104632" stop-color="#EF3ACC"/>
<stop offset="1" stop-color="#EF3ACC" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint10_linear_4147_15491" x1="127.982" y1="76.0698" x2="127.982" y2="82.6751" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="0.90535" stop-color="#BEE1FE"/>
<stop offset="1" stop-color="#96CEFD"/>
</linearGradient>
<linearGradient id="paint11_linear_4147_15491" x1="128.105" y1="79.8075" x2="128.105" y2="82.5745" gradientUnits="userSpaceOnUse">
<stop stop-color="#BCE0FD"/>
<stop offset="1" stop-color="#88CCFC"/>
</linearGradient>
<linearGradient id="paint12_linear_4147_15491" x1="216.096" y1="77.3462" x2="221.812" y2="61.1923" gradientUnits="userSpaceOnUse">
<stop stop-color="#28B0E8"/>
<stop offset="1" stop-color="#C5B7FF" stop-opacity="0"/>
</linearGradient>
<radialGradient id="paint13_radial_4147_15491" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(250.653 75.9793) rotate(-138.034) scale(48.3148 39.5031)">
<stop stop-color="#E2DBFF"/>
<stop offset="1" stop-color="#6D4AFF"/>
</radialGradient>
<linearGradient id="paint14_linear_4147_15491" x1="255.861" y1="10.272" x2="256.004" y2="43.0557" gradientUnits="userSpaceOnUse">
<stop stop-color="#2AF091"/>
<stop offset="1" stop-color="#00C5A1"/>
</linearGradient>
<clipPath id="clip0_4147_15491">
<rect width="16" height="16" fill="white" transform="translate(120 70)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,33 @@
<svg width="190" height="104" viewBox="0 0 190 104" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M95.6666 29C78.0961 29 63.8542 43.2418 63.8542 60.8123V92.8357C63.8542 96.2402 66.6141 99 70.0185 99H121.973C125.013 99 127.479 96.5338 127.479 93.4941V60.8123C127.479 43.2441 113.237 29 95.6666 29ZM113.978 60.6357L100.074 72.3494C97.5638 74.4646 93.8933 74.4646 91.3835 72.3494L77.4789 60.6357C77.4789 50.6953 85.5381 42.6362 95.4785 42.6362H95.9786C105.919 42.6362 113.978 50.6953 113.978 60.6357Z" fill="#6D4AFF"/>
<path d="M95.6666 29C78.0961 29 63.8542 43.2418 63.8542 60.8123V92.8357C63.8542 96.2402 66.6141 99 70.0185 99H121.973C125.013 99 127.479 96.5338 127.479 93.4941V60.8123C127.479 43.2441 113.237 29 95.6666 29ZM113.978 60.6357L100.074 72.3494C97.5638 74.4646 93.8933 74.4646 91.3835 72.3494L77.4789 60.6357C77.4789 50.6953 85.5381 42.6362 95.4785 42.6362H95.9786C105.919 42.6362 113.978 50.6953 113.978 60.6357Z" fill="url(#paint0_linear_4081_30418)"/>
<g filter="url(#filter0_i_4081_30418)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M100.096 72.4913C98.6461 73.6649 94.8768 75.3079 91.3975 72.4913C87.9183 69.6747 80.6973 63.4479 77.5218 60.6866H77.5393L77.479 60.6357C77.479 50.6953 85.5382 42.6362 95.4785 42.6362H95.9786C105.919 42.6362 113.978 50.6953 113.978 60.6357L113.918 60.6866H113.972V99H121.973C125.013 99 127.479 96.5338 127.479 93.4941V60.8123C127.479 43.2441 113.237 29 95.6666 29C78.0961 29 63.8542 43.2418 63.8542 60.8123V62.8243L83.5277 79.7398C84.9774 81.1205 88.7881 83.0534 92.4331 79.7398C96.078 76.4262 99.0603 73.5268 100.096 72.4913Z" fill="url(#paint1_radial_4081_30418)"/>
</g>
<ellipse cx="128.5" cy="35.9999" rx="16" ry="15.9999" fill="url(#paint2_linear_4081_30418)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M135.227 32.2191C135.659 32.647 135.661 33.3434 135.233 33.7747L127.295 41.7746C127.089 41.9828 126.808 42.0998 126.515 42.0998C126.221 42.0998 125.94 41.9828 125.734 41.7746L121.672 37.6811C121.244 37.2499 121.247 36.5534 121.678 36.1255C122.109 35.6976 122.806 35.7003 123.233 36.1315L126.515 39.4381L133.672 32.2251C134.1 31.7939 134.796 31.7912 135.227 32.2191Z" fill="white"/>
<defs>
<filter id="filter0_i_4081_30418" x="62.8663" y="29" width="64.6127" height="72.7562" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="-0.987903" dy="2.75625"/>
<feGaussianBlur stdDeviation="7.40927"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.462745 0 0 0 0 0.337255 0 0 0 0 1 0 0 0 0.24 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_4081_30418"/>
</filter>
<linearGradient id="paint0_linear_4081_30418" x1="66.1324" y1="107.077" x2="75.659" y2="80.1539" gradientUnits="userSpaceOnUse">
<stop stop-color="#28B0E8"/>
<stop offset="1" stop-color="#C5B7FF" stop-opacity="0"/>
</linearGradient>
<radialGradient id="paint1_radial_4081_30418" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(123.728 104.799) rotate(-138.034) scale(80.5247 65.8384)">
<stop stop-color="#E2DBFF"/>
<stop offset="1" stop-color="#6D4AFF"/>
</radialGradient>
<linearGradient id="paint2_linear_4081_30418" x1="130.158" y1="17.6667" x2="130.308" y2="52" gradientUnits="userSpaceOnUse">
<stop stop-color="#2AF091"/>
<stop offset="1" stop-color="#00C5A1"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 443 KiB

View File

@ -0,0 +1,34 @@
<svg width="134" height="36" viewBox="0 0 134 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_53_676)">
<path d="M38 21.259V24.924H40.5591V21.4191C40.5591 21.0789 40.6927 20.7502 40.9344 20.51C41.1732 20.2699 41.5002 20.1327 41.8386 20.1327H44.463C45.6857 20.1327 46.86 19.6438 47.7244 18.7719C48.5888 17.9028 49.075 16.7221 49.075 15.4928C49.075 14.2635 48.5888 13.0828 47.7244 12.2109C46.86 11.3418 45.6857 10.8529 44.4602 10.8529H38V15.4328H40.5591V13.2744H44.2896C44.8697 13.2744 45.4241 13.5059 45.8336 13.9176C46.243 14.3293 46.4733 14.8867 46.4733 15.4699C46.4733 16.0531 46.243 16.6106 45.8336 17.0223C45.4241 17.4339 44.8697 17.6655 44.2896 17.6655H41.5798C41.1107 17.6655 40.6444 17.757 40.2122 17.9399C39.7771 18.12 39.3847 18.3859 39.0521 18.7204C38.7194 19.0549 38.4578 19.4523 38.2758 19.8868C38.0938 20.3185 38 20.7873 38 21.259Z" fill="white"/>
<path d="M49.4703 24.924V19.3408C49.4703 17.0623 50.7925 15.2498 53.4397 15.2498C53.8633 15.2441 54.287 15.2898 54.7022 15.3899V17.6855C54.4008 17.6655 54.142 17.6655 54.0197 17.6655C52.6179 17.6655 52.0151 18.3116 52.0151 19.6209V24.924H49.4703Z" fill="white"/>
<path d="M55.4641 20.1899C55.4641 17.3882 57.5682 15.2527 60.4969 15.2527C63.4256 15.2527 65.5297 17.3882 65.5297 20.1899C65.5297 22.9915 63.4256 25.1471 60.4969 25.1471C57.5682 25.1471 55.4641 22.9886 55.4641 20.1899ZM63.0218 20.1899C63.0218 18.5975 61.9584 17.4683 60.4969 17.4683C59.0325 17.4683 57.972 18.5946 57.972 20.1899C57.972 21.8022 59.0354 22.9115 60.4969 22.9115C61.9612 22.9115 63.0218 21.7994 63.0218 20.1899Z" fill="white"/>
<path d="M73.6676 20.1899C73.6676 17.3882 75.7717 15.2527 78.7004 15.2527C81.6263 15.2527 83.7304 17.3882 83.7304 20.1899C83.7304 22.9915 81.6263 25.1471 78.7004 25.1471C75.7717 25.1471 73.6676 22.9886 73.6676 20.1899ZM81.2225 20.1899C81.2225 18.5975 80.1591 17.4683 78.6976 17.4683C77.2361 17.4683 76.1726 18.5946 76.1726 20.1899C76.1726 21.8022 77.2361 22.9115 78.6976 22.9115C80.1591 22.9115 81.2225 21.7994 81.2225 20.1899Z" fill="white"/>
<path d="M85.0526 24.9241V19.5438C85.0526 17.0452 86.6364 15.2498 89.4627 15.2498C92.2692 15.2498 93.8529 17.0423 93.8529 19.5438V24.9241H91.328V19.7439C91.328 18.3545 90.7053 17.4854 89.4627 17.4854C88.2202 17.4854 87.5975 18.3516 87.5975 19.7439V24.9241H85.0526Z" fill="white"/>
<path d="M72.9056 17.4881H70.1588V21.0159C70.1588 22.2452 70.5996 22.8084 71.862 22.8084C71.9815 22.8084 72.2829 22.8084 72.6639 22.7884V24.8639C72.1435 25.004 71.6829 25.0869 71.1796 25.0869C69.0556 25.0869 67.6112 23.7976 67.6112 21.359V17.4881H65.9051V15.4527H66.3316C66.4994 15.4527 66.6671 15.4184 66.8207 15.3555C66.9771 15.2897 67.1164 15.1954 67.2358 15.0753C67.3553 14.9552 67.4491 14.8152 67.5145 14.6579C67.5799 14.5007 67.6112 14.3349 67.6112 14.1662V12.2479H70.156V15.4527H72.9027V17.4881H72.9056Z" fill="white"/>
<path d="M98.8823 11.216H102.457L105.808 19.4391C106.107 20.1282 106.362 20.8315 106.577 21.5492H106.612C106.827 20.8315 107.083 20.1253 107.381 19.4391L110.732 11.216H114.307V24.9288H111.717V15.7421C111.714 15.4391 111.729 15.136 111.76 14.8358H111.717C111.637 15.1589 111.531 15.4762 111.396 15.7822L107.683 24.7687H105.535L101.811 15.7822C101.676 15.4734 101.561 15.1589 101.469 14.8358H101.429C101.458 15.136 101.472 15.4391 101.469 15.7421V24.9316H98.8823V11.216Z" fill="white"/>
<path d="M123.165 15.8823C123.915 16.274 124.535 16.8744 124.951 17.6092C125.396 18.407 125.62 19.3105 125.603 20.2225V24.9288H123.335L123.174 23.5164C122.878 24.0281 122.444 24.4456 121.922 24.7258C121.362 25.0146 120.736 25.1604 120.104 25.1461C119.297 25.1547 118.505 24.9402 117.813 24.5256C117.112 24.1025 116.544 23.4963 116.165 22.7758C115.751 21.981 115.545 21.0975 115.562 20.2025C115.55 19.3219 115.777 18.4556 116.217 17.6893C116.647 16.9459 117.27 16.334 118.025 15.9194C118.806 15.4877 119.685 15.2647 120.578 15.2761C121.477 15.259 122.367 15.4677 123.165 15.8823ZM122.335 22.2126C122.829 21.7436 123.073 21.0832 123.073 20.2025C123.105 19.482 122.849 18.7787 122.367 18.244C122.137 18.0038 121.862 17.8151 121.554 17.6836C121.247 17.5521 120.92 17.4863 120.587 17.4863C120.254 17.4863 119.923 17.5521 119.619 17.6836C119.312 17.8151 119.036 18.0038 118.806 18.244C118.347 18.7929 118.094 19.4849 118.094 20.1997C118.094 20.9145 118.347 21.6064 118.806 22.1554C119.033 22.4013 119.309 22.5928 119.619 22.7244C119.926 22.853 120.259 22.9188 120.592 22.9102C120.914 22.9159 121.236 22.8559 121.537 22.7358C121.833 22.6186 122.106 22.4384 122.335 22.2126Z" fill="white"/>
<path d="M127.001 13.5206C126.849 13.3805 126.728 13.2118 126.645 13.0231C126.562 12.8344 126.522 12.6314 126.525 12.4255C126.522 12.2196 126.565 12.0138 126.645 11.8222C126.728 11.6306 126.849 11.4591 127.001 11.319C127.303 11.0216 127.71 10.8529 128.135 10.8529C128.56 10.8529 128.968 11.0216 129.27 11.319C129.422 11.4619 129.542 11.6335 129.623 11.8222C129.703 12.0138 129.746 12.2168 129.743 12.4255C129.746 12.6314 129.706 12.8344 129.623 13.0231C129.542 13.2118 129.422 13.3805 129.27 13.5206C128.965 13.8122 128.557 13.9752 128.135 13.9752C127.71 13.9752 127.306 13.8122 127.001 13.5206ZM129.413 24.9288H126.863V15.4791H129.413V24.9288Z" fill="white"/>
<path d="M133.824 24.9288H131.271V11.216H133.824V24.9288Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.7801 14.3609L17.7816 14.3622L10 23L0 11.9929V7.24491C0 6.69969 0.635467 6.4014 1.05491 6.74973L12.1495 15.9633C13.2223 16.8543 14.7777 16.8543 15.8506 15.9633L17.7801 14.3609Z" fill="url(#paint0_linear_53_676)"/>
<path d="M22 10.8565L17.7801 14.361L17.7816 14.3622L12.1943 19.2977C11.2425 20.1385 9.81982 20.1597 8.84341 19.3476L0 11.9929V26.1035C0 27.7032 1.29683 29.0001 2.89655 29.0001H22L24 19.9283L22 10.8565Z" fill="url(#paint1_radial_53_676)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M22 10.86V29L25.1034 29C26.7032 29 28 27.7031 28 26.1035V7.24493C28 6.69971 27.3645 6.40136 26.9451 6.74976L22 10.86Z" fill="url(#paint2_linear_53_676)"/>
</g>
<defs>
<linearGradient id="paint0_linear_53_676" x1="10.507" y1="23.1523" x2="1.11629" y2="-9.46948" gradientUnits="userSpaceOnUse">
<stop stop-color="#E3D9FF"/>
<stop offset="1" stop-color="#7341FF"/>
</linearGradient>
<radialGradient id="paint1_radial_53_676" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(23.8954 13.0774) scale(27.9882 26.381)">
<stop offset="0.5561" stop-color="#6D4AFF"/>
<stop offset="0.9944" stop-color="#AA8EFF"/>
</radialGradient>
<linearGradient id="paint2_linear_53_676" x1="37.0552" y1="43.5221" x2="15.4546" y2="-3.07472" gradientUnits="userSpaceOnUse">
<stop offset="0.271" stop-color="#E3D9FF"/>
<stop offset="1" stop-color="#7341FF"/>
</linearGradient>
<clipPath id="clip0_53_676">
<rect width="134" height="36" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -0,0 +1,34 @@
<svg width="134" height="36" viewBox="0 0 134 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_22_69)">
<path d="M38 21.259V24.924H40.5591V21.4191C40.5591 21.0789 40.6927 20.7502 40.9344 20.51C41.1732 20.2699 41.5002 20.1327 41.8386 20.1327H44.463C45.6857 20.1327 46.86 19.6438 47.7244 18.7719C48.5888 17.9028 49.075 16.7221 49.075 15.4928C49.075 14.2635 48.5888 13.0828 47.7244 12.2109C46.86 11.3418 45.6857 10.8529 44.4602 10.8529H38V15.4328H40.5591V13.2744H44.2896C44.8697 13.2744 45.4241 13.5059 45.8336 13.9176C46.243 14.3293 46.4733 14.8867 46.4733 15.4699C46.4733 16.0531 46.243 16.6106 45.8336 17.0223C45.4241 17.4339 44.8697 17.6655 44.2896 17.6655H41.5798C41.1107 17.6655 40.6444 17.757 40.2122 17.9399C39.7771 18.12 39.3847 18.3859 39.0521 18.7204C38.7194 19.0549 38.4578 19.4523 38.2758 19.8868C38.0938 20.3185 38 20.7873 38 21.259Z" fill="#1B1340"/>
<path d="M49.4703 24.924V19.3408C49.4703 17.0623 50.7925 15.2498 53.4397 15.2498C53.8633 15.2441 54.287 15.2898 54.7022 15.3899V17.6855C54.4008 17.6655 54.142 17.6655 54.0197 17.6655C52.6179 17.6655 52.0151 18.3116 52.0151 19.6209V24.924H49.4703Z" fill="#1B1340"/>
<path d="M55.4641 20.1899C55.4641 17.3882 57.5682 15.2527 60.4969 15.2527C63.4256 15.2527 65.5297 17.3882 65.5297 20.1899C65.5297 22.9915 63.4256 25.1471 60.4969 25.1471C57.5682 25.1471 55.4641 22.9886 55.4641 20.1899ZM63.0218 20.1899C63.0218 18.5975 61.9584 17.4683 60.4969 17.4683C59.0325 17.4683 57.972 18.5946 57.972 20.1899C57.972 21.8022 59.0354 22.9115 60.4969 22.9115C61.9612 22.9115 63.0218 21.7994 63.0218 20.1899Z" fill="#1B1340"/>
<path d="M73.6676 20.1899C73.6676 17.3882 75.7717 15.2527 78.7004 15.2527C81.6263 15.2527 83.7304 17.3882 83.7304 20.1899C83.7304 22.9915 81.6263 25.1471 78.7004 25.1471C75.7717 25.1471 73.6676 22.9886 73.6676 20.1899ZM81.2225 20.1899C81.2225 18.5975 80.1591 17.4683 78.6976 17.4683C77.2361 17.4683 76.1726 18.5946 76.1726 20.1899C76.1726 21.8022 77.2361 22.9115 78.6976 22.9115C80.1591 22.9115 81.2225 21.7994 81.2225 20.1899Z" fill="#1B1340"/>
<path d="M85.0526 24.9241V19.5438C85.0526 17.0452 86.6364 15.2498 89.4627 15.2498C92.2692 15.2498 93.8529 17.0423 93.8529 19.5438V24.9241H91.328V19.7439C91.328 18.3545 90.7053 17.4854 89.4627 17.4854C88.2202 17.4854 87.5975 18.3516 87.5975 19.7439V24.9241H85.0526Z" fill="#1B1340"/>
<path d="M72.9056 17.4881H70.1588V21.0159C70.1588 22.2452 70.5996 22.8084 71.862 22.8084C71.9815 22.8084 72.2829 22.8084 72.6639 22.7884V24.8639C72.1435 25.004 71.6829 25.0869 71.1796 25.0869C69.0556 25.0869 67.6112 23.7976 67.6112 21.359V17.4881H65.9051V15.4527H66.3316C66.4994 15.4527 66.6671 15.4184 66.8207 15.3555C66.9771 15.2897 67.1164 15.1954 67.2358 15.0753C67.3553 14.9552 67.4491 14.8152 67.5145 14.6579C67.5799 14.5007 67.6112 14.3349 67.6112 14.1662V12.2479H70.156V15.4527H72.9027V17.4881H72.9056Z" fill="#1B1340"/>
<path d="M98.8823 11.216H102.457L105.808 19.4391C106.107 20.1282 106.362 20.8315 106.577 21.5492H106.612C106.827 20.8315 107.083 20.1253 107.381 19.4391L110.732 11.216H114.307V24.9288H111.717V15.7421C111.714 15.4391 111.729 15.136 111.76 14.8358H111.717C111.637 15.1589 111.531 15.4762 111.396 15.7822L107.683 24.7687H105.535L101.811 15.7822C101.676 15.4734 101.561 15.1589 101.469 14.8358H101.429C101.458 15.136 101.472 15.4391 101.469 15.7421V24.9316H98.8823V11.216Z" fill="#6D4AFF"/>
<path d="M123.165 15.8823C123.915 16.274 124.535 16.8744 124.951 17.6092C125.396 18.407 125.62 19.3105 125.603 20.2225V24.9288H123.335L123.174 23.5164C122.878 24.0281 122.444 24.4456 121.922 24.7258C121.362 25.0146 120.736 25.1604 120.104 25.1461C119.297 25.1547 118.505 24.9402 117.813 24.5256C117.112 24.1025 116.544 23.4963 116.165 22.7758C115.751 21.981 115.545 21.0975 115.562 20.2025C115.55 19.3219 115.777 18.4556 116.217 17.6893C116.647 16.9459 117.27 16.334 118.025 15.9194C118.806 15.4877 119.685 15.2647 120.578 15.2761C121.477 15.259 122.367 15.4677 123.165 15.8823ZM122.335 22.2126C122.829 21.7436 123.073 21.0832 123.073 20.2025C123.105 19.482 122.849 18.7787 122.367 18.244C122.137 18.0038 121.862 17.8151 121.554 17.6836C121.247 17.5521 120.92 17.4863 120.587 17.4863C120.254 17.4863 119.923 17.5521 119.619 17.6836C119.312 17.8151 119.036 18.0038 118.806 18.244C118.347 18.7929 118.094 19.4849 118.094 20.1997C118.094 20.9145 118.347 21.6064 118.806 22.1554C119.033 22.4013 119.309 22.5928 119.619 22.7244C119.926 22.853 120.259 22.9188 120.592 22.9102C120.914 22.9159 121.236 22.8559 121.537 22.7358C121.833 22.6186 122.106 22.4384 122.335 22.2126Z" fill="#6D4AFF"/>
<path d="M127.001 13.5206C126.849 13.3805 126.728 13.2118 126.645 13.0231C126.562 12.8344 126.522 12.6314 126.525 12.4255C126.522 12.2196 126.565 12.0138 126.645 11.8222C126.728 11.6306 126.849 11.4591 127.001 11.319C127.303 11.0216 127.71 10.8529 128.135 10.8529C128.56 10.8529 128.968 11.0216 129.27 11.319C129.422 11.4619 129.542 11.6335 129.623 11.8222C129.703 12.0138 129.746 12.2168 129.743 12.4255C129.746 12.6314 129.706 12.8344 129.623 13.0231C129.542 13.2118 129.422 13.3805 129.27 13.5206C128.965 13.8122 128.557 13.9752 128.135 13.9752C127.71 13.9752 127.306 13.8122 127.001 13.5206ZM129.413 24.9288H126.863V15.4791H129.413V24.9288Z" fill="#6D4AFF"/>
<path d="M133.824 24.9288H131.271V11.216H133.824V24.9288Z" fill="#6D4AFF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.7801 14.3609L17.7816 14.3622L10 23L0 11.9929V7.24491C0 6.69969 0.635467 6.4014 1.05491 6.74973L12.1495 15.9633C13.2223 16.8543 14.7777 16.8543 15.8506 15.9633L17.7801 14.3609Z" fill="url(#paint0_linear_22_69)"/>
<path d="M22 10.8565L17.7801 14.3609L17.7816 14.3622L12.1943 19.2977C11.2425 20.1385 9.81982 20.1596 8.84341 19.3476L0 11.9929V26.1035C0 27.7032 1.29683 29.0001 2.89655 29.0001H22L24 19.9283L22 10.8565Z" fill="url(#paint1_radial_22_69)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M22 10.86V29L25.1034 29C26.7032 29 28 27.7031 28 26.1035V7.24493C28 6.69971 27.3645 6.40136 26.9451 6.74976L22 10.86Z" fill="url(#paint2_linear_22_69)"/>
</g>
<defs>
<linearGradient id="paint0_linear_22_69" x1="10.507" y1="23.1523" x2="1.11629" y2="-9.46948" gradientUnits="userSpaceOnUse">
<stop stop-color="#E3D9FF"/>
<stop offset="1" stop-color="#7341FF"/>
</linearGradient>
<radialGradient id="paint1_radial_22_69" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(23.8954 13.0774) scale(27.9882 26.381)">
<stop offset="0.5561" stop-color="#6D4AFF"/>
<stop offset="0.9944" stop-color="#AA8EFF"/>
</radialGradient>
<linearGradient id="paint2_linear_22_69" x1="37.0552" y1="43.5221" x2="15.4546" y2="-3.07472" gradientUnits="userSpaceOnUse">
<stop offset="0.271" stop-color="#E3D9FF"/>
<stop offset="1" stop-color="#7341FF"/>
</linearGradient>
<clipPath id="clip0_22_69">
<rect width="134" height="36" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

View File

@ -1,331 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="265"
height="148"
viewBox="0 0 265 148"
fill="none"
version="1.1"
id="svg110"
sodipodi:docname="img-welcome-dark.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
inkscape:export-filename="/home/dev/gopath/src/github.com/ProtonMail/proton-bridge/internal/frontend/qml/icons/img-welcome.png"
inkscape:export-xdpi="400"
inkscape:export-ydpi="400"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview112"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:pageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
showgrid="false"
inkscape:snap-global="true"
inkscape:snap-page="true"
inkscape:zoom="0.69351284"
inkscape:cx="93.004767"
inkscape:cy="115.35475"
inkscape:window-width="1916"
inkscape:window-height="1041"
inkscape:window-x="0"
inkscape:window-y="18"
inkscape:window-maximized="1"
inkscape:current-layer="svg110" />
<rect
style="fill:#1c1b24;fill-opacity:1;stroke:none;stroke-width:27.9987;stroke-linecap:round;stroke-linejoin:round"
id="rect951"
width="265"
height="148"
x="0"
y="0"
ry="0"
inkscape:export-filename="/home/dev/gopath/src/github.com/ProtonMail/proton-bridge/internal/frontend/qml/icons/img-welcome.png"
inkscape:export-xdpi="400"
inkscape:export-ydpi="400" />
<path
d="M221.171 147.001H44.8555C43.1441 147.001 41.8047 145.661 41.8047 143.95V142.238C41.8047 140.527 43.1441 139.188 44.8555 139.188H221.171C222.882 139.188 224.221 140.527 224.221 142.238V143.95C224.221 145.661 222.808 147.001 221.171 147.001Z"
fill="#B0D4E5"
id="path2" />
<path
d="M141.83 143.503H123.376C120.995 143.503 119.135 141.568 119.135 139.262H146.071C146.071 141.568 144.211 143.503 141.83 143.503Z"
fill="#DAF3FF"
id="path4" />
<path
d="M206.034 139.187H58.501V53.9292C58.501 49.0182 62.5191 45 67.4302 45H197.104C202.016 45 206.034 49.0182 206.034 53.9292V139.187Z"
fill="url(#paint0_radial_8674_44242)"
id="path6" />
<path
d="M199.115 139.187H66.167V55.3434C66.167 54.1529 67.1343 53.1855 68.3249 53.1855H196.883C198.074 53.1855 199.041 54.1529 199.041 55.3434V139.187H199.115Z"
fill="url(#paint1_linear_8674_44242)"
id="path8" />
<path
d="M190.805 131.286H76.1797V62.5185C76.1797 61.5234 77.028 60.7148 78.0721 60.7148H188.847C189.891 60.7148 190.739 61.5234 190.739 62.5185V131.286H190.805Z"
fill="url(#paint2_radial_8674_44242)"
id="path10" />
<path
d="M84.1558 70.7744C85.3064 70.7744 86.2392 69.8416 86.2392 68.6909C86.2392 67.5402 85.3064 66.6074 84.1558 66.6074C83.0051 66.6074 82.0723 67.5402 82.0723 68.6909C82.0723 69.8416 83.0051 70.7744 84.1558 70.7744Z"
fill="#B0D4E5"
id="path12" />
<path
d="M90.6304 70.7744C91.781 70.7744 92.7139 69.8416 92.7139 68.6909C92.7139 67.5402 91.781 66.6074 90.6304 66.6074C89.4797 66.6074 88.5469 67.5402 88.5469 68.6909C88.5469 69.8416 89.4797 70.7744 90.6304 70.7744Z"
fill="#B0D4E5"
id="path14" />
<path
d="M97.105 70.7744C98.2557 70.7744 99.1885 69.8416 99.1885 68.6909C99.1885 67.5402 98.2557 66.6074 97.105 66.6074C95.9543 66.6074 95.0215 67.5402 95.0215 68.6909C95.0215 69.8416 95.9543 70.7744 97.105 70.7744Z"
fill="#B0D4E5"
id="path16" />
<path
d="M242.633 76.3538H180.924C178.747 76.3538 177 74.5616 177 72.3891V33.9646C177 31.7922 178.774 30 180.924 30H242.606C244.783 30 246.53 31.7922 246.53 33.9646V72.3891C246.557 74.5887 244.783 76.3538 242.633 76.3538Z"
fill="url(#paint3_linear_8674_44242)"
id="path18" />
<path
d="M209.232 56.6899C210.689 57.8921 212.822 57.8921 214.28 56.6899L245.431 31.0419C244.729 30.4007 243.784 30 242.758 30H180.78C179.754 30 178.809 30.4007 178.107 31.0419L209.232 56.6899Z"
fill="url(#paint4_linear_8674_44242)"
id="path20" />
<path
d="M134.4 75C123.858 75 115.312 83.5451 115.312 94.0874V113.301C115.312 115.344 116.968 117 119.011 117H150.184C152.008 117 153.487 115.52 153.487 113.696V94.0874C153.487 83.5465 144.942 75 134.4 75ZM145.387 93.9814L137.044 101.01C135.538 102.279 133.336 102.279 131.83 101.01L123.487 93.9814C123.487 88.0172 128.323 83.1817 134.287 83.1817H134.587C140.551 83.1817 145.387 88.0172 145.387 93.9814Z"
fill="#6D4AFF"
id="path22" />
<path
d="M134.4 75C123.858 75 115.312 83.5451 115.312 94.0874V113.301C115.312 115.344 116.968 117 119.011 117H150.184C152.008 117 153.487 115.52 153.487 113.696V94.0874C153.487 83.5465 144.942 75 134.4 75ZM145.387 93.9814L137.044 101.01C135.538 102.279 133.336 102.279 131.83 101.01L123.487 93.9814C123.487 88.0172 128.323 83.1817 134.287 83.1817H134.587C140.551 83.1817 145.387 88.0172 145.387 93.9814Z"
fill="url(#paint5_linear_8674_44242)"
id="path24" />
<g
filter="url(#filter0_i_8674_44242)"
id="g28">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M137.057 101.095C136.188 101.799 133.926 102.785 131.838 101.095C129.751 99.4048 125.418 95.6687 123.513 94.0119H123.524L123.487 93.9814C123.487 88.0172 128.323 83.1817 134.287 83.1817H134.587C140.551 83.1817 145.387 88.0172 145.387 93.9814L145.351 94.0119H145.383V117H150.184C152.008 117 153.487 115.52 153.487 113.696V94.0874C153.487 83.5465 144.942 75 134.4 75C123.858 75 115.312 83.5451 115.312 94.0874V95.2946L127.117 105.444C127.986 106.272 130.273 107.432 132.46 105.444C134.647 103.456 136.436 101.716 137.057 101.095Z"
fill="url(#paint6_radial_8674_44242)"
id="path26" />
</g>
<circle
cx="239.278"
cy="30.2778"
r="15.2778"
fill="url(#paint7_linear_8674_44242)"
id="circle30" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M245.702 26.668C246.113 27.0766 246.116 27.7417 245.707 28.1534L238.128 35.7923C237.93 35.9911 237.662 36.1028 237.382 36.1028C237.102 36.1028 236.834 35.9911 236.636 35.7923L232.758 31.8835C232.349 31.4718 232.352 30.8067 232.764 30.3981C233.175 29.9895 233.84 29.9921 234.249 30.4039L237.382 33.5613L244.216 26.6738C244.625 26.262 245.29 26.2595 245.702 26.668Z"
fill="white"
id="path32" />
<path
d="M0.878906 69.6212C0.878906 56.0233 11.9022 45 25.5001 45V45C39.0981 45 50.1214 56.0233 50.1214 69.6212V94.2425H25.5002C11.9022 94.2425 0.878906 83.2192 0.878906 69.6212V69.6212Z"
fill="url(#paint8_linear_8674_44242)"
id="path34" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M31.4987 62.28V64.8004H33.9547C34.7948 64.8004 35.5056 65.4789 35.5056 66.3513V79.5019C35.5056 80.342 34.8271 81.0529 33.9547 81.0529H17.3791C16.539 81.0529 15.8281 80.3743 15.8281 79.5019V66.3513C15.8281 65.5112 16.5067 64.8004 17.3791 64.8004H19.802V62.28C19.802 59.0488 22.4192 56.4316 25.6504 56.4316C28.8815 56.4316 31.4987 59.0488 31.4987 62.28ZM29.0361 62.28V64.8004H22.3292V62.28C22.3292 59.9536 24.1059 58.9265 25.6827 58.9265C27.2594 58.9265 29.0361 59.9536 29.0361 62.28ZM25.9832 69.195C27.1141 69.195 28.0188 70.0997 28.0188 71.2306C28.0188 72.006 27.5988 72.6846 26.9526 73.0077L27.7927 76.6265H24.1738L25.0139 73.0077C24.3677 72.6523 23.9476 72.006 23.9476 71.2306C23.9476 70.0997 24.8523 69.195 25.9832 69.195Z"
fill="white"
id="path36" />
<defs
id="defs108">
<filter
id="filter0_i_8674_44242"
x="114.72"
y="75"
width="38.7675"
height="43.6537"
filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB">
<feFlood
flood-opacity="0"
result="BackgroundImageFix"
id="feFlood38" />
<feBlend
mode="normal"
in="SourceGraphic"
in2="BackgroundImageFix"
result="shape"
id="feBlend40" />
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
id="feColorMatrix42" />
<feOffset
dx="-0.592742"
dy="1.65375"
id="feOffset44" />
<feGaussianBlur
stdDeviation="4.44556"
id="feGaussianBlur46" />
<feComposite
in2="hardAlpha"
operator="arithmetic"
k2="-1"
k3="1"
id="feComposite48" />
<feColorMatrix
type="matrix"
values="0 0 0 0 0.462745 0 0 0 0 0.337255 0 0 0 0 1 0 0 0 0.24 0"
id="feColorMatrix50" />
<feBlend
mode="normal"
in2="shape"
result="effect1_innerShadow_8674_44242"
id="feBlend52" />
</filter>
<radialGradient
id="paint0_radial_8674_44242"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(202 51.5) rotate(145.641) scale(59.357 92.9759)">
<stop
stop-color="#292842"
id="stop55" />
<stop
offset="1"
stop-color="#38385F"
id="stop57" />
</radialGradient>
<linearGradient
id="paint1_linear_8674_44242"
x1="63.7079"
y1="144.988"
x2="207.623"
y2="58.7515"
gradientUnits="userSpaceOnUse">
<stop
stop-color="#35168C"
id="stop60" />
<stop
offset="0.317708"
stop-color="#FF5454"
id="stop62" />
<stop
offset="0.46875"
stop-color="#FFDD64"
id="stop64" />
<stop
offset="0.677083"
stop-color="#BCE6FF"
id="stop66" />
<stop
offset="0.911458"
stop-color="#6983EF"
id="stop68" />
<stop
offset="1"
stop-color="#2395FF"
id="stop70" />
</linearGradient>
<radialGradient
id="paint2_radial_8674_44242"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(188.5 63.5) rotate(135) scale(33.9411 55.1286)">
<stop
stop-color="#DDDBE3"
id="stop73" />
<stop
offset="1"
stop-color="white"
id="stop75" />
</radialGradient>
<linearGradient
id="paint3_linear_8674_44242"
x1="211.765"
y1="30"
x2="211.765"
y2="66.4208"
gradientUnits="userSpaceOnUse">
<stop
stop-color="#C1DEF8"
id="stop78" />
<stop
offset="1"
stop-color="#ECFAFF"
id="stop80" />
</linearGradient>
<linearGradient
id="paint4_linear_8674_44242"
x1="211.769"
y1="18.4116"
x2="211.769"
y2="50.4177"
gradientUnits="userSpaceOnUse">
<stop
stop-color="#DEEBF7"
id="stop83" />
<stop
offset="1"
stop-color="white"
id="stop85" />
</linearGradient>
<linearGradient
id="paint5_linear_8674_44242"
x1="116.679"
y1="121.846"
x2="122.395"
y2="105.692"
gradientUnits="userSpaceOnUse">
<stop
stop-color="#28B0E8"
id="stop88" />
<stop
offset="1"
stop-color="#C5B7FF"
stop-opacity="0"
id="stop90" />
</linearGradient>
<radialGradient
id="paint6_radial_8674_44242"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(151.237 120.479) rotate(-138.034) scale(48.3148 39.5031)">
<stop
stop-color="#E2DBFF"
id="stop93" />
<stop
offset="1"
stop-color="#6D4AFF"
id="stop95" />
</radialGradient>
<linearGradient
id="paint7_linear_8674_44242"
x1="240.861"
y1="12.772"
x2="241.004"
y2="45.5557"
gradientUnits="userSpaceOnUse">
<stop
stop-color="#2AF091"
id="stop98" />
<stop
offset="1"
stop-color="#00C5A1"
id="stop100" />
</linearGradient>
<linearGradient
id="paint8_linear_8674_44242"
x1="41.1252"
y1="56.3637"
x2="5.14027"
y2="101.345"
gradientUnits="userSpaceOnUse">
<stop
stop-color="#FFD66C"
id="stop103" />
<stop
offset="1"
stop-color="#FF8E4F"
id="stop105" />
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

View File

@ -1,331 +1,34 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="265"
height="148"
viewBox="0 0 265 148"
fill="none"
version="1.1"
id="svg110"
sodipodi:docname="img-welcome.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
inkscape:export-filename="/home/dev/gopath/src/github.com/ProtonMail/proton-bridge/internal/frontend/qml/icons/img-welcome.png"
inkscape:export-xdpi="400"
inkscape:export-ydpi="400"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview112"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:pageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
showgrid="false"
inkscape:snap-global="true"
inkscape:snap-page="true"
inkscape:zoom="0.69351284"
inkscape:cx="93.004767"
inkscape:cy="115.35475"
inkscape:window-width="1916"
inkscape:window-height="1041"
inkscape:window-x="0"
inkscape:window-y="18"
inkscape:window-maximized="1"
inkscape:current-layer="svg110" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:27.9987;stroke-linecap:round;stroke-linejoin:round"
id="rect951"
width="265"
height="148"
x="0"
y="0"
ry="0"
inkscape:export-filename="/home/dev/gopath/src/github.com/ProtonMail/proton-bridge/internal/frontend/qml/icons/img-welcome.png"
inkscape:export-xdpi="400"
inkscape:export-ydpi="400" />
<path
d="M221.171 147.001H44.8555C43.1441 147.001 41.8047 145.661 41.8047 143.95V142.238C41.8047 140.527 43.1441 139.188 44.8555 139.188H221.171C222.882 139.188 224.221 140.527 224.221 142.238V143.95C224.221 145.661 222.808 147.001 221.171 147.001Z"
fill="#B0D4E5"
id="path2" />
<path
d="M141.83 143.503H123.376C120.995 143.503 119.135 141.568 119.135 139.262H146.071C146.071 141.568 144.211 143.503 141.83 143.503Z"
fill="#DAF3FF"
id="path4" />
<path
d="M206.034 139.187H58.501V53.9292C58.501 49.0182 62.5191 45 67.4302 45H197.104C202.016 45 206.034 49.0182 206.034 53.9292V139.187Z"
fill="url(#paint0_radial_8674_44242)"
id="path6" />
<path
d="M199.115 139.187H66.167V55.3434C66.167 54.1529 67.1343 53.1855 68.3249 53.1855H196.883C198.074 53.1855 199.041 54.1529 199.041 55.3434V139.187H199.115Z"
fill="url(#paint1_linear_8674_44242)"
id="path8" />
<path
d="M190.805 131.286H76.1797V62.5185C76.1797 61.5234 77.028 60.7148 78.0721 60.7148H188.847C189.891 60.7148 190.739 61.5234 190.739 62.5185V131.286H190.805Z"
fill="url(#paint2_radial_8674_44242)"
id="path10" />
<path
d="M84.1558 70.7744C85.3064 70.7744 86.2392 69.8416 86.2392 68.6909C86.2392 67.5402 85.3064 66.6074 84.1558 66.6074C83.0051 66.6074 82.0723 67.5402 82.0723 68.6909C82.0723 69.8416 83.0051 70.7744 84.1558 70.7744Z"
fill="#B0D4E5"
id="path12" />
<path
d="M90.6304 70.7744C91.781 70.7744 92.7139 69.8416 92.7139 68.6909C92.7139 67.5402 91.781 66.6074 90.6304 66.6074C89.4797 66.6074 88.5469 67.5402 88.5469 68.6909C88.5469 69.8416 89.4797 70.7744 90.6304 70.7744Z"
fill="#B0D4E5"
id="path14" />
<path
d="M97.105 70.7744C98.2557 70.7744 99.1885 69.8416 99.1885 68.6909C99.1885 67.5402 98.2557 66.6074 97.105 66.6074C95.9543 66.6074 95.0215 67.5402 95.0215 68.6909C95.0215 69.8416 95.9543 70.7744 97.105 70.7744Z"
fill="#B0D4E5"
id="path16" />
<path
d="M242.633 76.3538H180.924C178.747 76.3538 177 74.5616 177 72.3891V33.9646C177 31.7922 178.774 30 180.924 30H242.606C244.783 30 246.53 31.7922 246.53 33.9646V72.3891C246.557 74.5887 244.783 76.3538 242.633 76.3538Z"
fill="url(#paint3_linear_8674_44242)"
id="path18" />
<path
d="M209.232 56.6899C210.689 57.8921 212.822 57.8921 214.28 56.6899L245.431 31.0419C244.729 30.4007 243.784 30 242.758 30H180.78C179.754 30 178.809 30.4007 178.107 31.0419L209.232 56.6899Z"
fill="url(#paint4_linear_8674_44242)"
id="path20" />
<path
d="M134.4 75C123.858 75 115.312 83.5451 115.312 94.0874V113.301C115.312 115.344 116.968 117 119.011 117H150.184C152.008 117 153.487 115.52 153.487 113.696V94.0874C153.487 83.5465 144.942 75 134.4 75ZM145.387 93.9814L137.044 101.01C135.538 102.279 133.336 102.279 131.83 101.01L123.487 93.9814C123.487 88.0172 128.323 83.1817 134.287 83.1817H134.587C140.551 83.1817 145.387 88.0172 145.387 93.9814Z"
fill="#6D4AFF"
id="path22" />
<path
d="M134.4 75C123.858 75 115.312 83.5451 115.312 94.0874V113.301C115.312 115.344 116.968 117 119.011 117H150.184C152.008 117 153.487 115.52 153.487 113.696V94.0874C153.487 83.5465 144.942 75 134.4 75ZM145.387 93.9814L137.044 101.01C135.538 102.279 133.336 102.279 131.83 101.01L123.487 93.9814C123.487 88.0172 128.323 83.1817 134.287 83.1817H134.587C140.551 83.1817 145.387 88.0172 145.387 93.9814Z"
fill="url(#paint5_linear_8674_44242)"
id="path24" />
<g
filter="url(#filter0_i_8674_44242)"
id="g28">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M137.057 101.095C136.188 101.799 133.926 102.785 131.838 101.095C129.751 99.4048 125.418 95.6687 123.513 94.0119H123.524L123.487 93.9814C123.487 88.0172 128.323 83.1817 134.287 83.1817H134.587C140.551 83.1817 145.387 88.0172 145.387 93.9814L145.351 94.0119H145.383V117H150.184C152.008 117 153.487 115.52 153.487 113.696V94.0874C153.487 83.5465 144.942 75 134.4 75C123.858 75 115.312 83.5451 115.312 94.0874V95.2946L127.117 105.444C127.986 106.272 130.273 107.432 132.46 105.444C134.647 103.456 136.436 101.716 137.057 101.095Z"
fill="url(#paint6_radial_8674_44242)"
id="path26" />
</g>
<circle
cx="239.278"
cy="30.2778"
r="15.2778"
fill="url(#paint7_linear_8674_44242)"
id="circle30" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M245.702 26.668C246.113 27.0766 246.116 27.7417 245.707 28.1534L238.128 35.7923C237.93 35.9911 237.662 36.1028 237.382 36.1028C237.102 36.1028 236.834 35.9911 236.636 35.7923L232.758 31.8835C232.349 31.4718 232.352 30.8067 232.764 30.3981C233.175 29.9895 233.84 29.9921 234.249 30.4039L237.382 33.5613L244.216 26.6738C244.625 26.262 245.29 26.2595 245.702 26.668Z"
fill="white"
id="path32" />
<path
d="M0.878906 69.6212C0.878906 56.0233 11.9022 45 25.5001 45V45C39.0981 45 50.1214 56.0233 50.1214 69.6212V94.2425H25.5002C11.9022 94.2425 0.878906 83.2192 0.878906 69.6212V69.6212Z"
fill="url(#paint8_linear_8674_44242)"
id="path34" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M31.4987 62.28V64.8004H33.9547C34.7948 64.8004 35.5056 65.4789 35.5056 66.3513V79.5019C35.5056 80.342 34.8271 81.0529 33.9547 81.0529H17.3791C16.539 81.0529 15.8281 80.3743 15.8281 79.5019V66.3513C15.8281 65.5112 16.5067 64.8004 17.3791 64.8004H19.802V62.28C19.802 59.0488 22.4192 56.4316 25.6504 56.4316C28.8815 56.4316 31.4987 59.0488 31.4987 62.28ZM29.0361 62.28V64.8004H22.3292V62.28C22.3292 59.9536 24.1059 58.9265 25.6827 58.9265C27.2594 58.9265 29.0361 59.9536 29.0361 62.28ZM25.9832 69.195C27.1141 69.195 28.0188 70.0997 28.0188 71.2306C28.0188 72.006 27.5988 72.6846 26.9526 73.0077L27.7927 76.6265H24.1738L25.0139 73.0077C24.3677 72.6523 23.9476 72.006 23.9476 71.2306C23.9476 70.0997 24.8523 69.195 25.9832 69.195Z"
fill="white"
id="path36" />
<defs
id="defs108">
<filter
id="filter0_i_8674_44242"
x="114.72"
y="75"
width="38.7675"
height="43.6537"
filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB">
<feFlood
flood-opacity="0"
result="BackgroundImageFix"
id="feFlood38" />
<feBlend
mode="normal"
in="SourceGraphic"
in2="BackgroundImageFix"
result="shape"
id="feBlend40" />
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
id="feColorMatrix42" />
<feOffset
dx="-0.592742"
dy="1.65375"
id="feOffset44" />
<feGaussianBlur
stdDeviation="4.44556"
id="feGaussianBlur46" />
<feComposite
in2="hardAlpha"
operator="arithmetic"
k2="-1"
k3="1"
id="feComposite48" />
<feColorMatrix
type="matrix"
values="0 0 0 0 0.462745 0 0 0 0 0.337255 0 0 0 0 1 0 0 0 0.24 0"
id="feColorMatrix50" />
<feBlend
mode="normal"
in2="shape"
result="effect1_innerShadow_8674_44242"
id="feBlend52" />
</filter>
<radialGradient
id="paint0_radial_8674_44242"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(202 51.5) rotate(145.641) scale(59.357 92.9759)">
<stop
stop-color="#292842"
id="stop55" />
<stop
offset="1"
stop-color="#38385F"
id="stop57" />
</radialGradient>
<linearGradient
id="paint1_linear_8674_44242"
x1="63.7079"
y1="144.988"
x2="207.623"
y2="58.7515"
gradientUnits="userSpaceOnUse">
<stop
stop-color="#35168C"
id="stop60" />
<stop
offset="0.317708"
stop-color="#FF5454"
id="stop62" />
<stop
offset="0.46875"
stop-color="#FFDD64"
id="stop64" />
<stop
offset="0.677083"
stop-color="#BCE6FF"
id="stop66" />
<stop
offset="0.911458"
stop-color="#6983EF"
id="stop68" />
<stop
offset="1"
stop-color="#2395FF"
id="stop70" />
</linearGradient>
<radialGradient
id="paint2_radial_8674_44242"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(188.5 63.5) rotate(135) scale(33.9411 55.1286)">
<stop
stop-color="#DDDBE3"
id="stop73" />
<stop
offset="1"
stop-color="white"
id="stop75" />
</radialGradient>
<linearGradient
id="paint3_linear_8674_44242"
x1="211.765"
y1="30"
x2="211.765"
y2="66.4208"
gradientUnits="userSpaceOnUse">
<stop
stop-color="#C1DEF8"
id="stop78" />
<stop
offset="1"
stop-color="#ECFAFF"
id="stop80" />
</linearGradient>
<linearGradient
id="paint4_linear_8674_44242"
x1="211.769"
y1="18.4116"
x2="211.769"
y2="50.4177"
gradientUnits="userSpaceOnUse">
<stop
stop-color="#DEEBF7"
id="stop83" />
<stop
offset="1"
stop-color="white"
id="stop85" />
</linearGradient>
<linearGradient
id="paint5_linear_8674_44242"
x1="116.679"
y1="121.846"
x2="122.395"
y2="105.692"
gradientUnits="userSpaceOnUse">
<stop
stop-color="#28B0E8"
id="stop88" />
<stop
offset="1"
stop-color="#C5B7FF"
stop-opacity="0"
id="stop90" />
</linearGradient>
<radialGradient
id="paint6_radial_8674_44242"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(151.237 120.479) rotate(-138.034) scale(48.3148 39.5031)">
<stop
stop-color="#E2DBFF"
id="stop93" />
<stop
offset="1"
stop-color="#6D4AFF"
id="stop95" />
</radialGradient>
<linearGradient
id="paint7_linear_8674_44242"
x1="240.861"
y1="12.772"
x2="241.004"
y2="45.5557"
gradientUnits="userSpaceOnUse">
<stop
stop-color="#2AF091"
id="stop98" />
<stop
offset="1"
stop-color="#00C5A1"
id="stop100" />
</linearGradient>
<linearGradient
id="paint8_linear_8674_44242"
x1="41.1252"
y1="56.3637"
x2="5.14027"
y2="101.345"
gradientUnits="userSpaceOnUse">
<stop
stop-color="#FFD66C"
id="stop103" />
<stop
offset="1"
stop-color="#FF8E4F"
id="stop105" />
</linearGradient>
</defs>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 265 148" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path id="path2" d="M221.171,147.001L44.856,147.001C43.144,147.001 41.805,145.661 41.805,143.95L41.805,142.238C41.805,140.527 43.144,139.188 44.856,139.188L221.171,139.188C222.882,139.188 224.221,140.527 224.221,142.238L224.221,143.95C224.221,145.661 222.808,147.001 221.171,147.001Z" style="fill:rgb(176,212,229);fill-rule:nonzero;"/>
<path id="path4" d="M141.83,143.503L123.376,143.503C120.995,143.503 119.135,141.568 119.135,139.262L146.071,139.262C146.071,141.568 144.211,143.503 141.83,143.503Z" style="fill:rgb(218,243,255);fill-rule:nonzero;"/>
<path id="path6" d="M206.034,139.187L58.501,139.187L58.501,53.929C58.501,49.018 62.519,45 67.43,45L197.104,45C202.016,45 206.034,49.018 206.034,53.929L206.034,139.187Z" style="fill:url(#_Radial1);fill-rule:nonzero;"/>
<path id="path8" d="M199.115,139.187L66.167,139.187L66.167,55.343C66.167,54.153 67.134,53.186 68.325,53.186L196.883,53.186C198.074,53.186 199.041,54.153 199.041,55.343L199.041,139.187L199.115,139.187Z" style="fill:url(#_Linear2);fill-rule:nonzero;"/>
<path id="path10" d="M190.805,131.286L76.18,131.286L76.18,62.519C76.18,61.523 77.028,60.715 78.072,60.715L188.847,60.715C189.891,60.715 190.739,61.523 190.739,62.519L190.739,131.286L190.805,131.286Z" style="fill:url(#_Radial3);fill-rule:nonzero;"/>
<path id="path12" d="M84.156,70.774C85.306,70.774 86.239,69.842 86.239,68.691C86.239,67.54 85.306,66.607 84.156,66.607C83.005,66.607 82.072,67.54 82.072,68.691C82.072,69.842 83.005,70.774 84.156,70.774Z" style="fill:rgb(176,212,229);fill-rule:nonzero;"/>
<path id="path14" d="M90.63,70.774C91.781,70.774 92.714,69.842 92.714,68.691C92.714,67.54 91.781,66.607 90.63,66.607C89.48,66.607 88.547,67.54 88.547,68.691C88.547,69.842 89.48,70.774 90.63,70.774Z" style="fill:rgb(176,212,229);fill-rule:nonzero;"/>
<path id="path16" d="M97.105,70.774C98.256,70.774 99.189,69.842 99.189,68.691C99.189,67.54 98.256,66.607 97.105,66.607C95.954,66.607 95.022,67.54 95.022,68.691C95.022,69.842 95.954,70.774 97.105,70.774Z" style="fill:rgb(176,212,229);fill-rule:nonzero;"/>
<path id="path18" d="M242.633,76.354L180.924,76.354C178.747,76.354 177,74.562 177,72.389L177,33.965C177,31.792 178.774,30 180.924,30L242.606,30C244.783,30 246.53,31.792 246.53,33.965L246.53,72.389C246.557,74.589 244.783,76.354 242.633,76.354Z" style="fill:url(#_Linear4);fill-rule:nonzero;"/>
<path id="path20" d="M209.232,56.69C210.689,57.892 212.822,57.892 214.28,56.69L245.431,31.042C244.729,30.401 243.784,30 242.758,30L180.78,30C179.754,30 178.809,30.401 178.107,31.042L209.232,56.69Z" style="fill:url(#_Linear5);fill-rule:nonzero;"/>
<path id="path22" d="M134.4,75C123.858,75 115.312,83.545 115.312,94.087L115.312,113.301C115.312,115.344 116.968,117 119.011,117L150.184,117C152.008,117 153.487,115.52 153.487,113.696L153.487,94.087C153.487,83.547 144.942,75 134.4,75ZM145.387,93.981L137.044,101.01C135.538,102.279 133.336,102.279 131.83,101.01L123.487,93.981C123.487,88.017 128.323,83.182 134.287,83.182L134.587,83.182C140.551,83.182 145.387,88.017 145.387,93.981Z" style="fill:rgb(109,74,255);fill-rule:nonzero;"/>
<path id="path24" d="M134.4,75C123.858,75 115.312,83.545 115.312,94.087L115.312,113.301C115.312,115.344 116.968,117 119.011,117L150.184,117C152.008,117 153.487,115.52 153.487,113.696L153.487,94.087C153.487,83.547 144.942,75 134.4,75ZM145.387,93.981L137.044,101.01C135.538,102.279 133.336,102.279 131.83,101.01L123.487,93.981C123.487,88.017 128.323,83.182 134.287,83.182L134.587,83.182C140.551,83.182 145.387,88.017 145.387,93.981Z" style="fill:url(#_Linear6);fill-rule:nonzero;"/>
<g id="g28">
<path id="path26" d="M137.057,101.095C136.188,101.799 133.926,102.785 131.838,101.095C129.751,99.405 125.418,95.669 123.513,94.012L123.524,94.012L123.487,93.981C123.487,88.017 128.323,83.182 134.287,83.182L134.587,83.182C140.551,83.182 145.387,88.017 145.387,93.981L145.351,94.012L145.383,94.012L145.383,117L150.184,117C152.008,117 153.487,115.52 153.487,113.696L153.487,94.087C153.487,83.547 144.942,75 134.4,75C123.858,75 115.312,83.545 115.312,94.087L115.312,95.295L127.117,105.444C127.986,106.272 130.273,107.432 132.46,105.444C134.647,103.456 136.436,101.716 137.057,101.095Z" style="fill:url(#_Radial7);"/>
</g>
<circle id="circle30" cx="239.278" cy="30.278" r="15.278" style="fill:url(#_Linear8);"/>
<path id="path32" d="M245.702,26.668C246.113,27.077 246.116,27.742 245.707,28.153L238.128,35.792C237.93,35.991 237.662,36.103 237.382,36.103C237.102,36.103 236.834,35.991 236.636,35.792L232.758,31.884C232.349,31.472 232.352,30.807 232.764,30.398C233.175,29.99 233.84,29.992 234.249,30.404L237.382,33.561L244.216,26.674C244.625,26.262 245.29,26.26 245.702,26.668Z" style="fill:white;"/>
<path id="path34" d="M0.879,69.621C0.879,56.023 11.902,45 25.5,45C39.098,45 50.121,56.023 50.121,69.621L50.121,94.243L25.5,94.243C11.902,94.243 0.879,83.219 0.879,69.621Z" style="fill:url(#_Linear9);fill-rule:nonzero;"/>
<path id="path36" d="M31.499,62.28L31.499,64.8L33.955,64.8C34.795,64.8 35.506,65.479 35.506,66.351L35.506,79.502C35.506,80.342 34.827,81.053 33.955,81.053L17.379,81.053C16.539,81.053 15.828,80.374 15.828,79.502L15.828,66.351C15.828,65.511 16.507,64.8 17.379,64.8L19.802,64.8L19.802,62.28C19.802,59.049 22.419,56.432 25.65,56.432C28.882,56.432 31.499,59.049 31.499,62.28ZM29.036,62.28L29.036,64.8L22.329,64.8L22.329,62.28C22.329,59.954 24.106,58.926 25.683,58.926C27.259,58.926 29.036,59.954 29.036,62.28ZM25.983,69.195C27.114,69.195 28.019,70.1 28.019,71.231C28.019,72.006 27.599,72.685 26.953,73.008L27.793,76.626L24.174,76.626L25.014,73.008C24.368,72.652 23.948,72.006 23.948,71.231C23.948,70.1 24.852,69.195 25.983,69.195Z" style="fill:white;"/>
<defs>
<radialGradient id="_Radial1" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-49.0002,33.4997,-52.4734,-76.7532,202,51.5)"><stop offset="0" style="stop-color:rgb(41,40,66);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(56,56,95);stop-opacity:1"/></radialGradient>
<linearGradient id="_Linear2" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(143.915,-86.2365,86.2365,143.915,63.7079,144.988)"><stop offset="0" style="stop-color:rgb(53,22,140);stop-opacity:1"/><stop offset="0.32" style="stop-color:rgb(255,84,84);stop-opacity:1"/><stop offset="0.47" style="stop-color:rgb(255,221,100);stop-opacity:1"/><stop offset="0.68" style="stop-color:rgb(188,230,255);stop-opacity:1"/><stop offset="0.91" style="stop-color:rgb(105,131,239);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(35,149,255);stop-opacity:1"/></linearGradient>
<radialGradient id="_Radial3" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-24,24,-38.9818,-38.9818,188.5,63.5)"><stop offset="0" style="stop-color:rgb(221,219,227);stop-opacity:1"/><stop offset="1" style="stop-color:white;stop-opacity:1"/></radialGradient>
<linearGradient id="_Linear4" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(2.23013e-15,36.4208,-36.4208,2.23013e-15,211.765,30)"><stop offset="0" style="stop-color:rgb(193,222,248);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(236,250,255);stop-opacity:1"/></linearGradient>
<linearGradient id="_Linear5" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.95981e-15,32.0061,-32.0061,1.95981e-15,211.769,18.4116)"><stop offset="0" style="stop-color:rgb(222,235,247);stop-opacity:1"/><stop offset="1" style="stop-color:white;stop-opacity:1"/></linearGradient>
<linearGradient id="_Linear6" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(5.716,-16.154,16.154,5.716,116.679,121.846)"><stop offset="0" style="stop-color:rgb(40,176,232);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(197,183,255);stop-opacity:0"/></linearGradient>
<radialGradient id="_Radial7" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-35.9241,-32.3076,26.4153,-29.3722,151.237,120.479)"><stop offset="0" style="stop-color:rgb(226,219,255);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(109,74,255);stop-opacity:1"/></radialGradient>
<linearGradient id="_Linear8" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.143,32.7837,-32.7837,0.143,240.861,12.772)"><stop offset="0" style="stop-color:rgb(42,240,145);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(0,197,161);stop-opacity:1"/></linearGradient>
<linearGradient id="_Linear9" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-35.9849,44.9813,-44.9813,-35.9849,41.1252,56.3637)"><stop offset="0" style="stop-color:rgb(255,214,108);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(255,142,79);stop-opacity:1"/></linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@ -202,6 +202,39 @@ SPStreamEvent newReportBugErrorEvent() {
}
//****************************************************************************************************************************************************
/// \return The event.
//****************************************************************************************************************************************************
SPStreamEvent newCertificateInstallSuccessEvent() {
auto event = new grpc::CertificateInstallSuccessEvent;
auto appEvent = new grpc::AppEvent;
appEvent->set_allocated_certificateinstallsuccess(event);
return wrapAppEvent(appEvent);
}
//****************************************************************************************************************************************************
/// \return The event.
//****************************************************************************************************************************************************
SPStreamEvent newCertificateInstallCanceledEvent() {
auto event = new grpc::CertificateInstallCanceledEvent;
auto appEvent = new grpc::AppEvent;
appEvent->set_allocated_certificateinstallcanceled(event);
return wrapAppEvent(appEvent);
}
//****************************************************************************************************************************************************
/// \return The event.
//****************************************************************************************************************************************************
SPStreamEvent newCertificateInstallFailedEvent() {
auto event = new grpc::CertificateInstallFailedEvent;
auto appEvent = new grpc::AppEvent;
appEvent->set_allocated_certificateinstallfailed(event);
return wrapAppEvent(appEvent);
}
//****************************************************************************************************************************************************
/// \return The event.
//****************************************************************************************************************************************************
@ -245,8 +278,9 @@ SPStreamEvent newLoginTfaRequestedEvent(QString const &username) {
/// \param[in] username The username.
/// \return The event.
//****************************************************************************************************************************************************
SPStreamEvent newLoginTwoPasswordsRequestedEvent() {
SPStreamEvent newLoginTwoPasswordsRequestedEvent(QString const &username) {
auto event = new ::grpc::LoginTwoPasswordsRequestedEvent;
event->set_username(username.toStdString());
auto loginEvent = new grpc::LoginEvent;
loginEvent->set_allocated_twopasswordrequested(event);
return wrapLoginEvent(loginEvent);

View File

@ -34,12 +34,15 @@ SPStreamEvent newResetFinishedEvent(); ///< Create a new ResetFinishedEvent even
SPStreamEvent newReportBugFinishedEvent(); ///< Create a new ReportBugFinishedEvent event.
SPStreamEvent newReportBugSuccessEvent(); ///< Create a new ReportBugSuccessEvent event.
SPStreamEvent newReportBugErrorEvent(); ///< Create a new ReportBugErrorEvent event.
SPStreamEvent newCertificateInstallSuccessEvent(); ///< Create a new CertificateInstallSuccessEvent event.
SPStreamEvent newCertificateInstallCanceledEvent(); ///< Create a new CertificateInstallCanceledEvent event.
SPStreamEvent newCertificateInstallFailedEvent(); ///< Create anew CertificateInstallFailedEvent event.
SPStreamEvent newShowMainWindowEvent(); ///< Create a new ShowMainWindowEvent event.
// Login events
SPStreamEvent newLoginError(grpc::LoginErrorType error, QString const &message); ///< Create a new LoginError event.
SPStreamEvent newLoginTfaRequestedEvent(QString const &username); ///< Create a new LoginTfaRequestedEvent event.
SPStreamEvent newLoginTwoPasswordsRequestedEvent(); ///< Create a new LoginTwoPasswordsRequestedEvent event.
SPStreamEvent newLoginTwoPasswordsRequestedEvent(QString const &username); ///< Create a new LoginTwoPasswordsRequestedEvent event.
SPStreamEvent newLoginFinishedEvent(QString const &userID, bool wasSignedOut); ///< Create a new LoginFinishedEvent event.
SPStreamEvent newLoginAlreadyLoggedInEvent(QString const &userID); ///< Create a new LoginAlreadyLoggedInEvent event.

View File

@ -373,14 +373,6 @@ grpc::Status GRPCClient::reportBug(QString const &category, QString const &descr
}
//****************************************************************************************************************************************************
/// \param[in] folderPath of the folder where the TLS files should be stored.
//****************************************************************************************************************************************************
grpc::Status GRPCClient::exportTLSCertificates(QString const &folderPath) {
return this->logGRPCCallStatus(this->setString(&Bridge::Stub::ExportTLSCertificates, folderPath), __FUNCTION__);
}
//****************************************************************************************************************************************************
/// \param[out] outIMAPPort The IMAP port.
/// \param[out] outSMTPPort The SMTP port.
@ -811,6 +803,32 @@ grpc::Status GRPCClient::setCurrentKeychain(QString const &keychain) {
}
//****************************************************************************************************************************************************
/// \param[out] outIsInstalled is The Bridge certificate installed in the keychain.
/// \return The status for the call
//****************************************************************************************************************************************************
grpc::Status GRPCClient::isTLSCertificateInstalled(bool &outIsInstalled) {
return this->logGRPCCallStatus(this->getBool(&Bridge::Stub::IsTLSCertificateInstalled, outIsInstalled), __FUNCTION__);
}
//****************************************************************************************************************************************************
/// \return The status for the gRPC call.
//****************************************************************************************************************************************************
grpc::Status GRPCClient::installTLSCertificate() {
return this->logGRPCCallStatus(this->simpleMethod(&Bridge::Stub::InstallTLSCertificate), __FUNCTION__);
}
//****************************************************************************************************************************************************
/// \param[in] folderPath of the folder where the TLS files should be stored.
/// \return The status for the gRPC call.
//****************************************************************************************************************************************************
grpc::Status GRPCClient::exportTLSCertificates(QString const &folderPath) {
return this->logGRPCCallStatus(this->setString(&Bridge::Stub::ExportTLSCertificates, folderPath), __FUNCTION__);
}
//****************************************************************************************************************************************************
/// \return true iff the event stream is active.
//****************************************************************************************************************************************************
@ -1134,6 +1152,18 @@ void GRPCClient::processAppEvent(AppEvent const &event) {
this->logTrace("App event received: ReportBugFallback.");
emit reportBugFallback();
break;
case AppEvent::kCertificateInstallSuccess:
this->logTrace("App event received: CertificateInstallSuccess.");
emit certificateInstallSuccess();
break;
case AppEvent::kCertificateInstallCanceled:
this->logTrace("App event received: CertificateInstallCanceled.");
emit certificateInstallCanceled();
break;
case AppEvent::kCertificateInstallFailed:
this->logTrace("App event received: CertificateInstallFailed.");
emit certificateInstallFailed();
break;
default:
this->logError("Unknown App event received.");
}
@ -1182,7 +1212,7 @@ void GRPCClient::processLoginEvent(LoginEvent const &event) {
break;
case LoginEvent::kTwoPasswordRequested:
this->logTrace("Login event received: TwoPasswordRequested.");
emit login2PasswordRequested();
emit login2PasswordRequested(QString::fromStdString(event.twopasswordrequested().username()));
break;
case LoginEvent::kFinished: {
this->logTrace("Login event received: Finished.");
@ -1517,4 +1547,5 @@ grpc::Status GRPCClient::KBArticleClicked(QString const &article) {
return this->logGRPCCallStatus(stub_->KBArticleClicked(this->clientContext().get(), s, &empty), __FUNCTION__);
}
} // namespace bridgepp

View File

@ -78,7 +78,6 @@ public: // member functions.
grpc::Status setColorSchemeName(QString const &name); ///< Performs the "setColorSchemeName' gRPC call.
grpc::Status currentEmailClient(QString &outName); ///< Performs the 'currentEmailClient' gRPC call.
grpc::Status reportBug(QString const &category, QString const &description, QString const &address, QString const &emailClient, bool includeLogs); ///< Performs the 'ReportBug' gRPC call.
grpc::Status exportTLSCertificates(QString const &folderPath); ///< Performs the 'ExportTLSCertificates' gRPC call.
grpc::Status quit(); ///< Perform the "Quit" gRPC call.
grpc::Status restart(); ///< Performs the Restart gRPC call.
grpc::Status triggerReset(); ///< Performs the triggerReset gRPC call.
@ -103,6 +102,9 @@ signals: // app related signals
void reportBugSuccess();
void reportBugError();
void reportBugFallback();
void certificateInstallSuccess();
void certificateInstallCanceled();
void certificateInstallFailed();
void showMainWindow();
// cache related calls
@ -144,10 +146,10 @@ signals:
void loginUsernamePasswordError(QString const &errMsg);
void loginFreeUserError();
void loginConnectionError(QString const &errMsg);
void login2FARequested(QString const &userName);
void login2FARequested(QString const &username);
void login2FAError(QString const &errMsg);
void login2FAErrorAbort(QString const &errMsg);
void login2PasswordRequested();
void login2PasswordRequested(QString const &username);
void login2PasswordError(QString const &errMsg);
void login2PasswordErrorAbort(QString const &errMsg);
void loginFinished(QString const &userID, bool wasSignedOut);
@ -201,6 +203,11 @@ public: // keychain related calls
grpc::Status currentKeychain(QString &outKeychain);
grpc::Status setCurrentKeychain(QString const &keychain);
public: // cert related calls
grpc::Status isTLSCertificateInstalled(bool &outIsInstalled); ///< Perform the 'IsTLSCertificateInstalled' gRPC call.
grpc::Status installTLSCertificate(); ///< Perform the 'InstallTLSCertificate' gRPC call.
grpc::Status exportTLSCertificates(QString const &folderPath); ///< Performs the 'ExportTLSCertificates' gRPC call.
signals:
void changeKeychainFinished();
void hasNoKeychain();

View File

@ -325,6 +325,10 @@ float User::syncProgress() const {
/// \param[in] progress The progress ratio.
//****************************************************************************************************************************************************
void User::setSyncProgress(float progress) {
// In some cases, we may have missed the syncStarted event because it was sent by bridge before the userChanged event,
// so we force the state to 'syncing' (GODT-2932).
this->setIsSyncing(true);
if (qAbs(syncProgress_ - progress) < 0.00001) {
return;
}

View File

@ -23,6 +23,7 @@ import (
"github.com/ProtonMail/go-proton-api"
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
"github.com/ProtonMail/proton-bridge/v3/internal/certs"
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
"github.com/abiosoft/ishell"
@ -297,6 +298,17 @@ func (f *frontendCLI) configureAppleMail(c *ishell.Context) {
return
}
cert, _ := f.bridge.GetBridgeTLSCert()
installer := certs.NewInstaller()
if !installer.IsCertInstalled(cert) {
f.Println("Apple Mail requires that a TLS certificate for bridge IMAP and SMTP server is installed in your system keychain.")
f.Println("Please provide your credentials in the system popup dialog in order to continue.")
if err := installer.InstallCert(cert); err != nil {
f.printAndLogError(err)
return
}
}
if err := f.bridge.ConfigureAppleMail(context.Background(), user.UserID, user.Addresses[0]); err != nil {
f.printAndLogError(err)
return

View File

@ -21,6 +21,7 @@ package cli
import (
"errors"
"os"
"runtime"
"github.com/ProtonMail/gluon/async"
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
@ -145,25 +146,52 @@ func New(
})
fe.AddCmd(dohCmd)
// Apple Mail commands.
configureCmd := &ishell.Cmd{
Name: "configure-apple-mail",
Help: "Configures Apple Mail to use ProtonMail Bridge",
Func: fe.configureAppleMail,
//goland:noinspection GoBoolExpressions
if runtime.GOOS == "darwin" {
// Apple Mail commands.
configureCmd := &ishell.Cmd{
Name: "configure-apple-mail",
Help: "Configures Apple Mail to use ProtonMail Bridge",
Func: fe.configureAppleMail,
}
fe.AddCmd(configureCmd)
}
fe.AddCmd(configureCmd)
// TLS commands.
fe.AddCmd(&ishell.Cmd{
Name: "export-tls-cert",
Help: "Export the TLS certificate used by the Bridge",
certCmd := &ishell.Cmd{
Name: "cert",
Help: "Manage the TLS certificate used by Bridge",
}
//goland:noinspection GoBoolExpressions
if runtime.GOOS == "darwin" {
certCmd.AddCmd(&ishell.Cmd{
Name: "status",
Help: "Check if the TLS certificate used by Bridge is installed in the OS keychain",
Func: fe.tlsCertStatus,
})
certCmd.AddCmd(&ishell.Cmd{
Name: "install",
Help: "Install TLS certificate used by Bridge in the OS keychain",
Func: fe.installTLSCert,
})
certCmd.AddCmd(&ishell.Cmd{
Name: "uninstall",
Help: "Uninstall the TLS certificate used by Bridge from the OS keychain",
Func: fe.uninstallTLSCert,
})
}
certCmd.AddCmd(&ishell.Cmd{
Name: "export",
Help: "Export the TLS certificate used by Bridge",
Func: fe.exportTLSCerts,
})
fe.AddCmd(&ishell.Cmd{
Name: "import-tls-cert",
Help: "Import a TLS certificate to be used by the Bridge",
certCmd.AddCmd(&ishell.Cmd{
Name: "import",
Help: "Import a TLS certificate to be used by Bridge",
Func: fe.importTLSCerts,
})
fe.AddCmd(certCmd)
// All mail visibility commands.
allMailCmd := &ishell.Cmd{

View File

@ -27,6 +27,7 @@ import (
"strings"
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
"github.com/ProtonMail/proton-bridge/v3/internal/certs"
"github.com/ProtonMail/proton-bridge/v3/pkg/ports"
"github.com/abiosoft/ishell"
)
@ -240,6 +241,50 @@ func (f *frontendCLI) setGluonLocation(c *ishell.Context) {
}
}
func (f *frontendCLI) tlsCertStatus(_ *ishell.Context) {
cert, _ := f.bridge.GetBridgeTLSCert()
installer := certs.NewInstaller()
if installer.IsCertInstalled(cert) {
f.Println("The Bridge TLS certificate is already installed in the OS keychain.")
} else {
f.Println("The Bridge TLS certificate is not installed in the OS keychain.")
}
}
func (f *frontendCLI) installTLSCert(_ *ishell.Context) {
cert, _ := f.bridge.GetBridgeTLSCert()
installer := certs.NewInstaller()
if installer.IsCertInstalled(cert) {
f.printAndLogError(errors.New("the Bridge TLS certificate is already installed in the OS keychain"))
return
}
f.Println("Please provide your credentials in the system popup dialog in order to continue.")
if err := installer.InstallCert(cert); err != nil {
f.printAndLogError(err)
return
}
f.Println("The Bridge TLS certificate was successfully installed in the OS keychain.")
}
func (f *frontendCLI) uninstallTLSCert(_ *ishell.Context) {
cert, _ := f.bridge.GetBridgeTLSCert()
installer := certs.NewInstaller()
if !installer.IsCertInstalled(cert) {
f.printAndLogError(errors.New("the Bridge TLS certificate is not installed in the OS keychain"))
return
}
f.Println("Please provide your credentials in the system popup dialog in order to continue.")
if err := installer.UninstallCert(cert); err != nil {
f.printAndLogError(err)
return
}
f.Println("The Bridge TLS certificate was successfully uninstalled from the OS keychain.")
}
func (f *frontendCLI) exportTLSCerts(c *ishell.Context) {
if location := f.readStringInAttempts("Enter a path to which to export the TLS certificate used for IMAP and SMTP", c.ReadLine, f.isCacheLocationUsable); location != "" {
cert, key := f.bridge.GetBridgeTLSCert()

File diff suppressed because it is too large Load Diff

View File

@ -56,7 +56,6 @@ service Bridge {
rpc ColorSchemeName(google.protobuf.Empty) returns (google.protobuf.StringValue); // TODO Color scheme should probably entirely be managed by the client.
rpc CurrentEmailClient(google.protobuf.Empty) returns (google.protobuf.StringValue);
rpc ReportBug(ReportBugRequest) returns (google.protobuf.Empty);
rpc ExportTLSCertificates(google.protobuf.StringValue) returns (google.protobuf.Empty);
rpc ForceLauncher(google.protobuf.StringValue) returns (google.protobuf.Empty);
rpc SetMainExecutable(google.protobuf.StringValue) returns (google.protobuf.Empty);
@ -103,6 +102,11 @@ service Bridge {
rpc AutoconfigClicked(google.protobuf.StringValue) returns (google.protobuf.Empty);
rpc KBArticleClicked(google.protobuf.StringValue) returns (google.protobuf.Empty);
// TLS certificate related calls
rpc IsTLSCertificateInstalled(google.protobuf.Empty) returns (google.protobuf.BoolValue);
rpc InstallTLSCertificate(google.protobuf.Empty) returns (google.protobuf.Empty);
rpc ExportTLSCertificates(google.protobuf.StringValue) returns (google.protobuf.Empty);
// Server -> Client event stream
rpc RunEventStream(EventStreamRequest) returns (stream StreamEvent); // Keep streaming until StopEventStream is called.
rpc StopEventStream(google.protobuf.Empty) returns (google.protobuf.Empty);
@ -262,6 +266,9 @@ message AppEvent {
ReportBugErrorEvent reportBugError = 6;
ShowMainWindowEvent showMainWindow = 7;
ReportBugFallbackEvent reportBugFallback = 8;
CertificateInstallSuccessEvent certificateInstallSuccess = 9;
CertificateInstallCanceledEvent certificateInstallCanceled = 10;
CertificateInstallFailedEvent certificateInstallFailed = 11;
}
}
@ -276,6 +283,9 @@ message ReportBugSuccessEvent {}
message ReportBugErrorEvent {}
message ShowMainWindowEvent {}
message ReportBugFallbackEvent {}
message CertificateInstallSuccessEvent {}
message CertificateInstallCanceledEvent {}
message CertificateInstallFailedEvent {}
//**********************************************************
// Login related events
@ -309,7 +319,9 @@ message LoginTfaRequestedEvent {
string username = 1;
}
message LoginTwoPasswordsRequestedEvent {}
message LoginTwoPasswordsRequestedEvent {
string username = 1;
}
message LoginFinishedEvent {
string userID = 1;

View File

@ -51,7 +51,6 @@ type BridgeClient interface {
ColorSchemeName(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.StringValue, error)
CurrentEmailClient(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.StringValue, error)
ReportBug(ctx context.Context, in *ReportBugRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
ExportTLSCertificates(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error)
ForceLauncher(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error)
SetMainExecutable(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error)
// login
@ -90,6 +89,10 @@ type BridgeClient interface {
ReportBugClicked(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
AutoconfigClicked(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error)
KBArticleClicked(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error)
// TLS certificate related calls
IsTLSCertificateInstalled(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error)
InstallTLSCertificate(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
ExportTLSCertificates(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error)
// Server -> Client event stream
RunEventStream(ctx context.Context, in *EventStreamRequest, opts ...grpc.CallOption) (Bridge_RunEventStreamClient, error)
StopEventStream(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
@ -337,15 +340,6 @@ func (c *bridgeClient) ReportBug(ctx context.Context, in *ReportBugRequest, opts
return out, nil
}
func (c *bridgeClient) ExportTLSCertificates(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) {
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, "/grpc.Bridge/ExportTLSCertificates", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bridgeClient) ForceLauncher(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) {
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, "/grpc.Bridge/ForceLauncher", in, out, opts...)
@ -625,6 +619,33 @@ func (c *bridgeClient) KBArticleClicked(ctx context.Context, in *wrapperspb.Stri
return out, nil
}
func (c *bridgeClient) IsTLSCertificateInstalled(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error) {
out := new(wrapperspb.BoolValue)
err := c.cc.Invoke(ctx, "/grpc.Bridge/IsTLSCertificateInstalled", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bridgeClient) InstallTLSCertificate(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, "/grpc.Bridge/InstallTLSCertificate", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bridgeClient) ExportTLSCertificates(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) {
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, "/grpc.Bridge/ExportTLSCertificates", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bridgeClient) RunEventStream(ctx context.Context, in *EventStreamRequest, opts ...grpc.CallOption) (Bridge_RunEventStreamClient, error) {
stream, err := c.cc.NewStream(ctx, &Bridge_ServiceDesc.Streams[0], "/grpc.Bridge/RunEventStream", opts...)
if err != nil {
@ -697,7 +718,6 @@ type BridgeServer interface {
ColorSchemeName(context.Context, *emptypb.Empty) (*wrapperspb.StringValue, error)
CurrentEmailClient(context.Context, *emptypb.Empty) (*wrapperspb.StringValue, error)
ReportBug(context.Context, *ReportBugRequest) (*emptypb.Empty, error)
ExportTLSCertificates(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error)
ForceLauncher(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error)
SetMainExecutable(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error)
// login
@ -736,6 +756,10 @@ type BridgeServer interface {
ReportBugClicked(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
AutoconfigClicked(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error)
KBArticleClicked(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error)
// TLS certificate related calls
IsTLSCertificateInstalled(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error)
InstallTLSCertificate(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
ExportTLSCertificates(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error)
// Server -> Client event stream
RunEventStream(*EventStreamRequest, Bridge_RunEventStreamServer) error
StopEventStream(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
@ -824,9 +848,6 @@ func (UnimplementedBridgeServer) CurrentEmailClient(context.Context, *emptypb.Em
func (UnimplementedBridgeServer) ReportBug(context.Context, *ReportBugRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method ReportBug not implemented")
}
func (UnimplementedBridgeServer) ExportTLSCertificates(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method ExportTLSCertificates not implemented")
}
func (UnimplementedBridgeServer) ForceLauncher(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method ForceLauncher not implemented")
}
@ -920,6 +941,15 @@ func (UnimplementedBridgeServer) AutoconfigClicked(context.Context, *wrapperspb.
func (UnimplementedBridgeServer) KBArticleClicked(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method KBArticleClicked not implemented")
}
func (UnimplementedBridgeServer) IsTLSCertificateInstalled(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error) {
return nil, status.Errorf(codes.Unimplemented, "method IsTLSCertificateInstalled not implemented")
}
func (UnimplementedBridgeServer) InstallTLSCertificate(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method InstallTLSCertificate not implemented")
}
func (UnimplementedBridgeServer) ExportTLSCertificates(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method ExportTLSCertificates not implemented")
}
func (UnimplementedBridgeServer) RunEventStream(*EventStreamRequest, Bridge_RunEventStreamServer) error {
return status.Errorf(codes.Unimplemented, "method RunEventStream not implemented")
}
@ -1407,24 +1437,6 @@ func _Bridge_ReportBug_Handler(srv interface{}, ctx context.Context, dec func(in
return interceptor(ctx, in, info, handler)
}
func _Bridge_ExportTLSCertificates_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(wrapperspb.StringValue)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BridgeServer).ExportTLSCertificates(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/grpc.Bridge/ExportTLSCertificates",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BridgeServer).ExportTLSCertificates(ctx, req.(*wrapperspb.StringValue))
}
return interceptor(ctx, in, info, handler)
}
func _Bridge_ForceLauncher_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(wrapperspb.StringValue)
if err := dec(in); err != nil {
@ -1983,6 +1995,60 @@ func _Bridge_KBArticleClicked_Handler(srv interface{}, ctx context.Context, dec
return interceptor(ctx, in, info, handler)
}
func _Bridge_IsTLSCertificateInstalled_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(emptypb.Empty)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BridgeServer).IsTLSCertificateInstalled(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/grpc.Bridge/IsTLSCertificateInstalled",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BridgeServer).IsTLSCertificateInstalled(ctx, req.(*emptypb.Empty))
}
return interceptor(ctx, in, info, handler)
}
func _Bridge_InstallTLSCertificate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(emptypb.Empty)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BridgeServer).InstallTLSCertificate(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/grpc.Bridge/InstallTLSCertificate",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BridgeServer).InstallTLSCertificate(ctx, req.(*emptypb.Empty))
}
return interceptor(ctx, in, info, handler)
}
func _Bridge_ExportTLSCertificates_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(wrapperspb.StringValue)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BridgeServer).ExportTLSCertificates(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/grpc.Bridge/ExportTLSCertificates",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BridgeServer).ExportTLSCertificates(ctx, req.(*wrapperspb.StringValue))
}
return interceptor(ctx, in, info, handler)
}
func _Bridge_RunEventStream_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(EventStreamRequest)
if err := stream.RecvMsg(m); err != nil {
@ -2133,10 +2199,6 @@ var Bridge_ServiceDesc = grpc.ServiceDesc{
MethodName: "ReportBug",
Handler: _Bridge_ReportBug_Handler,
},
{
MethodName: "ExportTLSCertificates",
Handler: _Bridge_ExportTLSCertificates_Handler,
},
{
MethodName: "ForceLauncher",
Handler: _Bridge_ForceLauncher_Handler,
@ -2261,6 +2323,18 @@ var Bridge_ServiceDesc = grpc.ServiceDesc{
MethodName: "KBArticleClicked",
Handler: _Bridge_KBArticleClicked_Handler,
},
{
MethodName: "IsTLSCertificateInstalled",
Handler: _Bridge_IsTLSCertificateInstalled_Handler,
},
{
MethodName: "InstallTLSCertificate",
Handler: _Bridge_InstallTLSCertificate_Handler,
},
{
MethodName: "ExportTLSCertificates",
Handler: _Bridge_ExportTLSCertificates_Handler,
},
{
MethodName: "StopEventStream",
Handler: _Bridge_StopEventStream_Handler,

View File

@ -45,6 +45,18 @@ func NewReportBugFallbackEvent() *StreamEvent {
return appEvent(&AppEvent{Event: &AppEvent_ReportBugFallback{ReportBugFallback: &ReportBugFallbackEvent{}}})
}
func NewCertInstallSuccessEvent() *StreamEvent {
return appEvent(&AppEvent{Event: &AppEvent_CertificateInstallSuccess{CertificateInstallSuccess: &CertificateInstallSuccessEvent{}}})
}
func NewCertInstallCanceledEvent() *StreamEvent {
return appEvent(&AppEvent{Event: &AppEvent_CertificateInstallCanceled{CertificateInstallCanceled: &CertificateInstallCanceledEvent{}}})
}
func NewCertInstallFailedEvent() *StreamEvent {
return appEvent(&AppEvent{Event: &AppEvent_CertificateInstallFailed{CertificateInstallFailed: &CertificateInstallFailedEvent{}}})
}
func NewShowMainWindowEvent() *StreamEvent {
return appEvent(&AppEvent{Event: &AppEvent_ShowMainWindow{ShowMainWindow: &ShowMainWindowEvent{}}})
}
@ -57,8 +69,8 @@ func NewLoginTfaRequestedEvent(username string) *StreamEvent {
return loginEvent(&LoginEvent{Event: &LoginEvent_TfaRequested{TfaRequested: &LoginTfaRequestedEvent{Username: username}}})
}
func NewLoginTwoPasswordsRequestedEvent() *StreamEvent {
return loginEvent(&LoginEvent{Event: &LoginEvent_TwoPasswordRequested{}})
func NewLoginTwoPasswordsRequestedEvent(username string) *StreamEvent {
return loginEvent(&LoginEvent{Event: &LoginEvent_TwoPasswordRequested{TwoPasswordRequested: &LoginTwoPasswordsRequestedEvent{Username: username}}})
}
func NewLoginFinishedEvent(userID string, wasSignedOut bool) *StreamEvent {

View File

@ -0,0 +1,79 @@
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package grpc
import (
"context"
"errors"
"os"
"path/filepath"
"github.com/ProtonMail/gluon/async"
"github.com/ProtonMail/proton-bridge/v3/internal/certs"
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/wrapperspb"
)
func (s *Service) IsTLSCertificateInstalled(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error) {
s.log.Info("IsTLSCertificateInstalled")
cert, _ := s.bridge.GetBridgeTLSCert()
return &wrapperspb.BoolValue{Value: certs.NewInstaller().IsCertInstalled(cert)}, nil
}
func (s *Service) InstallTLSCertificate(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
s.log.Info("InstallTLSCertificate")
go func() {
defer async.HandlePanic(s.panicHandler)
cert, _ := s.bridge.GetBridgeTLSCert()
err := certs.NewInstaller().InstallCert(cert)
switch {
case err == nil:
_ = s.SendEvent(NewCertInstallSuccessEvent())
case errors.Is(err, certs.ErrUserCanceledCertificateInstall):
_ = s.SendEvent(NewCertInstallCanceledEvent())
default:
_ = s.SendEvent(NewCertInstallFailedEvent())
}
}()
return &emptypb.Empty{}, nil
}
func (s *Service) ExportTLSCertificates(_ context.Context, folderPath *wrapperspb.StringValue) (*emptypb.Empty, error) {
s.log.WithField("folderPath", folderPath).Info("ExportTLSCertificates")
go func() {
defer async.HandlePanic(s.panicHandler)
cert, key := s.bridge.GetBridgeTLSCert()
if err := os.WriteFile(filepath.Join(folderPath.Value, "cert.pem"), cert, 0o600); err != nil {
_ = s.SendEvent(NewGenericErrorEvent(ErrorCode_TLS_CERT_EXPORT_ERROR))
}
if err := os.WriteFile(filepath.Join(folderPath.Value, "key.pem"), key, 0o600); err != nil {
_ = s.SendEvent(NewGenericErrorEvent(ErrorCode_TLS_KEY_EXPORT_ERROR))
}
}()
return &emptypb.Empty{}, nil
}

View File

@ -21,8 +21,6 @@ import (
"context"
"encoding/base64"
"errors"
"os"
"path/filepath"
"runtime"
"github.com/Masterminds/semver/v3"
@ -364,26 +362,6 @@ func (s *Service) ReportBug(_ context.Context, report *ReportBugRequest) (*empty
return &emptypb.Empty{}, nil
}
func (s *Service) ExportTLSCertificates(_ context.Context, folderPath *wrapperspb.StringValue) (*emptypb.Empty, error) {
s.log.WithField("folderPath", folderPath).Info("ExportTLSCertificates")
go func() {
defer async.HandlePanic(s.panicHandler)
cert, key := s.bridge.GetBridgeTLSCert()
if err := os.WriteFile(filepath.Join(folderPath.Value, "cert.pem"), cert, 0o600); err != nil {
_ = s.SendEvent(NewGenericErrorEvent(ErrorCode_TLS_CERT_EXPORT_ERROR))
}
if err := os.WriteFile(filepath.Join(folderPath.Value, "key.pem"), key, 0o600); err != nil {
_ = s.SendEvent(NewGenericErrorEvent(ErrorCode_TLS_KEY_EXPORT_ERROR))
}
}()
return &emptypb.Empty{}, nil
}
func (s *Service) ForceLauncher(_ context.Context, launcher *wrapperspb.StringValue) (*emptypb.Empty, error) {
s.log.WithField("launcher", launcher.Value).Debug("ForceLauncher")
@ -446,7 +424,7 @@ func (s *Service) Login(_ context.Context, login *LoginRequest) (*emptypb.Empty,
_ = s.SendEvent(NewLoginTfaRequestedEvent(login.Username))
case auth.PasswordMode == proton.TwoPasswordMode:
_ = s.SendEvent(NewLoginTwoPasswordsRequestedEvent())
_ = s.SendEvent(NewLoginTwoPasswordsRequestedEvent(login.Username))
default:
s.finishLogin()
@ -491,7 +469,7 @@ func (s *Service) Login2FA(_ context.Context, login *LoginRequest) (*emptypb.Emp
}
if s.auth.PasswordMode == proton.TwoPasswordMode {
_ = s.SendEvent(NewLoginTwoPasswordsRequestedEvent())
_ = s.SendEvent(NewLoginTwoPasswordsRequestedEvent(login.Username))
return
}

View File

@ -126,7 +126,7 @@ func (s *Service) StartEventTest() error {
// login
NewLoginError(LoginErrorType_FREE_USER, "error"),
NewLoginTfaRequestedEvent(dummyAddress),
NewLoginTwoPasswordsRequestedEvent(),
NewLoginTwoPasswordsRequestedEvent(dummyAddress),
NewLoginFinishedEvent("userID", false),
NewLoginAlreadyLoggedInEvent("userID"),

View File

@ -189,7 +189,7 @@ func (s *Connector) GetMessageLiteral(ctx context.Context, id imap.MessageID) ([
var literal []byte
err = s.identityState.WithAddrKR(msg.AddressID, func(_, addrKR *crypto.KeyRing) error {
l, buildErr := message.BuildRFC822(addrKR, msg.Message, msg.AttData, defaultMessageJobOpts())
l, buildErr := message.DecryptAndBuildRFC822(addrKR, msg.Message, msg.AttData, defaultMessageJobOpts())
if buildErr != nil {
return buildErr
}
@ -279,7 +279,7 @@ func (s *Connector) CreateMessage(ctx context.Context, _ connector.IMAPStateWrit
if err := s.identityState.WithAddrKR(full.AddressID, func(_, addrKR *crypto.KeyRing) error {
var err error
if literal, err = message.BuildRFC822(addrKR, full.Message, full.AttData, defaultMessageJobOpts()); err != nil {
if literal, err = message.DecryptAndBuildRFC822(addrKR, full.Message, full.AttData, defaultMessageJobOpts()); err != nil {
return err
}
@ -702,7 +702,7 @@ func (s *Connector) importMessage(
return fmt.Errorf("failed to fetch message: %w", err)
}
if literal, err = message.BuildRFC822(addrKR, full.Message, full.AttData, defaultMessageJobOpts()); err != nil {
if literal, err = message.DecryptAndBuildRFC822(addrKR, full.Message, full.AttData, defaultMessageJobOpts()); err != nil {
return fmt.Errorf("failed to build message: %w", err)
}

View File

@ -57,7 +57,7 @@ func buildRFC822(apiLabels map[string]proton.Label, full proton.FullMessage, add
buffer.Grow(full.Size)
if buildErr := message.BuildRFC822Into(addrKR, full.Message, full.AttData, defaultMessageJobOpts(), buffer); buildErr != nil {
if buildErr := message.DecryptAndBuildRFC822Into(addrKR, full.Message, full.AttData, defaultMessageJobOpts(), buffer); buildErr != nil {
update = newMessageCreatedFailedUpdate(apiLabels, full.MessageMetadata, buildErr)
err = buildErr
} else if created, parseErr := newMessageCreatedUpdate(apiLabels, full.MessageMetadata, buffer.Bytes()); parseErr != nil {

View File

@ -46,7 +46,7 @@ func (s SyncMessageBuilder) BuildMessage(
) (syncservice.BuildResult, error) {
buffer.Grow(full.Size)
if err := message.BuildRFC822Into(addrKR, full.Message, full.AttData, defaultMessageJobOpts(), buffer); err != nil {
if err := message.DecryptAndBuildRFC822Into(addrKR, full.Message, full.AttData, defaultMessageJobOpts(), buffer); err != nil {
return syncservice.BuildResult{}, err
}

View File

@ -535,7 +535,7 @@ func getContactSettings(
return proton.ContactSettings{}, fmt.Errorf("failed to get contact: %w", err)
}
return contact.GetSettings(userKR, recipient)
return contact.GetSettings(userKR, recipient, proton.CardTypeSigned)
}
func getMessageSender(parser *parser.Parser) (string, bool) {

View File

@ -340,10 +340,9 @@ func TestDownloadStage_JobAbortsOnAttachmentDownloadError(t *testing.T) {
MessageMetadata: proton.MessageMetadata{
ID: "msg",
},
Header: "",
ParsedHeaders: nil,
Body: "",
MIMEType: "",
Header: "",
Body: "",
MIMEType: "",
Attachments: []proton.Attachment{{
ID: "attach",
}},
@ -387,11 +386,10 @@ func buildDownloadStageData(tj *tjob, numMessages int, with422 bool) ([]string,
ID: msgID,
Size: len([]byte(msgID)),
},
Header: "",
ParsedHeaders: nil,
Body: msgID,
MIMEType: "",
Attachments: nil,
Header: "",
Body: msgID,
MIMEType: "",
Attachments: nil,
},
AttData: nil,
}

View File

@ -66,16 +66,6 @@ func (vault *Vault) SetBridgeTLSCertKey(cert, key []byte) error {
})
}
func (vault *Vault) GetCertsInstalled() bool {
return vault.getSafe().Certs.Installed
}
func (vault *Vault) SetCertsInstalled(installed bool) error {
return vault.modSafe(func(data *Data) {
data.Certs.Installed = installed
})
}
func readPEMCert(certPEMPath, keyPEMPath string) ([]byte, []byte, error) {
certPEM, err := os.ReadFile(filepath.Clean(certPEMPath))
if err != nil {

View File

@ -31,13 +31,4 @@ func TestVault_TLSCerts(t *testing.T) {
cert, key := s.GetBridgeTLSCert()
require.NotEmpty(t, cert)
require.NotEmpty(t, key)
// Check the certificates are not installed.
require.False(t, s.GetCertsInstalled())
// Install the certificates.
require.NoError(t, s.SetCertsInstalled(true))
// Check the certificates are installed.
require.True(t, s.GetCertsInstalled())
}

View File

@ -20,8 +20,7 @@ package vault
import "github.com/ProtonMail/proton-bridge/v3/internal/certs"
type Certs struct {
Bridge Cert
Installed bool
Bridge Cert
// If non-empty, the path to the PEM-encoded certificate file.
CustomCertPath string

View File

@ -19,8 +19,6 @@ package message
import (
"bytes"
"encoding/base64"
"io"
"mime"
"net/mail"
"strings"
@ -47,48 +45,36 @@ var (
// InternalIDDomain is used as a placeholder for reference/message ID headers to improve compatibility with various clients.
const InternalIDDomain = `protonmail.internalid`
func BuildRFC822(kr *crypto.KeyRing, msg proton.Message, attData [][]byte, opts JobOptions) ([]byte, error) {
buf := new(bytes.Buffer)
if err := BuildRFC822Into(kr, msg, attData, opts, buf); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func BuildRFC822Into(kr *crypto.KeyRing, msg proton.Message, attData [][]byte, opts JobOptions, buf *bytes.Buffer) error {
func BuildRFC822Into(kr *crypto.KeyRing, decrypted *DecryptedMessage, opts JobOptions, buf *bytes.Buffer) error {
switch {
case len(msg.Attachments) > 0:
return buildMultipartRFC822(kr, msg, attData, opts, buf)
case len(decrypted.Msg.Attachments) > 0:
return buildMultipartRFC822(decrypted, opts, buf)
case msg.MIMEType == "multipart/mixed":
return buildPGPRFC822(kr, msg, opts, buf)
case decrypted.Msg.MIMEType == "multipart/mixed":
return buildPGPRFC822(kr, decrypted, opts, buf)
default:
return buildSimpleRFC822(kr, msg, opts, buf)
return buildSimpleRFC822(decrypted, opts, buf)
}
}
func buildSimpleRFC822(kr *crypto.KeyRing, msg proton.Message, opts JobOptions, buf *bytes.Buffer) error {
var decrypted bytes.Buffer
decrypted.Grow(len(msg.Body))
if err := msg.DecryptInto(kr, &decrypted); err != nil {
func buildSimpleRFC822(decrypted *DecryptedMessage, opts JobOptions, buf *bytes.Buffer) error {
if decrypted.BodyErr != nil {
if !opts.IgnoreDecryptionErrors {
return errors.Wrap(ErrDecryptionFailed, err.Error())
return decrypted.BodyErr
}
return buildMultipartRFC822(kr, msg, nil, opts, buf)
return buildMultipartRFC822(decrypted, opts, buf)
}
hdr := getTextPartHeader(getMessageHeader(msg, opts), decrypted.Bytes(), msg.MIMEType)
hdr := getTextPartHeader(getMessageHeader(decrypted.Msg, opts), decrypted.Body.Bytes(), decrypted.Msg.MIMEType)
w, err := message.CreateWriter(buf, hdr)
if err != nil {
return err
}
if _, err := w.Write(decrypted.Bytes()); err != nil {
if _, err := w.Write(decrypted.Body.Bytes()); err != nil {
return err
}
@ -96,15 +82,13 @@ func buildSimpleRFC822(kr *crypto.KeyRing, msg proton.Message, opts JobOptions,
}
func buildMultipartRFC822(
kr *crypto.KeyRing,
msg proton.Message,
attData [][]byte,
decrypted *DecryptedMessage,
opts JobOptions,
buf *bytes.Buffer,
) error {
boundary := newBoundary(msg.ID)
boundary := newBoundary(decrypted.Msg.ID)
hdr := getMessageHeader(msg, opts)
hdr := getMessageHeader(decrypted.Msg, opts)
hdr.SetContentType("multipart/mixed", map[string]string{"boundary": boundary.gen()})
@ -115,31 +99,31 @@ func buildMultipartRFC822(
var (
inlineAtts []proton.Attachment
inlineData [][]byte
inlineData []DecryptedAttachment
attachAtts []proton.Attachment
attachData [][]byte
attachData []DecryptedAttachment
)
for index, att := range msg.Attachments {
for index, att := range decrypted.Msg.Attachments {
if att.Disposition == proton.InlineDisposition {
inlineAtts = append(inlineAtts, att)
inlineData = append(inlineData, attData[index])
inlineData = append(inlineData, decrypted.Attachments[index])
} else {
attachAtts = append(attachAtts, att)
attachData = append(attachData, attData[index])
attachData = append(attachData, decrypted.Attachments[index])
}
}
if len(inlineAtts) > 0 {
if err := writeRelatedParts(w, kr, boundary, msg, inlineAtts, inlineData, opts); err != nil {
if err := writeRelatedParts(w, boundary, decrypted, inlineAtts, inlineData, opts); err != nil {
return err
}
} else if err := writeTextPart(w, kr, msg, opts); err != nil {
} else if err := writeTextPart(w, decrypted, opts); err != nil {
return err
}
for i, att := range attachAtts {
if err := writeAttachmentPart(w, kr, att, attachData[i], opts); err != nil {
if err := writeAttachmentPart(w, att, attachData[i], opts); err != nil {
return err
}
}
@ -149,89 +133,53 @@ func buildMultipartRFC822(
func writeTextPart(
w *message.Writer,
kr *crypto.KeyRing,
msg proton.Message,
decrypted *DecryptedMessage,
opts JobOptions,
) error {
var decrypted bytes.Buffer
decrypted.Grow(len(msg.Body))
if err := msg.DecryptInto(kr, &decrypted); err != nil {
if decrypted.BodyErr != nil {
if !opts.IgnoreDecryptionErrors {
return errors.Wrap(ErrDecryptionFailed, err.Error())
return decrypted.BodyErr
}
return writeCustomTextPart(w, msg, err)
return writeCustomTextPart(w, decrypted, decrypted.BodyErr)
}
return writePart(w, getTextPartHeader(message.Header{}, decrypted.Bytes(), msg.MIMEType), decrypted.Bytes())
return writePart(w, getTextPartHeader(message.Header{}, decrypted.Body.Bytes(), decrypted.Msg.MIMEType), decrypted.Body.Bytes())
}
func writeAttachmentPart(
w *message.Writer,
kr *crypto.KeyRing,
att proton.Attachment,
attData []byte,
decryptedAttachment DecryptedAttachment,
opts JobOptions,
) error {
kps, err := base64.StdEncoding.DecodeString(att.KeyPackets)
if err != nil {
return err
}
// Use io.Multi
attachmentReader := io.MultiReader(bytes.NewReader(kps), bytes.NewReader(attData))
stream, err := kr.DecryptStream(attachmentReader, nil, crypto.GetUnixTime())
if err != nil {
if decryptedAttachment.Err != nil {
if !opts.IgnoreDecryptionErrors {
return errors.Wrap(ErrDecryptionFailed, err.Error())
return decryptedAttachment.Err
}
log.
WithField("attID", att.ID).
WithError(err).
Warn("Attachment decryption failed - construct")
WithError(decryptedAttachment.Err).
Warn("Attachment decryption failed")
var pgpMessageBuffer bytes.Buffer
pgpMessageBuffer.Grow(len(kps) + len(attData))
pgpMessageBuffer.Write(kps)
pgpMessageBuffer.Write(attData)
pgpMessageBuffer.Grow(len(decryptedAttachment.Packet) + len(decryptedAttachment.Encrypted))
pgpMessageBuffer.Write(decryptedAttachment.Packet)
pgpMessageBuffer.Write(decryptedAttachment.Encrypted)
return writeCustomAttachmentPart(w, att, &crypto.PGPMessage{Data: pgpMessageBuffer.Bytes()}, err)
return writeCustomAttachmentPart(w, att, &crypto.PGPMessage{Data: pgpMessageBuffer.Bytes()}, decryptedAttachment.Err)
}
var decryptBuffer bytes.Buffer
decryptBuffer.Grow(len(kps) + len(attData))
if _, err := decryptBuffer.ReadFrom(stream); err != nil {
if !opts.IgnoreDecryptionErrors {
return errors.Wrap(ErrDecryptionFailed, err.Error())
}
log.
WithField("attID", att.ID).
WithError(err).
Warn("Attachment decryption failed - stream")
var pgpMessageBuffer bytes.Buffer
pgpMessageBuffer.Grow(len(kps) + len(attData))
pgpMessageBuffer.Write(kps)
pgpMessageBuffer.Write(attData)
return writeCustomAttachmentPart(w, att, &crypto.PGPMessage{Data: pgpMessageBuffer.Bytes()}, err)
}
return writePart(w, getAttachmentPartHeader(att), decryptBuffer.Bytes())
return writePart(w, getAttachmentPartHeader(att), decryptedAttachment.Data.Bytes())
}
func writeRelatedParts(
w *message.Writer,
kr *crypto.KeyRing,
boundary *boundary,
msg proton.Message,
decrypted *DecryptedMessage,
atts []proton.Attachment,
attData [][]byte,
attData []DecryptedAttachment,
opts JobOptions,
) error {
hdr := message.Header{}
@ -239,12 +187,12 @@ func writeRelatedParts(
hdr.SetContentType("multipart/related", map[string]string{"boundary": boundary.gen()})
return createPart(w, hdr, func(rel *message.Writer) error {
if err := writeTextPart(rel, kr, msg, opts); err != nil {
if err := writeTextPart(rel, decrypted, opts); err != nil {
return err
}
for i, att := range atts {
if err := writeAttachmentPart(rel, kr, att, attData[i], opts); err != nil {
if err := writeAttachmentPart(rel, att, attData[i], opts); err != nil {
return err
}
}
@ -253,37 +201,34 @@ func writeRelatedParts(
})
}
func buildPGPRFC822(kr *crypto.KeyRing, msg proton.Message, opts JobOptions, buf *bytes.Buffer) error {
var decrypted bytes.Buffer
decrypted.Grow(len(msg.Body))
if err := msg.DecryptInto(kr, &decrypted); err != nil {
func buildPGPRFC822(kr *crypto.KeyRing, decrypted *DecryptedMessage, opts JobOptions, buf *bytes.Buffer) error {
if decrypted.BodyErr != nil {
if !opts.IgnoreDecryptionErrors {
return errors.Wrap(ErrDecryptionFailed, err.Error())
return decrypted.BodyErr
}
return buildPGPMIMEFallbackRFC822(msg, opts, buf)
return buildPGPMIMEFallbackRFC822(decrypted, opts, buf)
}
hdr := getMessageHeader(msg, opts)
hdr := getMessageHeader(decrypted.Msg, opts)
sigs, err := proton.ExtractSignatures(kr, msg.Body)
sigs, err := proton.ExtractSignatures(kr, decrypted.Msg.Body)
if err != nil {
log.WithError(err).WithField("id", msg.ID).Warn("Extract signature failed")
log.WithError(err).WithField("id", decrypted.Msg.ID).Warn("Extract signature failed")
}
if len(sigs) > 0 {
return writeMultipartSignedRFC822(hdr, decrypted.Bytes(), sigs[0], buf)
return writeMultipartSignedRFC822(hdr, decrypted.Body.Bytes(), sigs[0], buf)
}
return writeMultipartEncryptedRFC822(hdr, decrypted.Bytes(), buf)
return writeMultipartEncryptedRFC822(hdr, decrypted.Body.Bytes(), buf)
}
func buildPGPMIMEFallbackRFC822(msg proton.Message, opts JobOptions, buf *bytes.Buffer) error {
hdr := getMessageHeader(msg, opts)
func buildPGPMIMEFallbackRFC822(decrypted *DecryptedMessage, opts JobOptions, buf *bytes.Buffer) error {
hdr := getMessageHeader(decrypted.Msg, opts)
hdr.SetContentType("multipart/encrypted", map[string]string{
"boundary": newBoundary(msg.ID).gen(),
"boundary": newBoundary(decrypted.Msg.ID).gen(),
"protocol": "application/pgp-encrypted",
})
@ -307,7 +252,7 @@ func buildPGPMIMEFallbackRFC822(msg proton.Message, opts JobOptions, buf *bytes.
dataHdr.SetContentDisposition("inline", map[string]string{"filename": "encrypted.asc"})
dataHdr.Set("Content-Description", "OpenPGP encrypted message")
if err := writePart(w, dataHdr, []byte(msg.Body)); err != nil {
if err := writePart(w, dataHdr, []byte(decrypted.Msg.Body)); err != nil {
return err
}
@ -558,8 +503,8 @@ func getAttachmentPartHeader(att proton.Attachment) message.Header {
func toMessageHeader(hdr proton.Headers) message.Header {
var res message.Header
for key, val := range hdr {
for _, val := range val {
for _, key := range hdr.Order {
for _, val := range hdr.Values[key] {
// Using AddRaw instead of Add to save key-value pair as byte buffer within Header.
// This buffer is used latter on in message writer to construct message and avoid crash
// when key length is more than 76 characters long.

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